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