Blog Data Structure

Implementing Queue Using Stacks and Stack Using Queues 🔄

In this blog, we’ll explore how to implement a queue using two stacks and a stack using two queues. We’ll provide implementations in both Java and Python, following clean code practices for better readability and maintainability. 💻

Queue Implementation Using Stacks 🥞

A queue is a linear data structure that follows the First-In-First-Out (FIFO) principle. We can implement a queue using two stacks, where one stack is used for enqueue operation, and the other is used for dequeue operation.

Java Implementation:

Java
import java.util.Stack;

class QueueUsingStacks {
    private Stack<Integer> enqStack; // For enqueue operation
    private Stack<Integer> deqStack; // For dequeue operation

    public QueueUsingStacks() {
        enqStack = new Stack<>();
        deqStack = new Stack<>();
    }

    // Enqueue operation
    public void enqueue(int value) {
        enqStack.push(value);
    }

    // Dequeue operation
    public int dequeue() {
        if (deqStack.isEmpty()) {
            // Move all elements from enqStack to deqStack
            while (!enqStack.isEmpty()) {
                deqStack.push(enqStack.pop());
            }
        }

        // If both stacks are empty, return an error value
        if (deqStack.isEmpty()) {
            return -1; // Or throw an exception
        }

        // Return the top element from deqStack
        return deqStack.pop();
    }
}

Python Implementation: 🐍

Python
class QueueUsingStacks:
    def __init__(self):
        self.enq_stack = []  # For enqueue operation
        self.deq_stack = []  # For dequeue operation

    def enqueue(self, value):
        self.enq_stack.append(value)

    def dequeue(self):
        if not self.deq_stack:
            # Move all elements from enq_stack to deq_stack
            while self.enq_stack:
                self.deq_stack.append(self.enq_stack.pop())

        # If both stacks are empty, return an error value
        if not self.deq_stack:
            return -1  # Or raise an exception

        # Return the top element from deq_stack
        return self.deq_stack.pop()

In both implementations, we use two stacks: enqStack (or enq_stack) for enqueue operation and deqStack (or deq_stack) for dequeue operation. The enqueue method simply pushes the element onto the enqStack. 🥞➡️🍽️

The dequeue method first checks if the deqStack is empty. If it is, we move all elements from enqStack to deqStack in reverse order. This ensures that the first element pushed into enqStack will be the last element in deqStack, maintaining the FIFO order. 🍽️⬅️🥞 If both stacks are empty, we return an error value or raise an exception. 🚫 Otherwise, we pop and return the top element from deqStack. 🍽️🔝

Stack Implementation Using Queues 🌮

A stack is a linear data structure that follows the Last-In-First-Out (LIFO) principle. We can implement a stack using two queues, where one queue is used for pushing elements, and the other is used for popping elements.

Java Implementation:

Java
import java.util.LinkedList;
import java.util.Queue;

class StackUsingQueues {
    private Queue<Integer> pushQueue; // For push operation
    private Queue<Integer> popQueue; // For pop operation

    public StackUsingQueues() {
        pushQueue = new LinkedList<>();
        popQueue = new LinkedList<>();
    }

    // Push operation
    public void push(int value) {
        pushQueue.offer(value);
    }

    // Pop operation
    public int pop() {
        if (popQueue.isEmpty()) {
            // Move all elements from pushQueue to popQueue except the last one
            while (pushQueue.size() > 1) {
                popQueue.offer(pushQueue.poll());
            }

            // If pushQueue is empty, return an error value
            if (pushQueue.isEmpty()) {
                return -1; // Or throw an exception
            }

            // The last element in pushQueue is the top of the stack
            int topElement = pushQueue.poll();
            popQueue.offer(topElement);
        }

        // Return the front element from popQueue
        return popQueue.poll();
    }
}

Python Implementation: 🐍

Python
from collections import deque

class StackUsingQueues:
    def __init__(self):
        self.push_queue = deque()  # For push operation
        self.pop_queue = deque()  # For pop operation

    def push(self, value):
        self.push_queue.append(value)

    def pop(self):
        if not self.pop_queue:
            # Move all elements from push_queue to pop_queue except the last one
            while len(self.push_queue) > 1:
                self.pop_queue.append(self.push_queue.popleft())

            # If push_queue is empty, return an error value
            if not self.push_queue:
                return -1  # Or raise an exception

            # The last element in push_queue is the top of the stack
            top_element = self.push_queue.popleft()
            self.pop_queue.append(top_element)

        # Return the front element from pop_queue
        return self.pop_queue.popleft()

In both implementations, we use two queues: pushQueue (or push_queue) for push operation and popQueue (or pop_queue) for pop operation. The push method simply adds the element to the end of the pushQueue. 🌮➡️🍔

The pop method first checks if the popQueue is empty. If it is, we move all elements from pushQueue to popQueue except the last one. This last element in pushQueue is the top of the stack. 🌮🔝 We store this element in a temporary variable and add it to the end of popQueue. If pushQueue is empty, we return an error value or raise an exception. 🚫 Otherwise, we pop and return the front element from popQueue. 🍔⬅️🌮

In both implementations, we follow clean code practices by using descriptive variable and method names, adding comments to explain the logic, and handling edge cases like empty stacks or queues. 👌

By implementing a queue using stacks and a stack using queues, we can understand the underlying principles of these data structures and how they can be used to create different abstractions. 🧠 These implementations also demonstrate the power of using existing data structures to create new ones, showcasing the versatility of programming languages like Java and Python. 💪

Avatar

Neelabh

About Author

As Neelabh Singh, I am a Senior Software Engineer with 6.6 years of experience, specializing in Java technologies, Microservices, AWS, Algorithms, and Data Structures. I am also a technology blogger and an active participant in several online coding communities.

You may also like

Blog Design Pattern

Understanding the Builder Design Pattern in Java | Creational Design Patterns | CodeTechSummit

Overview The Builder design pattern is a creational pattern used to construct a complex object step by step. It separates
Blog Tech Toolkit

Base64 Decode

Base64 encoding is a technique used to encode binary data into ASCII characters, making it easier to transmit data over