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));     } } |
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
Ready to dive in? Explore our Free Demo Content and join our DSA course, trusted by over 100,000 neveropen!