Circular Queue Linked List
Circular Queue using Linked List: A circular queue is a data structure that follows the FIFO (First-In-First-Out) principle, where the elements are inserted at the rear and removed from the front. In a circular queue, the last element points back to the first element, creating a circular structure.
To implement a circular queue using a linked list, we can use a singly linked list where each node holds the element and a reference to the next node. The last node of the linked list will point back to the first node, forming a circular structure.
The circular queue using a linked list can have the following methods:
__init__(self, k)
: Initializes an empty circular queue with a maximum size of k.enqueue(self, value)
: Inserts an element into the circular queue if it is not full.dequeue(self)
: Deletes an element from the circular queue if it is not empty.get_front(self)
: Gets the front element of the circular queue if it is not empty.get_rear(self)
: Gets the last element of the circular queue if it is not empty.is_empty(self)
: Checks if the circular queue is empty.is_full(self)
: Checks if the circular queue is full.
Now, let's provide a complete code implementation of the circular queue using a linked list with proper comments and explain its time and space complexity. We'll also solve a question from LeetCode that involves using a circular queue.
class ListNode:
def __init__(self, value):
self.value = value
self.next = None
class MyCircularQueue:
def __init__(self, k: int):
self.head = None
self.tail = None
self.size = 0
self.max_size = k
def enqueue(self, value: int) -> bool:
if self.is_full():
return False
new_node = ListNode(value)
if self.is_empty():
self.head = new_node
self.tail = new_node
else:
self.tail.next = new_node
self.tail = new_node
self.size += 1
return True
def dequeue(self) -> bool:
if self.is_empty():
return False
if self.head == self.tail:
self.head = None
self.tail = None
else:
self.head = self.head.next
self.size -= 1
return True
def get_front(self) -> int:
if self.is_empty():
return -1
return self.head.value
def get_rear(self) -> int:
if self.is_empty():
return -1
return self.tail.value
def is_empty(self) -> bool:
return self.size == 0
def is_full(self) -> bool:
return self.size == self.max_size
# Example usage:
cq = MyCircularQueue(3)
print(cq.enqueue(1)) # Output: True
print(cq.enqueue(2)) # Output: True
print(cq.enqueue(3)) # Output: True
print(cq.enqueue(4)) # Output: False (queue is full)
print(cq.get_front()) # Output: 1
print(cq.get_rear()) # Output: 3
print(cq.is_empty()) # Output: False
print(cq.is_full()) # Output: True
print(cq.dequeue()) # Output: True
print(cq.get_front()) # Output: 2
In this implementation, we have a ListNode
class representing a node in the linked list. The MyCircularQueue
class has various methods:
__init__
: Initializes an empty circular queue with a maximum size of k.enqueue
: Inserts an element into the circular queue if it is not full.dequeue
: Deletes an element from the circular queue if it is not empty.get_front
: Gets the front element of the circular queue if it is not empty.get_rear
: Gets the last element of the circular queue if it is not empty.is_empty
: Checks if the circular queue is empty.is_full
: Checks if the circular queue is full.
Time and Space Complexity Analysis:
- Enqueue, Dequeue, GetFront, IsEmpty, and IsFull methods all have a time complexity of O(1) since they involve constant-time operations like assigning values, updating references, and checking conditions.
- The space complexity of the circular queue is O(k), where k is the maximum size of the circular queue. We store k elements in the linked list.
Let's solve a question from LeetCode that uses the circular queue implementation.
Question: "Design Circular Deque" Problem Description: Design your implementation of the circular double-ended queue (deque).
Implement the MyCircularDeque
class:
MyCircularDeque(k)
: Initializes the deque with a maximum size of k.insertFront()
: Adds an item at the front of the deque if it is not full.insertLast()
: Adds an item at the rear of the deque if it is not full.deleteFront()
: Deletes an item from the front of the deque if it is not empty.deleteLast()
: Deletes an item from the rear of the deque if it is not empty.getFront()
: Returns the front item from the deque if it is not empty.getRear()
: Returns the last item from the deque if it is not empty.isEmpty()
: Checks whether the circular deque is empty or not.isFull()
: Checks whether the circular deque is full or not.
Example:
# Create a MyCircularDeque object with maximum size 3
circularDeque = MyCircularDeque(3)
# Insert new elements at the front and rear
print(circularDeque.insertLast(1)) # Output: True
print(circularDeque.insertLast(2)) # Output: True
print(circularDeque.insertFront(3)) # Output: True
print(circularDeque.insertFront(4)) # Output: False (deque is full)
# Get the front and rear elements
print(circularDeque.getFront()) # Output: 3
print(circularDeque.getRear()) # Output: 2
# Delete elements from the front and rear
print(circularDeque.deleteFront()) # Output: True
print(circularDeque.deleteLast()) # Output: True
# Check if the deque is empty or full
print(circularDeque.isEmpty()) # Output: False
print(circularDeque.isFull()) # Output: False
In this example, we use the MyCircularDeque
class to create a circular double-ended queue with a maximum size of 3. We insert elements at the front and rear, get the front and rear elements, delete elements from the front and rear, and check if the deque is empty or full.
The time and space complexity for each method in the MyCircularDeque
class remains the same as explained earlier.