How do I use two stacks to implement queues?

Suppose we have two stacks and no other temporary variables. ...

Suppose we have two stacks and no other temporary variables.

Can I "construct" queue data structures using only two stacks?

#1 building

public class QueueUsingStacks<T> { private LinkedListStack<T> stack1; private LinkedListStack<T> stack2; public QueueUsingStacks() { stack1=new LinkedListStack<T>(); stack2 = new LinkedListStack<T>(); } public void Copy(LinkedListStack<T> source,LinkedListStack<T> dest ) { while(source.Head!=null) { dest.Push(source.Head.Data); source.Head = source.Head.Next; } } public void Enqueue(T entry) { stack1.Push(entry); } public T Dequeue() { T obj; if (stack2 != null) { Copy(stack1, stack2); obj = stack2.Pop(); Copy(stack2, stack1); } else { throw new Exception("Stack is empty"); } return obj; } public void Display() { stack1.Display(); } }

For each enqueue operation, we add it to the top of stack1. For each dequeue, we empty the contents of stack1 into stack2 and delete the elements at the top of the stack. The time complexity is O (n) out of the team, because we have to copy stack1 to stack2. Enqueue has the same time complexity as regular stack

#2 building

And. The two stacks in the queue are defined as and.

In: queued elements are always pushed

Can be popped out since it is the first element inserted into queue when is not empty. is empty, we pop all elements from and push them into one by one. . the bottom of the queue where the first element is pushed. . after the eject and push operation, it can eject directly because it is at the top of.

Here is the same C + + sample code:

template <typename T> class CQueue { public: CQueue(void); ~CQueue(void); void appendTail(const T& node); T deleteHead(); private: stack<T> stack1; stack<T> stack2; }; template<typename T> void CQueue<T>::appendTail(const T& element) { stack1.push(element); } template<typename T> T CQueue<T>::deleteHead() { if(stack2.size()<= 0) { while(stack1.size()>0) { T& data = stack1.top(); stack1.pop(); stack2.push(data); } } if(stack2.size() == 0) throw new exception("queue is empty"); T head = stack2.top(); stack2.pop(); return head; }

This solution comes from My blog . A more detailed analysis of step-by-step simulation is available on my blog page.

#3 building

Let the queue be implemented as q. the stacks used to implement q are stack1 and stack2.

q can be achieved in two ways:

Method 1 (by making enQueue operations expensive)

This method ensures that the newly entered element is always at the top of stack 1, so the deQueue operation just pops from stack 1. To place the element at the top of stack1, use stack2.

enQueue(q, x) 1) While stack1 is not empty, push everything from stack1 to stack2. 2) Push x to stack1 (assuming size of stacks is unlimited). 3) Push everything back to stack1. deQueue(q) 1) If stack1 is empty then error 2) Pop an item from stack1 and return it.

Method 2 (by making deQueue expensive to operate)

In this method, in the queue operation, the new element is entered at the top of stack1. In the dequeue operation, if stack2 is empty, all elements are moved to stack2, and finally the top of stack2 is returned.

enQueue(q, x) 1) Push x to stack1 (assuming size of stacks is unlimited). deQueue(q) 1) If both stacks are empty then error. 2) If stack2 is empty While stack1 is not empty, push everything from stack1 to stack2. 3) Pop the element from stack2 and return it.

Method 2 is better than method 1. Method 1 moves all elements twice in the enQueue operation, while method 2 (in the deQueue operation) moves elements once and only when stack2 is empty.

#4 building

// Two stacks s1 Original and s2 as Temp one private Stack<Integer> s1 = new Stack<Integer>(); private Stack<Integer> s2 = new Stack<Integer>(); /* * Here we insert the data into the stack and if data all ready exist on * stack than we copy the entire stack s1 to s2 recursively and push the new * element data onto s1 and than again recursively call the s2 to pop on s1. * * Note here we can use either way ie We can keep pushing on s1 and than * while popping we can remove the first element from s2 by copying * recursively the data and removing the first index element. */ public void insert( int data ) { if( s1.size() == 0 ) { s1.push( data ); } else { while( !s1.isEmpty() ) { s2.push( s1.pop() ); } s1.push( data ); while( !s2.isEmpty() ) { s1.push( s2.pop() ); } } } public void remove() { if( s1.isEmpty() ) { System.out.println( "Empty" ); } else { s1.pop(); } }

#5 building

I'll answer this question in Go, because Go doesn't have a rich set in its standard library.

Since the stack is actually easy to implement, I think I'll try to use two stacks to complete a double ended queue. In order to better understand how I got the answer, I divided the implementation into two parts. The first part wants to be easier to understand, but it is incomplete.

type IntQueue struct { front []int back []int } func (q *IntQueue) PushFront(v int) { q.front = append(q.front, v) } func (q *IntQueue) Front() int { if len(q.front) > 0 { return q.front[len(q.front)-1] } else { return q.back[0] } } func (q *IntQueue) PopFront() { if len(q.front) > 0 { q.front = q.front[:len(q.front)-1] } else { q.back = q.back[1:] } } func (q *IntQueue) PushBack(v int) { q.back = append(q.back, v) } func (q *IntQueue) Back() int { if len(q.back) > 0 { return q.back[len(q.back)-1] } else { return q.front[0] } } func (q *IntQueue) PopBack() { if len(q.back) > 0 { q.back = q.back[:len(q.back)-1] } else { q.front = q.front[1:] } }

It's basically two stacks, and we allow the bottom of the stack to manipulate each other. I also use the STL naming convention, where the traditional push, pop, peek operations of the stack have a pre / post prefix, whether they refer to the front or back of the queue.

The problem with this code is that it doesn't use memory very efficiently. In fact, it will continue to grow until you run out of space. That's really bad. The fix for this is to reuse the bottom of the stack space as easily as possible. We have to introduce an offset to track this, because once the slice in Go shrinks, it cannot grow in front.

type IntQueue struct { front []int frontOffset int back []int backOffset int } func (q *IntQueue) PushFront(v int) { if q.backOffset > 0 { i := q.backOffset - 1 q.back[i] = v q.backOffset = i } else { q.front = append(q.front, v) } } func (q *IntQueue) Front() int { if len(q.front) > 0 { return q.front[len(q.front)-1] } else { return q.back[q.backOffset] } } func (q *IntQueue) PopFront() { if len(q.front) > 0 { q.front = q.front[:len(q.front)-1] } else { if len(q.back) > 0 { q.backOffset++ } else { panic("Cannot pop front of empty queue.") } } } func (q *IntQueue) PushBack(v int) { if q.frontOffset > 0 { i := q.frontOffset - 1 q.front[i] = v q.frontOffset = i } else { q.back = append(q.back, v) } } func (q *IntQueue) Back() int { if len(q.back) > 0 { return q.back[len(q.back)-1] } else { return q.front[q.frontOffset] } } func (q *IntQueue) PopBack() { if len(q.back) > 0 { q.back = q.back[:len(q.back)-1] } else { if len(q.front) > 0 { q.frontOffset++ } else { panic("Cannot pop back of empty queue.") } } }

It's a lot of small features, but there are six of them, three of which are just images of the other.

14 February 2020, 01:08 | Views: 7755

Add new comment

For adding a comment, please log in
or create account

0 comments