blog posts

Circular Queue

What is a Circular Queue in Data Structures and How is it Implemented?

A circular queue is a type of data structure that uses an array to store elements, where the end of the array is connected to its beginning, forming a circle.
In standard queues, after dequeuing several elements from the front, the space at the beginning of the array cannot be reused. However, in a circular queue, by connecting the end of the array to the beginning, this space becomes reusable.

To manage elements in a circular queue, two pointers named “front” and “rear” are used. The “rear” pointer indicates the position where a new element should be added, and the “front” pointer indicates the position from which the next element will be removed. When an element is added to the queue, the “rear” pointer is incremented.

front and rear
When an element is removed, the “front” pointer is incremented. If the “rear” pointer reaches the end of the array, it wraps around to the beginning. Similarly, if the “front” pointer reaches the end, it also wraps around.
The circular queue is very useful for managing limited resources, such as data buffers. For example, in operating systems, a circular queue is used to manage processes waiting for resources.


Everyday Operations on a Circular Queue in Data Structures

A circular queue, like a regular queue, is a data structure that follows the FIFO (First-In, First-Out) principle. The difference is that its end connects to its beginning, creating a loop. This feature allows the space created at the front of the queue to be reused.

The common operations on a circular queue include:

  • Enqueue: This operation adds a new element to the end of the queue. If the queue is complete (i.e., the rear pointer is one position behind the front pointer), the operation fails with an error. After adding the element, the rear pointer is incremented.
  • Dequeue: This operation removes the element from the front of the queue. If the queue is empty (i.e., the front and rear pointers are at the same position), the operation fails with an error. After removing the element, the front pointer is incremented.
  • isEmpty: This operation checks whether the queue is empty. The queue is empty if the front and rear pointers are in the exact location.
  • isFull: This operation checks whether the queue is full. The queue is complete if the rear pointer is one position behind the front pointer.
  • Peek: This operation returns the first element in the queue without removing it.

In a circular queue, the front and rear pointers are used to manage elements. The rear pointer points to the location for the subsequent insertion, and the front pointer points to the area for the following removal.


Applications of the Circular Queue

Due to its unique features, the circular queue has various applications in computer science and programming. Here are some of the most important ones:

Applications of the Circular Queue

  • Buffer Management: One of the primary uses of a circular queue is managing buffers in computer systems. Buffers are memory regions used for the temporary storage of data. The circular queue is highly suitable for managing data buffers because of its ability to reuse space.
  • Operating Systems: In operating systems, circular queues are used to manage processes waiting for resources. For example, when multiple processes need a shared resource (like a printer), they are placed in a circular queue and gain access to the resource in order.
  • Computer Networks: In computer networking, circular queues are used to manage data packets. Routers and switches use circular queues to store data packets that are being processed.
  • Signal Processing: In signal processing, circular queues are used to store signal samples. This is particularly useful in systems that deal with continuous signals, such as audio and video systems.
  • Process Scheduling: In operating systems, a circular queue is used for process scheduling. Processes that want to use the CPU are placed in a circular queue, and the CPU executes them sequentially (as in Round-Robin scheduling).
  • Message Queue Management: In distributed systems, message queues are used to transfer messages between different components of the system. The circular queue is very efficient for managing these queues.

The Enqueue Operation

The enqueue operation in a queue data structure is the process of adding a new element to the end of the queue. This operation is typically performed using a pointer called “rear” or “tail.” First, a check is performed to see if the queue is full. If it is, the operation fails, and the new element is not added. If the queue is not complete, the “rear” pointer indicates the location where the new element should be inserted.
The new element is placed at this location. Then, the “rear” pointer is incremented to point to the next available position for insertion. In circular queues, if the “rear” pointer reaches the end of the array, it wraps around to the beginning. This process ensures that elements are added in the order they arrive, maintaining the FIFO principle.


Different Scenarios for Adding an Element to a List

When adding an element to a list, different scenarios can occur depending on the type of list (array, linked list, etc.) and its current state. In the simplest case, if the list is empty, the new element is added as the first element, and the head pointer or index points to it. If the list is not empty and has space (e.g., in an array), the new element is added to the end, and the tail pointer or index is updated.
In linked lists, if the list is not empty, the new element is added as the last node, and the next pointer of the previous last node is set to the new node. If the list must remain sorted, the new element must be inserted at its correct position. In this case, a suitable search algorithm is used to find the correct position, and the element is inserted there. If the list is complete (as in a fixed-size array), the add operation will fail.
Linked lists typically do not have a space limitation unless the system’s memory is full. In some cases, it may be necessary to increase the size of the list before adding a new element, which usually involves allocating new memory and copying the old elements over.


Algorithm for Adding an Element to a Circular Queue

