Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions Python/data_structures/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This directory contains implementations of fundamental data structures in Python
data_structures/
├── linked_lists/ # Linked list implementations and operations
├── matrices/ # Matrix operations and algorithms
├── queue/ # Queue implementations (FIFO, Priority)
├── stacks/ # Stack implementations and applications
└── trees/ # Tree structures and algorithms
```
Expand Down Expand Up @@ -35,6 +36,7 @@ Located in `stacks/`
### Current Implementations
- **Delete Middle Element** (`delete_middle_element_of_stack.py`) - Remove middle element from stack
- **Stack using Queues** (`implement_stack_using_queues.py`) - Implement stack using queue operations
- **Stack with Min** (`stack_with_min.py`) - Stack supporting O(1) `get_min` operation

### Stack Applications
- Expression evaluation
Expand All @@ -58,6 +60,44 @@ stack.append(1) # push
top = stack.pop() # pop
```

## 🔄 Queues

Located in `queue/`

### Current Implementations
- **Array Queue** (`array_queue.py`) - Simple queue using Python list (O(n) dequeue)
- **Queue using Stacks** (`queue_using_stacks.py`) - Queue implemented with two stacks (O(1) amortized enqueue/dequeue)

### Queue Applications
- Breadth-First Search (BFS)
- Scheduling (CPU, tasks)
- Buffers (I/O, network)
- Simulations

### Python Queue Examples
```python
# Using collections.deque (efficient FIFO)
from collections import deque
q = deque()
q.append('a') # enqueue
q.append('b') # enqueue
front = q.popleft() # dequeue ('a')

# Using queue.Queue (thread-safe)
from queue import Queue
q = Queue()
q.put('a') # enqueue
q.put('b') # enqueue
front = q.get() # dequeue ('a')

# Using heapq for Priority Queue
import heapq
pq = []
heapq.heappush(pq, (2, 'task1')) # (priority, item)
heapq.heappush(pq, (1, 'task2'))
highest_prio_item = heapq.heappop(pq)[1] # 'task2'
```

## 🌳 Trees

Located in `trees/`
Expand Down
133 changes: 133 additions & 0 deletions Python/data_structures/queue/array_queue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Python/data_structures/queue/array_queue.py

"""
Data Structure Description:
A Queue is a linear data structure that follows the First-In, First-Out (FIFO)
principle. Elements are added (enqueued) at the rear and removed (dequeued)
from the front. This implementation uses a standard Python list as the
underlying storage. While simple, dequeuing from the beginning of a Python
list can be inefficient (O(n)) as elements need to be shifted.
"""

# Time Complexity:
# - Enqueue (append): O(1) on average
# - Dequeue (pop(0)): O(n) because all subsequent elements must be shifted
# - Peek: O(1)
# - is_empty: O(1)
# - size: O(1)
# Space Complexity: O(n)
# - Where n is the number of elements stored in the queue.

class ArrayQueue:
"""Implements a queue using a Python list."""

def __init__(self):
"""Initializes an empty queue."""
self._items = []

def enqueue(self, item):
"""Adds an item to the rear of the queue.
>>> q = ArrayQueue()
>>> q.enqueue(1)
>>> q.enqueue(2)
>>> print(q._items)
[1, 2]
"""
self._items.append(item)

def dequeue(self):
"""Removes and returns the item from the front of the queue.
Raises IndexError if the queue is empty.
>>> q = ArrayQueue()
>>> q.enqueue(1)
>>> q.enqueue(2)
>>> q.dequeue()
1
>>> q.dequeue()
2
>>> q.dequeue()
Traceback (most recent call last):
...
IndexError: dequeue from empty queue
"""
if not self._items:
raise IndexError("dequeue from empty queue")
return self._items.pop(0) # Inefficient part

def peek(self):
"""Returns the item at the front of the queue without removing it.
Raises IndexError if the queue is empty.
>>> q = ArrayQueue()
>>> q.enqueue(1)
>>> q.enqueue(2)
>>> q.peek()
1
>>> q.dequeue()
1
>>> q.peek()
2
>>> q.dequeue()
2
>>> q.peek()
Traceback (most recent call last):
...
IndexError: peek from empty queue
"""
if not self._items:
raise IndexError("peek from empty queue")
return self._items[0]

def is_empty(self):
"""Returns True if the queue is empty, False otherwise.
>>> q = ArrayQueue()
>>> q.is_empty()
True
>>> q.enqueue(1)
>>> q.is_empty()
False
"""
return len(self._items) == 0

def size(self):
"""Returns the number of items in the queue.
>>> q = ArrayQueue()
>>> q.size()
0
>>> q.enqueue(1)
>>> q.enqueue(2)
>>> q.size()
2
"""
return len(self._items)

def __len__(self):
"""Allows using len(q)."""
return self.size()

def __str__(self):
"""String representation of the queue."""
return f"ArrayQueue({self._items})"

def __repr__(self):
"""Representation of the queue."""
return f"ArrayQueue({self._items})"

if __name__ == "__main__":
import doctest
doctest.testmod()

