stack, queue in python

Stacks and queues are two of the most fundamental data structures in computer science. Understanding how they work and knowing how to implement them efficiently is essential for solving a wide variety of problems, especially when working with algorithms and data processing. In this article, we will explore the theoretical concepts behind stacks and queues, dive into their operations, and implement both structures using Python with practical examples.

1. What Are Stacks and Queues?

Both stacks and queues are linear data structures that store elements in a sequence. However, they differ in the way elements are inserted and removed:

  • Stack: Follows a Last In, First Out (LIFO) principle. This means the last element added to the stack is the first one to be removed.
  • Queue: Follows a First In, First Out (FIFO) principle. This means the first element added to the queue is the first one to be removed.

Both structures are used extensively in programming and algorithms due to their simple yet powerful operations.


2. Understanding Stacks

A stack can be visualized as a pile of plates. You can only add a new plate on top of the stack, and when you need to remove one, you take the plate from the top. This is precisely how the LIFO principle works in a stack.

2.1 Operations in a Stack

There are four key operations associated with stacks:

  1. Push: Adds an element to the top of the stack.
  2. Pop: Removes the top element from the stack.
  3. Peek/Top: Views the top element without removing it.
  4. isEmpty: Checks whether the stack is empty.

2.2 Implementation of a Stack in Python

Python’s built-in list can be used to implement a stack, as the append() method mimics the push operation, and pop() mimics the pop operation.

class Stack:
    def __init__(self):
        self.stack = []

    def push(self, item):
        self.stack.append(item)

    def pop(self):
        if not self.is_empty():
            return self.stack.pop()
        else:
            return "Stack is empty"

    def peek(self):
        if not self.is_empty():
            return self.stack[-1]
        else:
            return "Stack is empty"

    def is_empty(self):
        return len(self.stack) == 0

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

Let’s test the implementation with some examples:

 my_stack = Stack()
my_stack.push(10)
my_stack.push(20)
my_stack.push(30)
print(my_stack.peek())  # Output: 30
print(my_stack.pop())   # Output: 30
print(my_stack.pop())   # Output: 20
print(my_stack.is_empty())  # Output: False

The stack starts empty, and after pushing three values (10, 20, 30), the peek function shows the top value, 30. After popping twice, the stack now contains only one element.

2.3 Practical Applications of Stacks

Stacks are useful in various programming situations:

  • Function Call Management: Programming languages often use stacks to manage function calls. When a function is called, its details are pushed onto the stack, and once it completes, it is popped from the stack.
  • Undo Functionality: Applications like text editors use stacks to implement the undo feature. Each operation is pushed onto the stack, and when the user wants to undo, the last operation is popped off.
  • Balancing Parentheses: A stack can be used to check if the parentheses in an expression are balanced.

2.4 Example: Checking Balanced Parentheses

Here’s a simple program that uses a stack to check if a string has balanced parentheses:

def is_balanced(expression):
stack = Stack()
for char in expression:
if char in “({[“:
stack.push(char)
elif char in “)}]”:
if stack.is_empty():
return False
top = stack.pop()
if not matches(top, char):
return False
return stack.is_empty()

def matches(opening, closing):
return (opening == ‘(‘ and closing == ‘)’) or \
(opening == ‘{‘ and closing == ‘}’) or \
(opening == ‘[‘ and closing == ‘]’)

print(is_balanced(“{[()]}”)) # Output: True
print(is_balanced(“{[(])}”)) # Output: False


3. Understanding Queues

A queue can be visualized as a line of people waiting to be served. The person who joins the queue first is the first to be served, which follows the FIFO principle.

3.1 Operations in a Queue

A queue has the following operations:

  1. Enqueue: Adds an element to the end of the queue.
  2. Dequeue: Removes the element from the front of the queue.
  3. Peek/Front: Views the front element without removing it.
  4. isEmpty: Checks if the queue is empty.

3.2 Implementation of a Queue in Python

Using Python’s collections.deque, which provides efficient operations for both ends of a queue, we can implement a queue efficiently.

 from collections import deque

class Queue:
    def __init__(self):
        self.queue = deque()

    def enqueue(self, item):
        self.queue.append(item)

    def dequeue(self):
        if not self.is_empty():
            return self.queue.popleft()
        else:
            return "Queue is empty"

    def peek(self):
        if not self.is_empty():
            return self.queue[0]
        else:
            return "Queue is empty"

    def is_empty(self):
        return len(self.queue) == 0

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

3.3 Example Usage

 my_queue = Queue()
my_queue.enqueue(10)
my_queue.enqueue(20)
my_queue.enqueue(30)
print(my_queue.peek())  # Output: 10
print(my_queue.dequeue())  # Output: 10
print(my_queue.dequeue())  # Output: 20
print(my_queue.is_empty())  # Output: False

This simple queue implementation allows us to add elements to the back and remove elements from the front, following the FIFO principle.

3.4 Practical Applications of Queues

Queues are extremely useful in many real-world applications:

  • Task Scheduling: Queues are used to manage tasks in operating systems, especially in CPU scheduling and disk scheduling.
  • Breadth-First Search (BFS): In graph traversal algorithms like BFS, a queue is used to explore nodes level by level.
  • Print Spooler: In a printer queue, documents are printed in the order they were sent.

4. Comparison Between Stacks and Queues

Both stacks and queues have their uses, but they are suited to different types of problems.

  • Stack is useful when you need to manage data in a LIFO fashion. This is helpful in scenarios like backtracking (e.g., navigating backward in a web browser) and parsing expressions.
  • Queue is helpful when you need to process data in a FIFO manner. This is used in scheduling problems, like managing customer service requests or simulating real-world queues.
FeatureStack (LIFO)Queue (FIFO)
InsertionElements are added to the top.Elements are added to the back.
RemovalElements are removed from the top.Elements are removed from the front.
UsageFunction calls, undo/redo operations.Scheduling tasks, BFS, managing queues.

5. Stacks and Queues in Python Libraries

Although we demonstrated implementing stacks and queues manually, Python provides built-in data structures that make the process more efficient:

  • Stack: Can be implemented using list or collections.deque for fast access.
  • Queue: The queue module provides implementations for different types of queues, such as:
    • Queue: FIFO queue.
    • LifoQueue: LIFO queue (similar to a stack).
    • PriorityQueue: A queue where each element is associated with a priority.
 from queue import Queue

q = Queue()
q.put(10)
q.put(20)
print(q.get())  # Output: 10

Conclusion

Understanding and implementing stacks and queues is an essential skill for any programmer. These simple yet powerful data structures are used in various applications, from operating systems to algorithms. Whether you use Python’s built-in tools or implement them from scratch, mastering these structures will enhance your problem-solving capabilities and deepen your understanding of data management.

By practicing with real-world examples and applying stacks and queues in different scenarios, you’ll gain confidence in knowing when and how to use these versatile data structures.

Recent Articles

Related Posts