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 multistackclass 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 codeint 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 approachclass 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 stacksMStack = 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 stacksprint(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!
