Implement Dynamic Multi Stack (K stacks) using only one Data Structure
Last Updated :
30 Oct, 2023
In this article, we will see how to create a data structure that can handle multiple stacks with growable size. The data structure needs to handle three operations:
- push(x, stackNum) = pushes value x to the stack numbered stackNum
- pop(stackNum) = pop the top element from the stack numbered stackNum
- top(stackNum) = shows the topmost element of the stack stackNum.
Example:
Suppose the given multi stack is [{1, 2}, {4, 6}, {9, 10}]
Input: push(3, 0), top(0)
push(7, 1), top(1)
pop(2), top(2)
Output: 3, 7, 9
Explanation: When 3 is pushed in stack 0, the stack becomes {1, 2, 3}. So the top element is 3.
When 7 is pushed in stack 1, the stack becomes {4, 6, 7}. So the top element is 7.
When topmost element is popped from stack 2, the stack becomes {9}. So the topmost element is 9
Approach: Follow the approach mentioned below to implement the idea.
- Store the size and the top index of every stack in arrays sizes[] and topIndex[].
- The sizes will be stored in a prefix sum array (using a prefix array sum will help us find the start/size of a stack in O(1) time)
- If the size of a stack reaches the maximum reserved capacity, expand the reserved size (multiply by 2)
- If the size of a stack gets down to a quarter of the reserved size shrink the reserved size (divide it by 2)
- Every time we need to expand/shrink a stack in the data structure, the indexes of other stacks might change so we need to take care of that. That is taken care by incrementing/decrementing value of sizes[] and topIndex[] arrays (we can do that in O(number of stacks) time).
Below is the implementation :
C++
#include <bits/stdc++.h>
using namespace std;
template <typename T>
// Class to implement multistack
class MultiStack {
int numberOfStacks;
vector<T> values;
vector<int> sizes, topIndex;
public:
// Constructor to create k stacks
// (by default 1)
MultiStack(int k = 1)
: numberOfStacks(k)
{
// reserve 2 elements for each stack first
values = vector<T>(numberOfStacks << 1);
// For each stack store the index
// of the element on the top
// and the size (starting point)
sizes = vector<int>(numberOfStacks);
topIndex = vector<int>(numberOfStacks);
// Sizes is a prefix sum vector
for (int size = 2, i = 0; i < numberOfStacks;
i++, size += 2)
sizes[i] = size, topIndex[i] = size - 2;
}
// Push a value in a stack
void push(int stackNum, T val)
{
// Check if the stack is full,
// if so Expand it
if (isFull(stackNum))
Expand(stackNum);
// Add the value to the top of the stack
// and increment the top index
values[topIndex[stackNum]++] = val;
}
// Pop the top value and
// return it from a stack
T pop(int stackNum)
{
// If the stack is empty
// throw an error
if (empty(stackNum))
throw("Empty Stack!");
// Save the top value
T val = values[topIndex[stackNum] - 1];
// Set top value to default data type
values[--topIndex[stackNum]] = T();
// Shrink the reserved size if needed
Shrink(stackNum);
// Return the pop-ed value
return val;
}
// Return the top value form a stack
// Same as pop (but without removal)
T top(int stackNum)
{
if (empty(stackNum))
throw("Empty Stack!");
return values[topIndex[stackNum] - 1];
}
// Return the size of a stack
// (the number of elements in the stack,
// not the reserved size)
int size(int stackNum)
{
if (!stackNum)
return topIndex[0];
return topIndex[stackNum] - sizes[stackNum - 1];
}
// Check if a stack is empty or not
// (has no elements)
bool empty(int stackNum)
{
int offset;
if (!stackNum)
offset = 0;
else
offset = sizes[stackNum - 1];
int index = topIndex[stackNum];
return index == offset;
}
// Helper function to check
// if a stack size has reached
// the reserved size of that stack
bool isFull(int stackNum)
{
int offset = sizes[stackNum];
int index = topIndex[stackNum];
return index >= offset;
}
// Function to expand the reserved size
// of a stack (multiply by 2)
void Expand(int stackNum)
{
// Get the reserved_size of the stack()
int reserved_size = size(stackNum);
// Update the prefix sums (sizes)
// and the top index of the next stacks
for (int i = stackNum + 1; i < numberOfStacks; i++)
sizes[i] += reserved_size,
topIndex[i] += reserved_size;
// Update the size of the recent stack
sizes[stackNum] += reserved_size;
// Double the size of the stack by
// inserting 'reserved_size' elements
values.insert(values.begin() + topIndex[stackNum],
reserved_size, T());
}
// Function to shrink the reserved size
// of a stack (divide by2)
void Shrink(int stackNum)
{
// Get the reserved size and the current size
int reserved_size, current_size;
if (!stackNum)
reserved_size = sizes[0],
current_size = topIndex[0];
else
reserved_size
= sizes[stackNum] - sizes[stackNum - 1],
current_size
= topIndex[stackNum] - sizes[stackNum - 1];
// Shrink only if the size is
// lower than a quarter of the
// reserved size and avoid shrinking
// if the reserved size is 2
if (current_size * 4 > reserved_size
|| reserved_size == 2)
return;
// Divide the size by 2 and update
// the prefix sums (sizes) and
// the top index of the next stacks
int dif = reserved_size / 2;
for (int i = stackNum + 1; i < numberOfStacks; i++)
sizes[i] -= dif, topIndex[i] -= dif;
sizes[stackNum] -= dif;
// Erase half of the reserved size
values.erase(values.begin() + topIndex[stackNum],
values.begin() + topIndex[stackNum]
+ dif);
}
};
// Driver code
int main()
{
// create 3 stacks
MultiStack<int> MStack(3);
// push elements in stack 0:
MStack.push(0, 21);
MStack.push(0, 13);
MStack.push(0, 14);
// Push one element in stack 1:
MStack.push(1, 15);
// Push two elements in stack 2:
MStack.push(2, 1);
MStack.push(2, 2);
MStack.push(2, 3);
// Print the top elements of the stacks
cout << MStack.top(0) << '\n';
cout << MStack.top(1) << '\n';
cout << MStack.top(2) << '\n';
return 0;
}
Java
// Java implementation for the above approach
import java.util.*;
class MultiStack<T> {
private int numberOfStacks;
private ArrayList<T> values;
private ArrayList<Integer> sizes, topIndex;
// Constructor for MultiStack
// Takes the number of stacks (k) as input and initializes the MultiStack object
public MultiStack(int k) {
// Set the number of stacks
numberOfStacks = k;
// Initialize the values ArrayList with an initial capacity of k*2
values = new ArrayList<>(numberOfStacks << 1);
// Initialize the sizes ArrayList with a size of k
sizes = new ArrayList<>(numberOfStacks);
// Initialize the topIndex ArrayList with a size of k
topIndex = new ArrayList<>(numberOfStacks);
// Loop through k times
for (int size = 2, i = 0; i < numberOfStacks; i++, size += 2) {
// Add size to the sizes ArrayList
sizes.add(size);
// Add size-2 to the topIndex ArrayList
topIndex.add(size - 2);
}
}
// Push a value onto a specified stack
public void push(int stackNum, T val) {
// If the stack is full, expand it
if (isFull(stackNum))
Expand(stackNum);
// Add the value to the ArrayList at the index
// specified by the topIndex of the specified stack
values.add(topIndex.get(stackNum), val);
// Increment the topIndex of the specified stack
topIndex.set(stackNum, topIndex.get(stackNum) + 1);
}
// Pop a value off a specified stack
public T pop(int stackNum) {
// If the stack is empty, throw an exception
if (empty(stackNum))
throw new RuntimeException("Empty Stack!");
// Get the value at the top of the specified stack
T val = values.get(topIndex.get(stackNum) - 1);
// Set the value at the top of the specified stack to null
values.set(topIndex.get(stackNum) - 1, null);
// Decrement the topIndex of the specified stack
topIndex.set(stackNum, topIndex.get(stackNum) - 1);
// Shrink the specified stack if necessary
shrink(stackNum);
// Return the value at the top of the specified stack
return val;
}
// Returns the element at the top of the specified stack
public T top(int stackNum) {
if (empty(stackNum))
throw new RuntimeException("Empty Stack!");
return values.get(topIndex.get(stackNum) - 1);
}
// Returns the number of elements in the specified stack
public int size(int stackNum) {
if (stackNum == 0)
return topIndex.get(0);
return topIndex.get(stackNum) - sizes.get(stackNum - 1);
}
// Checks if the specified stack is empty
public boolean empty(int stackNum) {
int offset;
if (stackNum == 0)
offset = 0;
else
offset = sizes.get(stackNum - 1);
int index = topIndex.get(stackNum);
return index == offset;
}
// Checks if the specified stack is full
public boolean isFull(int stackNum) {
int offset = sizes.get(stackNum);
int index = topIndex.get(stackNum);
return index >= offset;
}
public void Expand(int stackNum) {
int reserved_size = size(stackNum);
for (int i = stackNum + 1; i < numberOfStacks; i++) {
sizes.set(i, sizes.get(i) + reserved_size);
topIndex.set(i, topIndex.get(i) + reserved_size);
}
sizes.set(stackNum, sizes.get(stackNum) + reserved_size);
for (int i = 0; i < reserved_size; i++)
values.add(topIndex.get(stackNum), null);
}
// Function to shrink the reserved size
// of a stack (divide by2)
void shrink(int stackNum) {
// Get the reserved size and the current size
int reserved_size, current_size;
if (stackNum == 0) {
reserved_size = sizes.get(0);
current_size = topIndex.get(0);
} else {
reserved_size = sizes.get(stackNum) - sizes.get(stackNum - 1);
current_size = topIndex.get(stackNum) - sizes.get(stackNum - 1);
}
// Shrink only if the size is
// lower than a quarter of the
// reserved size and avoid shrinking
// if the reserved size is 2
if (current_size * 4 > reserved_size || reserved_size == 2) {
return;
}
// Divide the size by 2 and update
// the prefix sums (sizes) and
// the top index of the next stacks
int dif = reserved_size / 2;
for (int i = stackNum + 1; i < numberOfStacks; i++) {
sizes.set(i, sizes.get(i) - dif);
topIndex.set(i, topIndex.get(i) - dif);
}
sizes.set(stackNum, sizes.get(stackNum) - dif);
// Erase half of the reserved size
values.subList(topIndex.get(stackNum), topIndex.get(stackNum) + dif).clear();
}
// Driver code
public static void main(String[] args) {
// create 3 stacks
MultiStack<Integer> MStack = new MultiStack<>(3);
// push elements in stack 0:
MStack.push(0, 21);
MStack.push(0, 13);
MStack.push(0, 14);
// Push one element in stack 1:
MStack.push(1, 15);
// Push two elements in stack 2:
MStack.push(2, 1);
MStack.push(2, 2);
MStack.push(2, 3);
// Print the top elements of the stacks
System.out.println(MStack.top(0));
System.out.println(MStack.top(1));
System.out.println(MStack.top(2));
}
};
// This code is contributed by amit_mangal_
Python3
# Python3 implementation for the above approach
class MultiStack:
def __init__(self, k=1):
# Initializes the MultiStack with k number of stacks.
self.number_of_stacks = k
# Initializes an array to hold values of all stacks.
self.values = [None] * (self.number_of_stacks * 2)
# Initializes an array to hold sizes of all stacks.
self.sizes = [2 + i*2 for i in range(self.number_of_stacks)]
# Initializes an array to hold the top index of each stack.
self.top_index = [size-2 for size in self.sizes]
def push(self, stack_num, val):
# Pushes a value onto the given stack_num.
# If the stack is full, expands the stack.
if self.is_full(stack_num):
self.expand(stack_num)
self.values[self.top_index[stack_num]] = val
self.top_index[stack_num] += 1
def pop(self, stack_num):
# Pops the top value off of the given stack_num.
# If the stack is empty, raises an exception.
if self.is_empty(stack_num):
raise Exception("Empty Stack!")
val = self.values[self.top_index[stack_num]-1]
self.values[self.top_index[stack_num]-1] = None
self.top_index[stack_num] -= 1
self.shrink(stack_num)
return val
def top(self, stack_num):
# Check if the stack is empty
if self.is_empty(stack_num):
raise Exception("Empty Stack!")
# Return the top element of the stack
return self.values[self.top_index[stack_num]-1]
def size(self, stack_num):
# If no stack_num specified, return the
# total number of elements in all stacks
if not stack_num:
return self.top_index[0]
# Calculate the number of elements in the specified stack
return self.top_index[stack_num] - self.sizes[stack_num-1]
def is_empty(self, stack_num):
# Calculate the index offset for the specified stack
if not stack_num:
offset = 0
else:
offset = self.sizes[stack_num-1]
# Check if the stack is empty
index = self.top_index[stack_num]
return index == offset
def is_full(self, stack_num):
# Calculate the index offset for the specified stack
offset = self.sizes[stack_num]
# Check if the stack is full
index = self.top_index[stack_num]
return index >= offset
# This method expands a stack by copying its elements
# to the next stack(s) and increasing its size
def expand(self, stack_num):
# Get the reserved size of the stack
reserved_size = self.size(stack_num)
# Increase the size and top index of all the
# stacks after the current stack
for i in range(stack_num+1, self.number_of_stacks):
self.sizes[i] += reserved_size
self.top_index[i] += reserved_size
# Increase the size of the current stack
self.sizes[stack_num] += reserved_size
# Insert reserved_size None values at the
# top of the current stack to expand it
self.values = (self.values[:self.top_index[stack_num]] +
[None] * reserved_size +
self.values[self.top_index[stack_num]:])
# This method shrinks a stack by reducing its size
# and copying its elements to the next stack(s)
def shrink(self, stack_num):
# Get the reserved size and current size of the stack
if not stack_num:
reserved_size, current_size = self.sizes[0], self.top_index[0]
else:
reserved_size = self.sizes[stack_num] - self.sizes[stack_num-1]
current_size = self.top_index[stack_num] - self.sizes[stack_num-1]
# Check if the stack should be shrunk
if current_size * 4 > reserved_size or reserved_size == 2:
return
# Calculate the difference to reduce the stack size
dif = reserved_size // 2
# Reduce the size and top index of all the stacks after the current stack
for i in range(stack_num+1, self.number_of_stacks):
self.sizes[i] -= dif
self.top_index[i] -= dif
# Reduce the size of the current stack
self.sizes[stack_num] -= dif
# Remove dif elements from the top of the current stack to shrink it
self.values = (self.values[:self.top_index[stack_num]-dif] +
self.values[self.top_index[stack_num]:])
# create 3 stacks
MStack = MultiStack(3)
# push elements in stack 0:
MStack.push(0, 21)
MStack.push(0, 13)
MStack.push(0, 14)
# Push one element in stack 1:
MStack.push(1, 15)
# Push two elements in stack 2:
MStack.push(2, 1)
MStack.push(2, 2)
MStack.push(2, 3)
# Print the top elements of the stacks
print(MStack.top(0))
print(MStack.top(1))
print(MStack.top(2))
# This code is contributed by amit_mangal_
C#
using System;
using System.Collections.Generic;
public class MultiStack<T> {
private int numberOfStacks; // Number of stacks in the
// MultiStack
private List<T> values; // List to store all the values
// of the stacks
private List<int>
sizes; // List to store the sizes of each stack
private List<int> topIndexes; // List to store the top
// indexes of each stack
// Constructor to create a MultiStack with 'k' stacks
// (default is 1)
public MultiStack(int k = 1)
{
numberOfStacks = k;
values = new List<T>();
sizes = new List<int>(new int[k]);
topIndexes = new List<int>(new int[k]);
}
// Push a value onto a specific stack
public void Push(int stackNum, T val)
{
if (stackNum < 0 || stackNum >= numberOfStacks) {
throw new ArgumentOutOfRangeException(
"Invalid stack number");
}
// Add the value to the main list
values.Add(val);
// Increase the size of the stack
sizes[stackNum]++;
// Update the top index for this stack
topIndexes[stackNum] = values.Count - 1;
}
// Pop a value from a specific stack
public T Pop(int stackNum)
{
if (stackNum < 0 || stackNum >= numberOfStacks) {
throw new ArgumentOutOfRangeException(
"Invalid stack number");
}
if (IsEmpty(stackNum)) {
throw new InvalidOperationException(
"Stack is empty");
}
// Get the index of the top element of the stack
int index = topIndexes[stackNum];
// Get the value at this index
T val = values[index];
// Remove the value from the main list
values.RemoveAt(index);
// Decrease the size of the stack
sizes[stackNum]--;
// Update top indexes for the remaining stacks
UpdateTopIndexes(stackNum, index);
// Return the popped value
return val;
}
// Get the top value of a specific stack
public T Top(int stackNum)
{
if (stackNum < 0 || stackNum >= numberOfStacks) {
throw new ArgumentOutOfRangeException(
"Invalid stack number");
}
if (IsEmpty(stackNum)) {
throw new InvalidOperationException(
"Stack is empty");
}
// Return the value at the top index of this stack
return values[topIndexes[stackNum]];
}
// Get the size of a specific stack
public int Size(int stackNum)
{
if (stackNum < 0 || stackNum >= numberOfStacks) {
throw new ArgumentOutOfRangeException(
"Invalid stack number");
}
// Return the size of this stack
return sizes[stackNum];
}
// Check if a specific stack is empty
public bool IsEmpty(int stackNum)
{
if (stackNum < 0 || stackNum >= numberOfStacks) {
throw new ArgumentOutOfRangeException(
"Invalid stack number");
}
// Check if the size of this stack is 0
return sizes[stackNum] == 0;
}
// Update the top indexes of stacks after a pop
// operation
private void UpdateTopIndexes(int stackNum,
int removedIndex)
{
for (int i = stackNum; i < numberOfStacks; i++) {
// Decrement the top index if it's greater than
// the removed index
if (topIndexes[i] > removedIndex) {
topIndexes[i]--;
}
}
}
}
class Program {
static void Main()
{
// Create an instance of MultiStack with 3 stacks
MultiStack<int> MStack = new MultiStack<int>(3);
// Push elements into different stacks
MStack.Push(0, 21);
MStack.Push(0, 13);
MStack.Push(0, 14);
MStack.Push(1, 15);
MStack.Push(2, 1);
MStack.Push(2, 2);
MStack.Push(2, 3);
// Print the tops of each stack
Console.WriteLine("Top of Stack 0: "
+ MStack.Top(0));
Console.WriteLine("Top of Stack 1: "
+ MStack.Top(1));
Console.WriteLine("Top of Stack 2: "
+ MStack.Top(2));
}
}
JavaScript
class MultiStack {
constructor(k = 1) {
this.numberOfStacks = k;
this.values = new Array(k * 2).fill(0);
this.sizes = new Array(k).fill(0);
this.topIndex = new Array(k).fill(0);
// Sizes is a prefix sum array
for (let size = 2, i = 0; i < this.numberOfStacks; i++, size += 2) {
this.sizes[i] = size;
this.topIndex[i] = size - 2;
}
}
push(stackNum, val) {
if (this.isFull(stackNum)) {
this.expand(stackNum);
}
this.values[this.topIndex[stackNum]++] = val;
}
pop(stackNum) {
if (this.empty(stackNum)) {
throw new Error("Empty Stack!");
}
const val = this.values[this.topIndex[stackNum] - 1];
this.values[--this.topIndex[stackNum]] = 0;
this.shrink(stackNum);
return val;
}
top(stackNum) {
if (this.empty(stackNum)) {
throw new Error("Empty Stack!");
}
return this.values[this.topIndex[stackNum] - 1];
}
size(stackNum) {
if (!stackNum) {
return this.topIndex[0];
}
return this.topIndex[stackNum] - this.sizes[stackNum - 1];
}
empty(stackNum) {
const offset = !stackNum ? 0 : this.sizes[stackNum - 1];
const index = this.topIndex[stackNum];
return index === offset;
}
isFull(stackNum) {
const offset = this.sizes[stackNum];
const index = this.topIndex[stackNum];
return index >= offset;
}
expand(stackNum) {
const reservedSize = this.size(stackNum);
for (let i = stackNum + 1; i < this.numberOfStacks; i++) {
this.sizes[i] += reservedSize;
this.topIndex[i] += reservedSize;
}
this.sizes[stackNum] += reservedSize;
const reservedArray = new Array(reservedSize).fill(0);
this.values.splice(this.topIndex[stackNum], 0, ...reservedArray);
}
shrink(stackNum) {
let reservedSize, currentSize;
if (!stackNum) {
reservedSize = this.sizes[0];
currentSize = this.topIndex[0];
} else {
reservedSize = this.sizes[stackNum] - this.sizes[stackNum - 1];
currentSize = this.topIndex[stackNum] - this.sizes[stackNum - 1];
}
if (currentSize * 4 > reservedSize || reservedSize === 2) {
return;
}
const difference = reservedSize / 2;
for (let i = stackNum + 1; i < this.numberOfStacks; i++) {
this.sizes[i] -= difference;
this.topIndex[i] -= difference;
}
this.sizes[stackNum] -= difference;
this.values.splice(
this.topIndex[stackNum],
difference,
);
}
}
// Driver code
const MStack = new MultiStack(3);
MStack.push(0, 21);
MStack.push(0, 13);
MStack.push(0, 14);
MStack.push(1, 15);
MStack.push(2, 1);
MStack.push(2, 2);
MStack.push(2, 3);
console.log(MStack.top(0));
console.log(MStack.top(1));
console.log(MStack.top(2));
Time complexities:
- O(1) for top() function.
- Amortized O(1) for push() and pop() functions.
Auxiliary Space: O(N) where N is the number of stacks
Similar Reads
Implement Stack Data Structure using ReactJS
To implement Stack Data Structure using React JS and display it on the application. we will use the npm rooks package to create stack and its functionalities. PrerequisitesReact JSReact JS hooksApproach to Implement Stack in ReactJsTo implement the stack we will use a custom hook, i.e. the useStackS
2 min read
How to implement a Stack using list in C++ STL
In this article, we will discuss how to implement a Stack using list in C++ STL. Stack is a linear data structure which follows. LIFO(Last In First Out) or FILO(First In Last Out). It mainly supports 4 major operations:1. Push: Push an element into the stack.2. Pop: Removes the element by following
3 min read
Basic Operations in Stack Data Structure with Implementations
In order to make manipulations in a stack, there are certain operations provided to us for Stack, which include:push() to insert an element into the stackpop() to remove an element from the stacktop() Returns the top element of the stack.isEmpty() returns true if the stack is empty else false.size()
13 min read
Design and Implement Special Stack Data Structure
Design a Data Structure SpecialStack that supports all the stack operations like push(), pop(), isEmpty(), isFull() and an additional operation getMin() which should return minimum element from the SpecialStack. All these operations of SpecialStack must be O(1). To implement SpecialStack, you should
1 min read
Implement a stack using singly linked list
To implement a stack using a singly linked list, we need to ensure that all operations follow the LIFO (Last In, First Out) principle. This means that the most recently added element is always the first one to be removed. In this approach, we use a singly linked list, where each node contains data a
13 min read
Implementing Stack Using Class Templates in C++
The task is to implement some important functions of stack like pop(), push(), display(), topElement(), isEmpty(), isFull() using class template in C++. Stack is a linear data structure that follows a particular order in which the operations are performed. The order may be LIFO(Last In First Out) or
5 min read
Implementation of stack using Doubly Linked List
Stack and doubly linked lists are two important data structures with their own benefits. Stack is a data structure that follows the LIFO (Last In First Out) order and can be implemented using arrays or linked list data structures. Doubly linked list has the advantage that it can also traverse the pr
14 min read
Introduction to Monotonic Stack - Data Structure and Algorithm Tutorials
A monotonic stack is a special data structure used in algorithmic problem-solving. Monotonic Stack maintaining elements in either increasing or decreasing order. It is commonly used to efficiently solve problems such as finding the next greater or smaller element in an array etc.Monotonic StackTable
12 min read
Implement a stack using single queue
We are given a queue data structure, the task is to implement a stack using a single queue.Also Read: Stack using two queuesThe idea is to keep the newly inserted element always at the front of the queue, preserving the order of previous elements by appending the new element at the back and rotating
5 min read
Top 50 Problems on Stack Data Structure asked in SDE Interviews
A Stack is a linear data structure in which the insertion of a new element and removal of an existing element takes place at the same end, represented as the top of the stack. To learn about Stack Data Structure in detail, please refer to the Tutorial on Stack Data Structure.Easy ProblemsParenthesis
2 min read