The algorithm to add an element to a circular queue (Enqueue) is as follows:

  1. Check if the queue is full. This is done using the front and rear pointers. If(rear + 1) % capacity == frontthe queue is full. In this case, the operation fails.
  2. Increment the rear pointer: If the queue is not complete, the rear pointer is incremented. The modulo operator (%) is used to prevent the pointer from going out of the array’s bounds: rear = (rear + 1) % capacity.
  3. The new element is placed at the location indicated by the new rear pointer.
  4. Handle an empty queue case (if needed): If the queue was previously empty and this is the first element being added, the front pointer must also be set to point to this first element. For instance, if it front was -1, it is now set to 0.

It is essential to keep a few key points in mind when working with a circular queue. The capacity is the size of the array used. The front pointer points to the first element, and the rear pointer points to the last element. If the queue is empty, a standard convention is front == rear == -1. If the queue is full, (rear + 1) % capacity == front. This algorithm ensures that elements are added in order, maintaining the FIFO principle.


The Dequeue Operation

The operation of extracting data from a queue, or “Dequeue,” is the process by which the first element in the queue is removed and its value is returned. This operation is based on the FIFO (First-In, First-Out) principle, meaning the element that was added earliest is removed first.
To perform this operation, one must first check if the queue is empty. If it is, the operation fails, and no value is returned. If the queue is not empty, the “front” pointer points to the first element. The value of this element is read, and then it is removed from the queue.
After removal, the “front” pointer is incremented to point to the next element. In circular queues, if the “front” pointer reaches the end of the array, it wraps around to the beginning. This process ensures that elements are dequeued in the order they were enqueued, upholding the FIFO principle.


Implementing a Circular Queue in Python

Now that we have the basic information about the circular queue, it is time to examine how to implement it in Python. The process is as follows:

class CircularQueue:
def __init__(self, capacity):
self.capacity = capacity
self.queue = [None] * capacity
self.front = self.rear = -1

def enqueue(self, item):
if (self.rear + 1) % self.capacity == self.front:
print("Queue is full")
return
elif self.front == -1:
self.front = 0
self.rear = 0
else:
self.rear = (self.rear + 1) % self.capacity
self.queue[self.rear] = item

def dequeue(self):
if self.front == -1:
print("Queue is empty")
return
elif self.front == self.rear:
temp = self.queue[self.front]
self.front = self.rear = -1
return temp
else:
temp = self.queue[self.front]
self.front = (self.front + 1) % self.capacity
return temp

def display(self):
if self.front == -1:
print("Queue is empty")
return
elif self.rear >= self.front:
for i in range(self.front, self.rear + 1):
print(self.queue[i], end=" ")
print()
else:
for i in range(self.front, self.capacity):
print(self.queue[i], end=" ")
for i in range(0, self.rear + 1):
print(self.queue[i], end=" ")
print()

# Example usage
q = CircularQueue(5)
q.enqueue(1)
q.enqueue(2)
q.enqueue(3)
q.enqueue(4)
q.enqueue(5)
q.display() # Output: 1 2 3 4 5
q.dequeue()
q.display() # Output: 2 3 4 5
q.enqueue(6)
q.display() # Output: 2 3 4 5 6

Explanation of the code snippet:

  • __init__(self, capacity): This is the class constructor. It initializes the queue’s capacity and creates the array to store elements. It also initializes the front and rear pointers to -1.
  • enqueue(self, item): This method adds a new item to the end of the queue. It first checks if the queue is full. If so, it prints “Queue is full.” Otherwise, it adds the new item and updates the rear pointer.
  • dequeue(self): This method removes the first element from the queue and returns its value. It first checks if the queue is empty. If so, it prints “Queue is empty.” Otherwise, it removes the first element and updates the front pointer.
  • display(self): This method prints all elements currently in the queue. It first checks if the queue is empty. If so, it prints “Queue is empty.” Otherwise, it prints all elements in order.

Final Analysis

The provided text offers a comprehensive and clear explanation of the circular queue data structure, from its fundamental concept to its practical implementation and applications.

The core strength of the text lies in its detailed breakdown of why a circular queue is necessary. It effectively contrasts it with a standard queue, highlighting the critical advantage of efficient memory utilization. By allowing the front and rear pointers to “wrap around” the array, a circular queue elegantly solves the problem of wasted space that occurs in a simple array-based queue after several dequeue operations.

The explanation of the key operations—Enqueue, Dequeue isFull, and isEmpty—is thorough. The logic behind using the modulo operator (%) to manage the circular movement of the pointers is correctly identified as the central mechanism for its implementation. This makes the concept accessible even to those new to data structures.

Furthermore, the text successfully bridges theory and practice by listing diverse and relevant applications, such as CPU scheduling, network buffering, and process management in operating systems. This contextualizes the data structure, demonstrating its importance in real-world computing scenarios.

Finally, the inclusion of a Python implementation serves as an excellent practical guide. The code is well-commented and structured, allowing a reader to not only understand the theory but also see how it translates into functional code.

In conclusion, the document is a well-rounded educational resource on circular queues. It effectively explains the what, why, and how of this data structure, making it a valuable tool for students and developers alike.
Its main takeaway is that the circular queue is a simple but powerful optimization of the basic queue, crucial for managing resources efficiently in memory-constrained environments.