5

How to implement K stacks in an array, with best storage usage (stacks should be dynamic)?

Kara
  • 6,115
  • 16
  • 50
  • 57
  • 2
    Is K known in advance, or does it change dynamically as well? – zmbq Nov 19 '11 at 08:44
  • Have a look at http://stackoverflow.com/questions/4770627/how-to-implement-3-stacks-with-one-array and augment for K – Ahmed Masud Nov 19 '11 at 08:48
  • @zmbq : K is a constant , we should implement K stacks (e.g 10) ,and the stacks should be dynamic , meaning , if the size of one of them increases to be at the maximum size of the array , others should remain zero and the whole storage of the array should be dedicated to that large stack :) –  Nov 19 '11 at 08:49
  • Solution:https://massivealgorithms.blogspot.com/2014/09/how-to-efficiently-implement-k-stacks.html – Yug Singh Jun 14 '20 at 07:59

4 Answers4

0

I would use a queue containing all free slots and update that queue when adding or popping data. That way, the space complexity is O(N) where N is the size of the array. The pop and add operations are O(1).

For example: you have an array of size S and K stacks in it. You have a queue which contains all free slots, from 0 to S-1. The first value you add will be in the first free slot (index 0). Then you pop index 0 from the queue. It does not matter to which stack you add or pop data. If you pop a value, you enqueue the index of the slot from which you removed the node, and set the pointers or indexes accordingly.

Here is an implementation in C++. I have used indexes to point to the next nodes (after all, we are using array), but you could use pointers or iterators, it doesn't matter.

// TYPE: type of the stacks, SIZE: size of the array, STACKS: number of stacks
template <typename TYPE, size_t SIZE, size_t STACKS>
class KStacksInOneArray
{
// A node holds the data and a pointer or index to the next value
private:
    struct Node
    {
        TYPE data;
        int next = -1; // -1 is equivalent to nullptr:
                       // there is no next element
    };

public:
    KStacksInOneArray()
    {
        // initialize the free slots from 0 to SIZE - 1
        for (size_t idx = 0; idx < SIZE; ++idx)
            _freeSlots.push(idx);

        // initialize the heads, all to -1
        std::fill(_heads.begin(), _heads.end(), -1);
    }

    void pop(size_t stack)
    {
        // don't trust the user
        if (stack >= STACKS) throw std::out_of_range("there are only " + std::to_string(STACKS) + " stacks");
        if (isEmpty(stack)) throw std::out_of_range("cannot pop from an empty stack");

        // before destroying the node, get the new head  
        auto newHead = _arr[_heads[stack]].next;
        _arr[_heads[stack]] = Node{};

        // push the free slot on the queue and adjust the head
        _freeSlots.push(_heads[stack]);
        _heads[stack] = newHead;
    }

    const TYPE& top(size_t stack) const
    {
        if (stack >= STACKS) throw std::out_of_range("there are only 3 stacks");

        return _arr[_heads[stack]];
    }

    void add(size_t stack, TYPE data)
    {
        if (stack >= STACKS) throw std::out_of_range("there are only " + std::to_string(STACKS) + " stacks");
        if (_freeSlots.empty()) throw std::bad_alloc();

        // set the new node in the first free slot and 
        _arr[_freeSlots.front()] = {std::move(data), _heads[stack] != -1 ? _heads[stack] : -1};

        // update the head and remove the free slot from the queue
        _heads[stack] = _freeSlots.front();
        _freeSlots.pop();
    }

    bool isEmpty(size_t stack) const
    {
        if (stack >= STACKS) throw std::out_of_range("there are only " + std::to_string(STACKS) + " stacks");

        if (_heads[stack] == -1) return true;
        return false;
    }

private:
    std::vector<Node> _arr = std::vector<Node>(SIZE);
    std::array<int, STACKS> _heads;
    std::queue<int> _freeSlots;
};
mfnx
  • 2,894
  • 1
  • 12
  • 28
0

Answer 1: store the K stack pointers at the start. now mark the first address after that as address 0 (makes life simpler) an even K stack (stack_0, stack_2, ...) should grow upward; an odd K stack (stack_1, ..) should grow downward when initializing segment the array into K/2 parts (assuming K is even for simplicity). stack0 starts at address 0 stack1 starts at (arraySize / (k/2)) and grows downward stack3 starts at (arraySize / (k/2)) and grows upward

when pushing data into a certain stack we should make sure that it is not overflowing to the adjacent stack, otherwise throw an exception.

the array should look like this: [[stack pointers][stack_0][stack_1]...[stack_k]] where stack[0] and stack[1] both share the same region so they can make an optimal use of the space available to them.

there could be further optimizations done by pairing each large stack with a small stack (this could be done by checking the behavior of the stacks over time). Also, grouping together rapidly changing arrays with slow changing arrays may help.

Answer 2: thinking some more on this, I saw that my 1st solution only guarantees using array_size/(k/2) (since if we only have one array of size array_size/(k/2), we will get a stack overflow). the following (completely impractical) solution can satisfy the requirements: we allocate the start of the array for our k stack pointers, and ignore this region from now on. In the rest of the array we look at each cell as a struct [data, previous, next].

push(stack_i, data) -> get sp_i from the stack pointers area. then go to that address, fill in the "next" pointer to point to the next empty cell in the array (we could have all the empty spaces linked together in another stack so this is o(1)). in the "next" cell store our data, and fill in the "prev" pointer. update sp_i

pop(stack_i) -> get sp_i. get the "data" from that cell. "prev" from that cell is our new sp_i. push the old (now empty) cell to the empty list.

OSH
  • 2,847
  • 3
  • 25
  • 46
  • This won't be very efficient. If you have things in Stack 8 and Stack 9, with 10 free elements between them, you won't be able to use all the other elements (including those 10) in stack5, for instance. – zmbq Nov 19 '11 at 09:01
0

Well, if you're only worried about space usage, and don't care that stack operations can take O(N), you can use the array's first few cells to manage the stacks:

Array[0] - the end of stack 0

Array[1] - the end of stack 1

...

Array[K-1] = the end of stack K

Stack n starts at Array[n-1] and ends at Array[n] (exclusive - [Array[n-1], Array[n]) ). If Array[n-1]==Array[n] the stack is empty. The first stack starts at K, so at first Array[0]..Array[K-1] = K

When you push into a stack, just move all the elements in the stacks below it, and adjust the pointers respectively.

It'll get you the memory constraint you need.

timo.rieber
  • 3,727
  • 3
  • 32
  • 47
zmbq
  • 38,013
  • 14
  • 101
  • 171
  • this is not an optimal usage of memory since if one stack is larger than array_size / k you will get a stack overflow, even if that is the only stack that is used --> so basically you used just array_size/k cells out of array_size available. – OSH Nov 19 '11 at 09:02
  • No you won't, you just move the other stacks around in memory. This scheme will let you use every array cell (but the first K), but will take O(N) time for stack operations (worst case). – zmbq Nov 19 '11 at 09:04
0

Oooh, ooh, if K is dynamic, too, you just make the K element array dynamic. Making it larger simply means push down all the stacks. So if you don't mind O(N) push and pop operations, K shouldn't be a constant.

I wonder if I got the job.

zmbq
  • 38,013
  • 14
  • 101
  • 171