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
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
15 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. Table of Content Wh
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 queues The 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 rotati
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 ProblemsParenthesi
2 min read
Design a dynamic stack using arrays that supports getMin() in O(1) time and O(1) extra space
Design a special dynamic Stack using an array that supports all the stack operations such as push(), pop(), peek(), isEmpty(), and getMin() operations in constant Time and Space complexities. Examples: Assuming the right to left orientation as the top to bottom orientation and performing the operati
15+ min read