Implement Queue using Stack Data Structure

Queues are a fundamental data structure that follow the First-In-First-Out (FIFO) principle. This principle is exactly opposite of Last-In-First-Out (LIFO) principle on which Stack data structure is based on. While queues can be implemented directly using an array or a linked list, they can also be constructed using two stacks.

Implementing a queue with stacks involves maintaining two stacks, where one stack is used to store the elements, while the other stack is used to reverse the order of the elements. Reversing the order of elements in the stack makes sure that among all the elements present in the stack, the most oldest or first element added to the stack will always stay on top of the stack. Similarly the next oldest element is the next topmost element in the stack and so on. The recently added element should be at the bottom of the stack. This ensures that the first element added will be the first element to be popped out, similar to how the Queue behaves.

Below is a basic program which demonstrates implementing a queue data structure using two stack objects:


class Queue:
  def __init__(self):
    # Initialize two empty stacks to implement the queue
    self.stack1 = []
    self.stack2 = []

  def enqueue(self, item):
    """
    Adds an item to the tail end of the Queue.
    """
    # Move all elements from stack1 to stack2
    while self.stack1:
      self.stack2.append(self.stack1.pop())

    # Push the new item to stack1
    self.stack1.append(item)

    # Move all elements back from stack2 to stack1
    while self.stack2:
      self.stack1.append(self.stack2.pop())

  def dequeue(self):
    """
    Removes and returns the front item from the Queue.
    """
    if not self.stack1:
      raise IndexError("Queue is empty")
    return self.stack1.pop()

  def peek(self):
    """
    Returns the front item from the Queue without removing it.
    """
    if not self.stack1:
      raise IndexError("Queue is empty")
    # Return the top most element in Stack without popping
    return self.stack1[-1]

  def is_empty(self):
    """
    Returns True if the stack is empty, False otherwise.
    """
    return not self.stack1

# Let's now test the Queue implementation
queue = Queue()
queue.enqueue(1)
queue.enqueue(2)
queue.enqueue(3)
print("Dequeuing front element: ", queue.dequeue())  # Output: 1
print("Peeking front element: ", queue.peek())  # Output: 2
print("Dequeuing front element: ", queue.dequeue())  # Output: 2
print("Is Queue empty?: ", queue.is_empty())  # Output: False, since item 3 is still in queue

Here’s how the implementation works:

  1. The Queue class has two stacks: stack1 and stack2.
  2. In the enqueue operation:
    • We ensure that the topmost element in the stack is always the first element added to the Queue among all the elements present.
    • And bottom most element in the stack should the mostly recently added element.
    • In order to achieve this, we perform the below steps:
      • Move all elements from stack1 to stack2.
      • Push the new item to stack1.
      • Move all elements back from stack2 to stack1.
      • The time complexity is O(N), where N is the number of elements in the queue.
  3. The dequeue operation:
    • Checks if the stack1 is empty. If it is, it raises an error indicating that the queue is empty.
    • Otherwise, it pops the top element from stack1, which is the first element added to the queue.
    • The time complexity of the dequeue operation is O(1).
  4. The peek operation:
    • Checks if the stack1 is empty. If it is, it raises an error indicating that the queue is empty.
    • Otherwise, it returns the top element of stack1, which is the first element added to the queue.
    • The time complexity of the peek operation is O(1).
  5. The is_empty operation:
    • Returns True if stack1 is empty, indicating that the queue is empty.
    • The time complexity of the is_empty operation is O(1).

This implementation ensures that the enqueue operation takes O(N) time, while the dequeue, peek, and is_empty operations take O(1) time. There are other variations of implementations as well with different time complexities. This implementation is simpler to understand among all as the core logic of reversing the order of elements in the stack is done only in one operation enqueue.