Stacks and Queues#

Stacks and queues are abstract data types — they define what operations are available, not how they are implemented.

Stacks (LIFO)#

A stack follows Last-In, First-Out order: the most recently added item is the first to be removed, like a stack of plates.

Operations:

  • push: add an item to the top

  • pop: remove and return the top item

  • peek: inspect the top item without removing it

  • is_empty: check whether the stack is empty

Using a Python list as a stack:

Run this to watch the stack grow and shrink — edit the pushes and re-run:

>>> stack = []
>>> stack.append(1)    # push
>>> stack.append(2)
>>> stack.append(3)
>>> print(stack[-1])   # peek: 3
3
>>> print(stack.pop()) # pop: 3
3
>>> print(stack.pop()) # pop: 2
2
>>> print(stack)       # [1]
[1]

list.append() adds to the end (top); list.pop() removes from the end — both O(1).

A Stack Class#

Wrapping the list in a class gives a cleaner interface:

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

    def push(self, item) -> None:
        self._data.append(item)

    def pop(self):
        if self.is_empty():
            raise IndexError("pop from empty stack")
        return self._data.pop()

    def peek(self):
        if self.is_empty():
            raise IndexError("peek at empty stack")
        return self._data[-1]

    def is_empty(self) -> bool:
        return len(self._data) == 0

    def __len__(self) -> int:
        return len(self._data)

Application: Checking Balanced Brackets#

A classic stack use-case: verify that brackets are correctly matched:

def is_balanced(s: str) -> bool:
    """Return True if all brackets in s are correctly matched and nested."""
    stack = Stack()
    pairs = {")": "(", "]": "[", "}": "{"}
    for ch in s:
        if ch in "([{":
            stack.push(ch)
        elif ch in ")]}":
            if stack.is_empty() or stack.pop() != pairs[ch]:
                return False
    return stack.is_empty()

Run it on a few strings — try one with mismatched brackets of your own:

>>> class Stack:
...     def __init__(self):
...         self._items = []
...     def push(self, item):
...         self._items.append(item)
...     def pop(self):
...         return self._items.pop()
...     def is_empty(self):
...         return len(self._items) == 0
...
>>> def is_balanced(s: str) -> bool:
...     stack = Stack()
...     pairs = {")": "(", "]": "[", "}": "{"}
...     for ch in s:
...         if ch in "([{":
...             stack.push(ch)
...         elif ch in ")]}":
...             if stack.is_empty() or stack.pop() != pairs[ch]:
...                 return False
...     return stack.is_empty()
...
>>> print(is_balanced("({[]})"))
True
>>> print(is_balanced("({[})"))
False

Queues (FIFO)#

A queue follows First-In, First-Out order: like a line of people waiting, the first to join is the first to leave.

Operations:

  • enqueue: add an item to the back

  • dequeue: remove and return the front item

  • is_empty: check whether the queue is empty

Using ``collections.deque``:

A Python list is slow for dequeue (removing from the front is O(N)). collections.deque supports O(1) operations at both ends:

Run this to see the queue serve people in arrival order:

>>> from collections import deque
>>> queue = deque()
>>> queue.append("Alice")    # enqueue
>>> queue.append("Bob")
>>> queue.append("Carol")
>>> print(queue.popleft())   # dequeue: Alice
Alice
>>> print(queue.popleft())   # dequeue: Bob
Bob
>>> print(queue)             # deque(['Carol'])
deque(['Carol'])

A Queue Class#

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

    def enqueue(self, item) -> None:
        self._data.append(item)

    def dequeue(self):
        if self.is_empty():
            raise IndexError("dequeue from empty queue")
        return self._data.popleft()

    def is_empty(self) -> bool:
        return len(self._data) == 0

    def __len__(self) -> int:
        return len(self._data)