Open In App

Implement Dynamic Multi Stack (K stacks) using only one Data Structure

Last Updated : 30 Oct, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

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));


Output

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



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads