16

I came across this problem in an interview website. The problem asks for efficiently implement three stacks in a single array, such that no stack overflows until there is no space left in the entire array space.

For implementing 2 stacks in an array, it's pretty obvious: 1st stack grows from LEFT to RIGHT, and 2nd stack grows from RIGHT to LEFT; and when the stackTopIndex crosses, it signals an overflow.

Thanks in advance for your insightful answer.

4
  • 1
    Ah, this is a very well studied problem of the '70ies (or maybe earlier than that). Trying to recall where I first saw this. Knuth? Sedgewick? Standish? Hmm... I think Knuth in particular mentioned a trick/heuristic to favor the faster growing stack (of N stacks, 3 in your case), but can't readily remember :) Commented Jun 19, 2010 at 23:51
  • Ah, found it, adding it as an answer below. Commented Jun 20, 2010 at 0:28
  • whats the application of doing 3 stacks in single array? real need? Commented Jun 5, 2013 at 9:04
  • 1
    @Dineshkumar Locality of reference. If we take three separate stacks, their memory will be allocated at different places, so they might not be in physical memory (RAM) at the same time. And, we might have page miss.. and will need to bring the new stack from disk to RAM. Whereas, in the case of 3 stack as one array implementation, most likely, all stacks will be on a single page, and all the stacks will be in RAM, even if only one stack is more frequently used, and others are used less often. Commented Apr 6, 2014 at 14:42

17 Answers 17

16

You can implement three stacks with a linked list:

  • You need a pointer pointing to the next free element. Three more pointers return the last element of each stack (or null, if the stack is empty).
  • When a stack gets another element added, it has to use the first free element and set the free pointer to the next free element (or an overflow error will be raised). Its own pointer has to point to the new element, from there back to the next element in the stack.
  • When a stack gets an element removed it will hand it back into the list of free elements. The own pointer of the stack will be redirected to the next element in the stack.

A linked list can be implemented within an array.

How (space) efficent is this?
It is no problem to build a linked list by using two cells of an array for each list element (value + pointer). Depending on the specification you could even get pointer and value into one array element (e.g. the array is long, value and pointer are only int).
Compare this to the solution of kgiannakakis ... where you lose up to 50% (only in the worst case). But I think that my solution is a bit cleaner (and maybe more academic, which should be no disadvantage for an interview question ^^).

Sign up to request clarification or add additional context in comments.

8 Comments

You can point stacks to "null"-indexes and have pointer to first free element in sequence of chained free elements. Each time you push to stack you get that element from sequence of free elements and changes next pointer of it to old stack-top. When element is popped out of stack it returns to head of free sequence. And kgiannakakis wastes up to 50% and your variant spends 50% always for pointer.
The question doesn't say what type the array is or the values you need to store. If you assumed your stack has to store 32-bit numbers and you create an array of 64-bit numbers you could easily pack the linked-list pointers into the upper/lower bits of each array value.
@Paolo: yes it depends on the specification - you always need some space for your pointers. But my point is that a doubly-linked list is basically an adequate data structure for this problem. You you use it the implementation isn't difficult anymore.
@tanascius Why the "double" links? A stack is always traversed in the same direction ...
@belisarius: You are right. The idea is to use a 4th pointer for a list of free elements. I updated my answer ... ^^ thx
|
10

See Knuth, The Art of Computer Programming, Volume 1, Section 2.2.2. titled "Sequential allocation". Discusses allocating multiple queues/stacks in a single array, with algorithms dealing with overflows, etc.

6 Comments

Heh, whoever downvoted Knuth's reference, don't be ashamed, reveal yourself :)
By the way, the best answers given are already subsumed in Knuth's much more thorough treatment of this problem. Just sayin'.
Perhaps that person was not downvoting Knuth, but an answer that is basically useless if you do not already have the book at home (in which case you would not be interested in the question in the first place, I guess).
How about libraries. I can't remember the last time when I lived at a place without a library having Knuth in it.
Hi, do you mind posting the part talking about that ? At least the idea of it
|
3

We can use long bit array representing to which stack the i-th array cell belongs to. We can take values by modulo 3 (00 - empty, 01 - A, 10 - B, 11 - C). It would take N/2 bits or N/4 bytes of additional memory for N sized array.

For example for 1024 long int elements (4096 bytes) it would take only 256 bytes or 6%.

This bit array map can be placed in the same array at the beginning or at the end, just shrinking the size of the given array by constant 6%!

3 Comments

I really like this idea; I think it's the most optimal use of memory-space. In terms of speed, the drawback is that a push() or pop() operation on any stack is no longer O(1), but can be O(N) in the worst case. Still, very nice!
@Ciaran, I'm pretty sure that for stack of depth N it'll take N log₃ / log₂ ≈ N ⋅ 1.585 additional bits. I.e. for elements with size 1 bit this bitmap will have overhead +158%, for elements with range 0..2 it will have overhead +100%, for byte long +20%. To get no more than +6% the size of element should be at least 27 bits or range ~ 0 .. 89 540 788.
@Vitamon, how this different from stackoverflow.com/a/3075233/230744 ? (except of strange calculations)
2

First stack grows from left to right.

Second stack grows from right to left.

Third stack starts from the middle. Suppose odd sized array for simplicity. Then third stack grows like this:

* * * * * * * * * * *
      5 3 1 2 4

First and second stacks are allowed to grow maximum at the half size of array. The third stack can grow to fill in the whole array at a maximum.

Worst case scenario is when one of the first two arrays grows at 50% of the array. Then there is a 50% waste of the array. To optimise the efficiency the third array must be selected to be the one that grows quicker than the other two.

4 Comments

But that does not fit the requirements. Put in one element for the 3rd stack, then only elements for the 1st stack ... how will your solution handle that?
But suppose 1st stack has 1 entry, 2nd stack has 4 entries. Where do you put 3rd stack's 4th entry?
Both of you are right. My solution can waste up to 50%. I will be interested to see if anyone can offer a better solution.
I wanted to mention this approach in my initial post. But as the author pointed that it could waste 50% space in the worst case.
2

This is an interesting conundrum, and I don't have a real answer but thinking slightly outside the box...

it could depend on what each element in the stack consists of. If it's three stacks of true/false flags, then you could use the first three bits of integer elements. Ie. bit 0 is the value for the first stack, bit 1 is the value for the second stack, bit 2 is the value for the third stack. Then each stack can grow independently until the whole array is full for that stack. This is even better as the other stacks can also continue to grow even when the first stack is full.

I know this is cheating a bit and doesn't really answer the question but it does work for a very specific case and no entries in the stack are wasted. I am watching with interest to see whether anyone can come up with a proper answer that works for more generic elements.

2 Comments

You'll have waste of bit-sized elements instead of waste of any-sized elements. That's variant of splitting array in 3 parts but in this case with using interleaving.
True and well spotted, so back to the think-tank. As Damien said, it depends on whether all array positions should be used to store values. If so, then the doubly-linked-list method (which is probably the correct answer, from an interview POV) can't be used. In this case kgiannakakis answer is probably ok, but obviously wastes up to 50% of the space. We're still waiting for a definitive answer that uses every element for a value and doesn't waste any space. Damien's does, but would be difficult to maintain order of the stack when moving from one end of the middle stack to the other.
2

Split array in any 3 parts (no matter if you'll split it sequentially or interleaved). If one stack grows greater than 1/3 of array you start filling ends of rest two stacks from the end.

aaa bbb ccc
1   2   3
145 2   3
145 2 6 3
145 2 6 3 7
145 286 3 7
145 286 397

The worse case is when two stacks grows up to 1/3 boundary and then you have 30% of space waste.

2 Comments

I couldn't grasp your idea completely. Did you mean, when first stack (marked with 'aaa') is filled up, say from LEFT to RIGHT, you'll insert elements in second stack space (marked with 'bbb') from RIGHT to LEFT. Similarly for second stack, you'll use third stack's space (marked with 'ccc'); and for third stack you'll use first stack's space. I believe it works with the penalty of 1/3rd space wastage.
When "aaa" is filled completely from LEFT to RIGHT it starts filling "bbb" and "ccc" simultaneously (odd element goes to one stack and even to other) from RIGHT to LEFT until it will met one of their top. I.e. length of stack for "aaa" is (n + (n- max (top("bbb"), top("ccc"))). When you hit problem with adding another element to stack "aaa" that means that array for "bbb" or for "ccc" is filled completely. So if all stacks grows with the same speed or one stack grows with speed 2x or two with 0x than no space is wasted. If there is one stack 2x and other 0x - you'll get (1/3)/2 space wasted.
1

Assuming that all array positions should be used to store values - I guess it depends on your definition of efficiency.

If you do the two stack solution, place the third stack somewhere in the middle, and track both its bottom and top, then most operations will continue to be efficient, at a penalty of an expensive Move operation (of the third stack towards wherever free space remains, moving to the half way point of free space) whenever a collision occurs.

It's certainly going to be quick to code and understand. What are our efficiency targets?

Comments

1

A rather silly but effective solution could be:

  • Store the first stack elements at i*3 positions: 0,3,6,...
  • Store the second stack elements at i*3+1 positions: 1,4,7...
  • And third stack elements at i*3+2 positions.

The problem with this solution is that the used memory will be always three times the size of the deepest stack and that you can overflow even when there are available positions at the array.

Comments

1
  1. Make a HashMap with keys to the begin and end positions e.g. < "B1" , 0 >, <"E1" , n/3 >

  2. for each Push(value) add a condition to check if position of Bx is previous to Ex or there is some other "By" in between. -- lets call it condition (2)

  3. with above condition in mind, if above (2) is true // if B1 and E1 are in order { if ( S1.Push()), then E1 ++ ; else // condition of overflow , { start pushing at end of E2 or E3 (whichever has a space) and update E1 to be E2-- or E3-- ; } }

    if above (2) is false { if ( S1.Push()), then E1 -- ; else // condition of overflow , { start pushing at end of E2 or E3 (whichever has a space) and update E1 to be E2-- or E3-- ; } }

Comments

1

Assume you only has integer index. if it's treated using FILO (First In Last Out) and not referencing individual, and only using an array as data. Using it's 6 space as stack reference should help:

[head-1, last-1, head-2, last-2, head-3, last-3, data, data, ... ,data]

you can simply using 4 space, because head-1 = 0 and last-3 = array length. If using FIFO (First In First Out) you need to re-indexing.

nb: I’m working on improving my English.

Comments

0

first stack grows at 3n, second stack grows at 3n+1, third grows at 3n+2

for n={0...N}

3 Comments

You divide the array just in three parts ... what happens when only the 1st stack grows all the time?
Doesn't fit requirements. Once the first stack has 1/3 as many entries as the array length, it overflows regardless of whether there's space in the array allotted for stacks 2 and 3.
It might waste 2/3rd space in worst case.
0

Yet another approach (as additional to linked-list) is to use map of stacks. In that case you'll have to use additional log(3^n)/log(2) bits for building map of data distribution in your n-length array. Each of 3-value part of map says which stack is owns next element. Ex. a.push(1); b.push(2); c.push(3); a.push(4); a.push(5); will give you image

aacba
54321

appropriate value of map is calculated while elements is pushed onto stack (with shifting contents of array)

map0 = any
map1 = map0*3 + 0
map2 = map1*3 + 1
map3 = map2*3 + 2
map4 = map3*3 + 0
map5 = map4*3 + 0 = any*3^5 + 45

and length of stacks 3,1,1
Once you'll want to do c.pop() you'll have to reorganize your elements by finding actual position of c.top() in original array through walking in cell-map (i.e. divide by 3 while mod by 3 isn't 2) and then shift all contents in array back to cover that hole. While walking through cell-map you'll have to store all position you have passed (mapX) and after passing that one which points to stack "c" you'll have to divide by 3 yet another time and multiply it by 3^(amount positions passed-1) and add mapX to get new value of cells-map.
Overhead for that fixed and depends on size of stack element (bits_per_value):
(log(3n)/log(2)) / (nlog(bits_per_value)/log(2)) = log(3n) / (nlog(bits_per_value)) = log(3) / log(bits_per_value)
So for bits_per_value = 32 it will be 31.7% space overhead and with growing bits_per_value it will decay (i.e. for 64 bits it will be 26.4%).

Comments

0

In this approach, any stack can grow as long as there is any free space in the array. We sequentially allocate space to the stacks and we link new blocks to the previous block. This means any new element in a stack keeps a pointer to the previous top element of that particular stack.

int stackSize = 300;
int indexUsed = 0;
int[] stackPointer = {-1,-1,-1};
StackNode[] buffer = new StackNode[stackSize * 3];

void push(int stackNum, int value) {
    int lastIndex = stackPointer[stackNum];
    stackPointer[stackNum] = indexUsed;
    indexUsed++;
    buffer[stackPointer[stackNum]]=new StackNode(lastIndex,value);
}

int pop(int stackNum) {
    int value = buffer[stackPointer[stackNum]].value;
    int lastIndex = stackPointer[stackNum];
    stackPointer[stackNum] = buffer[stackPointer[stackNum]].previous;
    buffer[lastIndex] = null;
    indexUsed--;
    return value;
}

int peek(int stack) { return buffer[stackPointer[stack]].value; }

boolean isEmpty(int stackNum) { return stackPointer[stackNum] == -1; }

class StackNode {
    public int previous;
    public int value;

    public StackNode(int p, int v){
        value = v;
        previous = p;
    }
}

Comments

0

This code implements 3 stacks in single array. It takes care of empty spaces and fills the empty spaces in between the data.

#include <stdio.h>

struct stacknode {
   int value;
   int prev;
};

struct stacknode stacklist[50];
int top[3] = {-1, -1, -1};
int freelist[50];
int stackindex=0;
int freeindex=-1;

void push(int stackno, int value) {
   int index;
   if(freeindex >= 0) {
     index = freelist[freeindex];
     freeindex--;
   } else {
     index = stackindex;
     stackindex++;
   }
   stacklist[index].value = value;
   if(top[stackno-1] != -1) {
     stacklist[index].prev = top[stackno-1];
   } else {
     stacklist[index].prev = -1;
   }
   top[stackno-1] = index;
   printf("%d is pushed in stack %d at %d\n", value, stackno, index);
}

int pop(int stackno) {
   int index, value;
   if(top[stackno-1] == -1) {
     printf("No elements in the stack %d\n", value, stackno);
     return -1;
   }
   index = top[stackno-1];
   freeindex++;
   freelist[freeindex] = index;
   value = stacklist[index].value;
   top[stackno-1] = stacklist[index].prev;
   printf("%d is popped put from stack %d at %d\n", value, stackno, index);
   return value;
}

int main() {
   push(1,1);
   push(1,2);
   push(3,3);
   push(2,4);
   pop(3);
   pop(3);
   push(3,3);
   push(2,3);
}

Comments

0

Another solution in PYTHON, please let me know if that works as what you think.

    class Stack(object):

    def __init__(self):
        self.stack = list()

        self.first_length = 0
        self.second_length = 0
        self.third_length = 0

        self.first_pointer = 0
        self.second_pointer = 1

    def push(self, stack_num, item):

        if stack_num == 1:
            self.first_pointer += 1
            self.second_pointer += 1
            self.first_length += 1
            self.stack.insert(0, item)

        elif stack_num == 2:
            self.second_length += 1
            self.second_pointer += 1
            self.stack.insert(self.first_pointer, item)
        elif stack_num == 3:
            self.third_length += 1
            self.stack.insert(self.second_pointer - 1, item)
        else:
            raise Exception('Push failed, stack number %d is not allowd' % stack_num)

    def pop(self, stack_num):
        if stack_num == 1:
            if self.first_length == 0:
                raise Exception('No more element in first stack')
            self.first_pointer -= 1
            self.first_length -= 1
            self.second_pointer -= 1
            return self.stack.pop(0)
        elif stack_num == 2:
            if self.second_length == 0:
                raise Exception('No more element in second stack')
            self.second_length -= 1
            self.second_pointer -= 1
            return self.stack.pop(self.first_pointer)
        elif stack_num == 3:
            if self.third_length == 0:
                raise Exception('No more element in third stack')
            self.third_length -= 1
            return self.stack.pop(self.second_pointer - 1)

    def peek(self, stack_num):
        if stack_num == 1:
            return self.stack[0]
        elif stack_num == 2:
            return self.stack[self.first_pointer]
        elif stack_num == 3:
            return self.stack[self.second_pointer]
        else:
            raise Exception('Peek failed, stack number %d is not allowd' % stack_num)

    def size(self):
        return len(self.items)

s = Stack()

# push item into stack 1
s.push(1, '1st_stack_1')
s.push(1, '2nd_stack_1')
s.push(1, '3rd_stack_1')
#
## push item into stack 2
s.push(2, 'first_stack_2')
s.push(2, 'second_stack_2')
s.push(2, 'third_stack_2')
#
## push item into stack 3
s.push(3, 'FIRST_stack_3')
s.push(3, 'SECOND_stack_3')
s.push(3, 'THIRD_stack_3')
#
print 'Before pop out: '
for i, elm in enumerate(s.stack):
    print '\t\t%d)' % i, elm

#
s.pop(1)
s.pop(1)
#s.pop(1)
s.pop(2)
s.pop(2)
#s.pop(2)
#s.pop(3)
s.pop(3)
s.pop(3)
#s.pop(3)
#
print 'After pop out: '
#
for i, elm in enumerate(s.stack):
    print '\t\t%d)' % i, elm

Comments

0

May be this can help you a bit...i wrote it by myself :)

// by ashakiran bhatter
// compile: g++ -std=c++11 test.cpp
// run    : ./a.out
// sample output as below
// adding:  1 2 3 4 5 6 7 8 9
// array contents:  9 8 7 6 5 4 3 2 1
// popping now...
// array contents:  8 7 6 5 4 3 2 1


#include <iostream>
#include <cstdint>

#define MAX_LEN 9
#define LOWER   0
#define UPPER   1
#define FULL   -1
#define NOT_SET -1

class CStack
{
private:
     int8_t array[MAX_LEN];
     int8_t stack1_range[2];
     int8_t stack2_range[2];
     int8_t stack3_range[2];
     int8_t stack1_size;
     int8_t stack2_size;
     int8_t stack3_size;
     int8_t stack1_cursize;
     int8_t stack2_cursize;
     int8_t stack3_cursize;
     int8_t stack1_curpos;
     int8_t stack2_curpos;
     int8_t stack3_curpos;
public:
     CStack();
    ~CStack();

     void push(int8_t data);
     void pop();
     void print();
};

CStack::CStack()
{
     stack1_range[LOWER] = 0;
     stack1_range[UPPER] = MAX_LEN/3 - 1;
     stack2_range[LOWER] = MAX_LEN/3;
     stack2_range[UPPER] = (2 * (MAX_LEN/3)) - 1;
     stack3_range[LOWER] = 2 * (MAX_LEN/3);
     stack3_range[UPPER] = MAX_LEN - 1;

     stack1_size = stack1_range[UPPER] - stack1_range[LOWER];
     stack2_size = stack2_range[UPPER] - stack2_range[LOWER];
     stack3_size = stack3_range[UPPER] - stack3_range[LOWER];

     stack1_cursize = stack1_size;
     stack2_cursize = stack2_size;
     stack3_cursize = stack3_size;

     stack1_curpos = stack1_cursize;
     stack2_curpos = stack2_cursize;
     stack3_curpos = stack3_cursize;
}

CStack::~CStack()
{
}

void CStack::push(int8_t data)
{
     if(stack3_cursize != FULL) 
     {
          array[stack3_range[LOWER] + stack3_curpos--] = data;
          stack3_cursize--;
     } else if(stack2_cursize != FULL) {
          array[stack2_range[LOWER] + stack2_curpos--] = data;
          stack2_cursize--;
     } else if(stack1_cursize != FULL) {   
          array[stack1_range[LOWER] + stack1_curpos--] = data;
          stack1_cursize--;
     } else {
          std::cout<<"\tstack is full...!"<<std::endl;
     }
}

void CStack::pop()
{
     std::cout<<"popping now..."<<std::endl;
     if(stack1_cursize < stack1_size)
     {
          array[stack1_range[LOWER] + ++stack1_curpos] = 0; 
          stack1_cursize++;
     } else if(stack2_cursize < stack2_size) {
          array[stack2_range[LOWER] + ++stack2_curpos] = 0;
          stack2_cursize++;
     } else if(stack3_cursize < stack3_size) {
          array[stack3_range[LOWER] + ++stack3_curpos] = 0;
          stack3_cursize++;
     } else {
          std::cout<<"\tstack is empty...!"<<std::endl;
     }
}

void CStack::print()
{
     std::cout<<"array contents: ";
     for(int8_t i = stack1_range[LOWER] + stack1_curpos + 1; i <=  stack1_range[UPPER]; i++)
          std::cout<<" "<<static_cast<int>(array[i]);
     for(int8_t i = stack2_range[LOWER] + stack2_curpos + 1; i <=  stack2_range[UPPER]; i++)
          std::cout<<" "<<static_cast<int>(array[i]);
     for(int8_t i = stack3_range[LOWER] + stack3_curpos + 1; i <=  stack3_range[UPPER]; i++)
          std::cout<<" "<<static_cast<int>(array[i]);
     std::cout<<"\n";    
}

int main()
{
     CStack stack;

     std::cout<<"adding: ";
     for(uint8_t i = 1; i < 10; i++) 
     {
          std::cout<<" "<<static_cast<int>(i);
          stack.push(i);
     }
     std::cout<<"\n";

     stack.print();

     stack.pop();

     stack.print();


     return 0;
}

Comments

0

Perhaps you can implement N number of stacks or queues in the single array. My defination of using single array is that we are using single array to store all the data of all the stacks and queues in the single array, anyhow we can use other N array to keep track of indices of all elements of particular stack or queue.

solution : store data sequentially to in the array during the time of insertion in any of the stack or queue. and store it's respective index to the index keeping array of that particular stack or queue.

for eg : you have 3 stacks (s1, s2, s3) and you want to implement this using a single array (dataArray[]). Hence we will make 3 other arrays (a1[], a2[], a3[]) for s1, s2 and s3 respectively which will keep track of all of their elements in dataArray[] by saving their respective index.

insert(s1, 10) at dataArray[0] a1[0] = 0;
insert(s2, 20) at dataArray[1] a2[0] = 1;
insert(s3, 30) at dataArray[2] a3[0] = 2;
insert(s1, 40) at dataArray[3] a1[1] = 3;
insert(s3, 50) at dataArray[4] a3[1] = 4;
insert(s3, 60) at dataArray[5] a3[2] = 5;
insert(s2, 30) at dataArray[6] a2[1] = 6;

and so on ...

now we will perform operation in dataArray[] by using a1, a2 and a3 for respective stacks and queues.

to pop an element from s1 return a1[0] shift all elements to left

do similar approach for other operations too and you can implement any number of stacks and queues in the single array.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.