A circular queue is a data structure that effectively manages first-in-first-out (FIFO) operations. It is a linear data structure that utilizes a circular memory layout to store data elements, allowing both ends of the queue to be used to insert and remove elements. This makes circular queues more memory efficient than traditional linear queues.
In this comprehensive guide, we will learn how to implement a circular queue in Python. We will cover the following topics:
Table of Contents
Open Table of Contents
Overview of Circular Queues
A circular queue is a linear data structure that stores elements in a circular fashion. Unlike a regular queue that ends when it reaches capacity, a circular queue connects the rear to the front to make use of the empty space when the queue is not full.
This makes circular queues more memory efficient as the memory is reused when elements are dequeued. The circular queue follows FIFO - first-in-first-out - order when enqueueing and dequeueing elements.
The key characteristics of circular queues:
- Stores data elements in a circular fashion using a circular array
- The rear and front of the queue are connected
- The queue capacity is limited by the size of the underlying array
- New elements are added to the rear of the queue
- Elements are removed from the front of the queue
- Oldest elements are found at the front
- Newest elements are found at the rear
Circular queues are useful when you need to buffer incoming data or transfer elements between threads efficiently. They have applications in real-time embedded systems, audio processing, and computer graphics.
Circular Queue Operations
The main operations supported by a circular queue are:
Enqueue
Adds a new element to the rear of the queue.
- Check if the queue is full before inserting
- Increment rear index and insert element
- Adjust rear index to loop back to 0 when it reaches max capacity
Dequeue
Removes the oldest element from the front of the queue.
- Check if the queue is empty before removing
- Retrieve the front element
- Increment front index to advance the queue
- Adjust front index to loop back to 0 when it reaches max capacity
Front
Returns the element at the front of the queue without removing it.
- Check if the queue is empty
- Return the element at the front index
Now let’s see how to implement these operations in Python.
Basic Implementation in Python
Before creating a custom class, we can implement a basic circular queue using built-in Python data structures like lists or the collections module deque.
Using List
# Initialize empty list with capacity
queue = [None] * 5
# Enqueue
def enqueue(queue, item):
queue.append(item)
if len(queue) > len(queue) - 1:
queue.pop(0)
# Dequeue
def dequeue(queue):
if len(queue) > 0:
item = queue[0]
queue.pop(0)
return item
else:
print("Queue empty!")
return None
# Front
def front(queue):
if len(queue) > 0:
return queue[0]
else:
print("Queue empty!")
return None
# Driver code
q = [None] * 5
enqueue(q, 1)
enqueue(q, 2)
enqueue(q, 3)
print(q) # [1, 2, 3, None, None]
print(front(q)) # 1
print(dequeue(q)) # 1
print(q) # [2, 3, None, None, None]
We initialize an empty list of a fixed capacity to represent the circular queue. The enqueue operation appends elements to the rear, popping from the front if full. Dequeue pops from the front while front returns the first element.
This demonstrates the basic logic, but lacks some validation and modularity. Next we’ll use the deque class which handles the circular behavior for us.
Using Collections Module
Python’s collections module provides specialized container datatypes, including a double-ended queue or deque class that naturally handles circular queues.
from collections import deque
# Initialize deque with max length
queue = deque(maxlen=5)
# Enqueue
def enqueue(queue, item):
queue.append(item)
# Dequeue
def dequeue(queue):
if len(queue) > 0:
return queue.popleft()
else:
print("Queue empty!")
return None
# Front
def front(queue):
if len(queue) > 0:
return queue[0]
else:
print("Queue empty!")
return None
# Driver code
q = deque(maxlen=5)
enqueue(q, 1)
enqueue(q, 2)
enqueue(q, 3)
print(q) # deque([1, 2, 3], maxlen=5)
print(front(q)) # 1
print(dequeue(q)) # 1
deque handles the circular behavior and capacity limit automatically. We simply append to enqueue, pop from the left to dequeue, and access index 0 to get the front.
This is simple but lacks customization. Next we’ll build a custom CircularQueue class with more control.
Circular Queue Class in Python
For full control and extensibility, we can build a CircularQueue class in Python. The key aspects are:
- Initialize class with max capacity
- Enqueue method to add elements to rear
- Dequeue method to remove from front
- Front method to get front element
- Adjust indices to loop around circularly
- Validate inputs and capacities
class CircularQueue:
def __init__(self, max_size):
self.queue = [None] * max_size
self.max_size = max_size
self.head = self.tail = -1
def enqueue(self, data):
if self.tail + 1 == self.head or (self.head == 0 and self.tail + 1 == self.max_size):
print("Queue full!")
return -1
elif self.tail == -1: # First element
self.tail = 0
self.head = 0
self.queue[self.tail] = data
else:
self.tail = (self.tail + 1) % self.max_size
self.queue[self.tail] = data
def dequeue(self):
if self.head == -1:
print("Queue empty!")
return -1
data = self.queue[self.head]
self.queue[self.head] = None
if self.head == self.tail:
self.head = -1
self.tail = -1
else:
self.head = (self.head + 1) % self.max_size
return data
def front(self):
if self.head == -1:
print("Queue empty!")
return -1
return self.queue[self.head]
# Driver code
q = CircularQueue(5)
q.enqueue(1)
q.enqueue(2)
q.enqueue(3)
print(q.queue) # [1, 2, 3, None, None]
print(q.front()) # 1
print(q.dequeue()) # 1
print(q.queue) # [2, 3, None, None, None]
The CircularQueue class initializes the circular queue with a given maximum capacity. We use a Python list to represent the underlying circular array.
The enqueue method inserts elements at the tail, looping back to 0 when reaching max size. Dequeue removes from the head, also looping back around. Front simply returns the head element.
This gives us full control over a circular queue implementation in Python!
Complexity Analysis
The complexity of core circular queue operations is:
- Enqueue - O(1) constant time. Just insert at tail index.
- Dequeue - O(1) constant time. Just remove from head index.
- Front - O(1) constant time. Access head element directly.
Since we use a fixed size array as the underlying data structure, all operations complete in constant time.
The space complexity is O(N) linear for the array of size N capacity.
Compared to dynamic arrays or linked lists, circular queues provide great performance by sacrificing flexibility in size.
Applications of Circular Queues
Circular queues provide an efficient FIFO data structure useful for several applications:
- Real-time systems - Used in real-time operating systems and embedded systems due to constant time operations and fixed memory footprint.
- Buffer - Can act as a fixed size buffer to store incoming data from one thread or process before passing to another.
- Traffic modeling - Used to simulate queued waiting vehicles at a traffic intersection.
- CPU scheduling - Can model fixed sized ready queue of processes for Round-Robin scheduling.
- Broadcasting - Queue up media segments in consumer devices like televisions and radios.
Some key benefits over regular queues:
- Memory efficient due to circular reuse
- Constant time enqueue and dequeue operations
- Does not require resizing array or linked list
- Simpler to implement than dynamic data structures
- Fixed size provides predictable memory footprint
Circular queues strike an effective balance between flexibility and performance for FIFO data structures.
Conclusion
In this guide, we learned how circular queues work and how to implement them in Python using lists, deques, and a custom class.
Key takeaways:
- Circular queues reuse memory by connecting front and rear in a circular array
- Main operations are enqueue, dequeue, and front
- Can be implemented using Python lists, deque class, or custom CircularQueue
- Provides constant time operations and fixed size memory footprint
- Useful for real-time systems, buffers, traffic modeling, and other applications
Circular queues are fundamental data structures in computer science and have many practical use cases. Using the concepts and Python code covered here, you should be able to implement circular queues efficiently in your own applications.