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 :
#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 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 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_
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));
}
}
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