Implement Dynamic Multi Stack (K stacks) using only one Data Structure
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 form 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; } |
14 15 3
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