Learning Objectives
- Define a Queue as a First-In-First-Out (FIFO) data structure.
- Implement a linear queue using an array and Head/Tail pointers.
- Compare and contrast Stacks and Queues.
- Identify the limitation of a simple linear queue (wasted space) and describe the concept of a Circular Queue.
Core Concept: What is a Queue?
A Queue is a linear data structure that strictly follows the First-In-First-Out (FIFO) principle. The first element added to the queue will be the first one to be removed.
Real-World Applications:
- Printer Spooling: Documents sent to a printer are printed in the exact order they were received.
- Ticket Counters: People wait in a line; the person at the front gets served first.
- Customer Service Calls: "Your call is important to us. You are currently number 3 in the queue."
FIFO: First-In-First-Out
Data flows in one direction. It enters from the back and leaves from the front, just like a pipe or a tunnel.
Think About It: How do we build this?
If we use a standard array to represent a queue, people join at the back and leave from the front. How does the computer know where the "front" and "back" are at any given time?
Answer: We need TWO variables (pointers)! One to track the front (Head) for removing items, and one to track the back (Tail) for adding new items.
Interactive Queue Simulator
Click Enqueue to add an item, and Dequeue to remove an item. Watch how the Head and Tail pointers move independently. Pay close attention to what happens when the Tail reaches the end!
The Drifting Problem (False Overflow)
Notice the red striped area? The Tail pointer has reached the maximum index, so the program thinks the queue is full. However, because we dequeued items, there is perfectly good empty space at the front! This wasted space is the major flaw of a simple linear queue.
Linear Queue Implementation Details
Review the code below. Notice how the array size is fixed, and how the is_empty() and is_full() functions rely entirely on the positions of the Head and Tail pointers, not the actual number of items.
class LinearQueue:
def __init__(self, size):
self.max_size = size
self.queue = [None] * size
self.head = 0 # Points to the front (for Dequeue)
self.tail = 0 # Points to the next empty slot at the back (for Enqueue)
def is_empty(self):
# Empty when both pointers point to the same index
return self.head == self.tail
def is_full(self):
# Full when tail reaches the absolute end of the array
# This causes the "Drifting Problem" (False Overflow)
return self.tail == self.max_size
def enqueue(self, item):
if self.is_full():
print("Queue is Full! (False Overflow may occur)")
return
self.queue[self.tail] = item
self.tail += 1 # Move tail pointer to the right
def dequeue(self):
if self.is_empty():
print("Queue is Empty!")
return None
item = self.queue[self.head]
self.queue[self.head] = None # Clear slot (optional, for visualization)
self.head += 1 # Move head pointer to the right
return item
#include <iostream>
using namespace std;
class LinearQueue {
private:
int* queue;
int head, tail, maxSize;
public:
LinearQueue(int size) {
maxSize = size;
queue = new int[maxSize];
head = 0; // Points to the front
tail = 0; // Points to the next empty slot
}
bool isEmpty() {
// Empty when both pointers point to the same index
return head == tail;
}
bool isFull() {
// Full when tail reaches the absolute end of the array
// This causes the "Drifting Problem"
return tail == maxSize;
}
void enqueue(int item) {
if (isFull()) {
cout << "Queue is Full! (False Overflow may occur)\n";
return;
}
queue[tail] = item;
tail++; // Move tail pointer to the right
}
int dequeue() {
if (isEmpty()) {
cout << "Queue is Empty!\n";
return -1;
}
int item = queue[head];
head++; // Move head pointer to the right
return item;
}
};
FAQ: Why do we just move the Head pointer instead of shifting all items forward?
Shifting all items forward every time we Dequeue takes too much time (O(N) time complexity). Moving the Head pointer is instant (O(1) time complexity), which is why we do it, even though it leads to the Drifting problem.
Stacks vs Queues
| Feature | Queue | Stack |
|---|---|---|
| Principle | FIFO | LIFO |
| Insertion | Enqueue (at Tail) | Push (at Top) |
| Deletion | Dequeue (at Head) | Pop (at Top) |
| Pointers | Head & Tail (2) | Top (1) |
Exam Trap Warning
Common Mistake:
Assuming that a linear queue is full ONLY when all slots are physically occupied by data.
The Reality:
Because of the "Drifting" problem, the Tail pointer can reach the end of the array (indicating it's "full" to the computer) even if there are empty slots at the front due to previous Dequeue operations.
Exam Tip:
In exams, carefully trace the Head and Tail pointers. If asked how to solve the wasted space issue, always answer: "Use a Circular Queue".
Moving Forward: How do we fix this?
We know shifting elements is too slow, and leaving empty space is wasteful. What if the end of the array could magically connect back to the empty spaces at the beginning? Let's look at the Circular Queue.
The Solution: Circular Queue
To solve the Drifting Problem, we logically connect the end of the array back to the beginning, creating a circle. When the Tail or Head pointer reaches the end of the array, it wraps around back to index 0.
The Magic Formula: Modulo Arithmetic
Next Index = (Current Index + 1) % MaxSize
The modulo operator (%) gives the remainder of a division. If MaxSize is 5, and Tail is at index 4, the next position is (4 + 1) % 5 = 0. It loops perfectly!
Circular Queue Pointer Movement (Python)
# Enqueue (Moving Tail)
self.tail = (self.tail + 1) % self.max_size
# Dequeue (Moving Head)
self.head = (self.head + 1) % self.max_size