q = ArrayQueue()
print("Is queue empty?", q.is_empty())
q.enqueue("A")
q.enqueue("B")
q.enqueue("C")
print(q)
print("Size:", len(q))
print("Peek:", q.peek())
print("Dequeue:", q.dequeue())
print(q)
print("Dequeue:", q.dequeue())
print("Is queue empty?", q.is_empty())
print("Dequeue:", q.dequeue())
print("Is queue empty?", q.is_empty())
# print("Dequeue:", q.dequeue()) # This will raise IndexError
155 changes: 155 additions & 0 deletions Python/data_structures/queue/queue_using_stacks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Python/data_structures/queue/queue_using_stacks.py

"""
Data Structure Description:
This implements a Queue (FIFO - First-In, First-Out) using two Stacks
(LIFO - Last-In, First-Out). One stack (`_in_stack`) is used for enqueue
operations, and the other (`_out_stack`) is used for dequeue and peek
operations. Elements are moved from `_in_stack` to `_out_stack` only when
`_out_stack` is empty, ensuring the FIFO order is maintained efficiently
on average (amortized O(1) for dequeue).
"""

# Time Complexity:
# - Enqueue: O(1) - Simply push onto the in_stack.
# - Dequeue: O(1) amortized. In the worst case (when out_stack is empty
# and in_stack has n elements), it takes O(n) to transfer elements.
# However, each element is moved only once, so over n operations, the
# average cost is O(1).
# - Peek: O(1) amortized - Same reasoning as dequeue.
# - is_empty: O(1)
# - size: O(1)
# Space Complexity: O(n)
# - Where n is the number of elements stored across both stacks.

class QueueUsingStacks:
"""Implements a FIFO queue using two LIFO stacks."""

def __init__(self):
"""Initializes the two stacks."""
self._in_stack = [] # Used for enqueue
self._out_stack = [] # Used for dequeue/peek

def enqueue(self, item):
"""Adds an item to the rear of the queue.
>>> q = QueueUsingStacks()
>>> q.enqueue(1)
>>> q.enqueue(2)
>>> print(q._in_stack)
[1, 2]
"""
self._in_stack.append(item)

def _transfer_elements(self):
"""Transfers elements from in_stack to out_stack if out_stack is empty."""
if not self._out_stack:
while self._in_stack:
self._out_stack.append(self._in_stack.pop())

def dequeue(self):
"""Removes and returns the item from the front of the queue.
Raises IndexError if the queue is empty.
>>> q = QueueUsingStacks()
>>> q.enqueue(1)
>>> q.enqueue(2)
>>> q.dequeue() # Transfers 1, 2 to out_stack [2, 1], pops 1
1
>>> q.enqueue(3)
>>> q.dequeue() # Pops 2 from out_stack
2
>>> q.dequeue() # Transfers 3 to out_stack [3], pops 3
3
>>> q.dequeue()
Traceback (most recent call last):
...
IndexError: dequeue from empty queue
"""
self._transfer_elements()
if not self._out_stack:
raise IndexError("dequeue from empty queue")
return self._out_stack.pop()

def peek(self):
"""Returns the item at the front of the queue without removing it.
Raises IndexError if the queue is empty.
>>> q = QueueUsingStacks()
>>> q.enqueue(1)
>>> q.enqueue(2)
>>> q.peek() # Transfers 1, 2 to out_stack [2, 1], returns 1
1
>>> q.dequeue()
1
>>> q.peek() # Returns 2 from out_stack
2
>>> q.dequeue()
2
>>> q.peek()
Traceback (most recent call last):
...
IndexError: peek from empty queue
"""
self._transfer_elements()
if not self._out_stack:
raise IndexError("peek from empty queue")
return self._out_stack[-1] # Peek at the top of out_stack

def is_empty(self):
"""Returns True if the queue is empty, False otherwise.
>>> q = QueueUsingStacks()
>>> q.is_empty()
True
>>> q.enqueue(1)
>>> q.is_empty()
False
"""
return not self._in_stack and not self._out_stack

def size(self):
"""Returns the total number of items in the queue.
>>> q = QueueUsingStacks()
>>> q.size()
0
>>> q.enqueue(1)
>>> q.enqueue(2)
>>> q.size()
2
>>> q.dequeue()
1
>>> q.size()
1
"""
return len(self._in_stack) + len(self._out_stack)

def __len__(self):
"""Allows using len(q)."""
return self.size()

def __str__(self):
"""String representation showing logical FIFO order (may be slow)."""
# Note: This is inefficient for visualization but shows the logical order
elements = list(reversed(self._out_stack)) + list(self._in_stack)
return f"QueueUsingStacks({elements})"

def __repr__(self):
"""Representation showing internal stack states."""
return f"QueueUsingStacks(in={self._in_stack}, out={self._out_stack})"

if __name__ == "__main__":
import doctest
doctest.testmod()

q = QueueUsingStacks()
print("Is empty?", q.is_empty())
q.enqueue('A')
q.enqueue('B')
print(q)
print("Peek:", q.peek())
print("Dequeue:", q.dequeue())
print(q)
q.enqueue('C')
print(q)
print("Size:", len(q))
print("Dequeue:", q.dequeue())
print("Dequeue:", q.dequeue())
print("Is empty?", q.is_empty())
# print("Dequeue:", q.dequeue()) # Raises IndexError
Loading