Chapter 7: Queues
Introduction
A queue is a linear data structure that follows the First-In-First-Out (FIFO) principle: the first element added is the first one removed. Think of a line at a ticket counter—people join at the back and leave from the front. This fundamental abstraction models many real-world systems and computing scenarios.
Why This Matters
Queues are everywhere in computing:
- Task scheduling: Operating systems manage processes
- Breadth-First Search: Graph and tree traversal
- Request handling: Web servers process requests in order
- Print spooler: Print jobs queued for execution
- Buffering: Data streams in networking
- Customer service: Call centers, support tickets
Understanding queues teaches:
- FIFO ordering and fairness
- Different implementation trade-offs
- Circular buffer optimization
- Priority-based ordering variations
How to Study This Chapter
- Visualize operations - Draw queue state after enqueue/dequeue
- Implement multiple types - Simple, circular, priority queues
- Compare implementations - Array vs linked list trade-offs
- Solve problems - Practice with BFS, scheduling scenarios
- Understand applications - See where queues appear in systems
Queue Operations
Core Operations
- enqueue(x) - Add element to rear - O(1)
- dequeue() - Remove and return front element - O(1)
- peek()/front() - View front element without removing - O(1)
- isEmpty() - Check if queue is empty - O(1)
- size() - Get number of elements - O(1)
Visualization
Enqueue operations: Dequeue operations:
enqueue(10): [10] dequeue(): [10, 20, 30]
enqueue(20): [10, 20] [20, 30] (returns 10)
enqueue(30): [10, 20, 30] [30] (returns 20)
FRONT→ →REAR FRONT→
Simple Queue Implementation
C Implementation (Array-Based)
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_SIZE 100
typedef struct {
int items[MAX_SIZE];
int front;
int rear;
} Queue;
void initQueue(Queue* q) {
q->front = -1;
q->rear = -1;
}
bool isEmpty(Queue* q) {
return q->front == -1;
}
bool isFull(Queue* q) {
return q->rear == MAX_SIZE - 1;
}
void enqueue(Queue* q, int value) {
if (isFull(q)) {
printf("Queue Overflow\n");
return;
}
if (isEmpty(q)) {
q->front = 0;
}
q->items[++(q->rear)] = value;
}
int dequeue(Queue* q) {
if (isEmpty(q)) {
printf("Queue Underflow\n");
return -1;
}
int value = q->items[q->front];
if (q->front == q->rear) {
// Last element
q->front = q->rear = -1;
} else {
q->front++;
}
return value;
}
int peek(Queue* q) {
if (isEmpty(q)) {
printf("Queue is empty\n");
return -1;
}
return q->items[q->front];
}
int size(Queue* q) {
if (isEmpty(q)) return 0;
return q->rear - q->front + 1;
}
void display(Queue* q) {
if (isEmpty(q)) {
printf("Queue is empty\n");
return;
}
printf("Queue: ");
for (int i = q->front; i <= q->rear; i++) {
printf("%d ", q->items[i]);
}
printf("\n");
}
int main() {
Queue q;
initQueue(&q);
enqueue(&q, 10);
enqueue(&q, 20);
enqueue(&q, 30);
display(&q); // Queue: 10 20 30
printf("Dequeued: %d\n", dequeue(&q)); // 10
printf("Front: %d\n", peek(&q)); // 20
display(&q); // Queue: 20 30
return 0;
}
C++ Implementation
#include <iostream>
#include <deque>
using namespace std;
class Queue {
private:
deque<int> items;
public:
void enqueue(int value) {
items.push_back(value);
}
int dequeue() {
if (isEmpty()) {
throw runtime_error("Queue Underflow");
}
int value = items.front();
items.pop_front();
return value;
}
int peek() {
if (isEmpty()) {
throw runtime_error("Queue is empty");
}
return items.front();
}
bool isEmpty() {
return items.empty();
}
int size() {
return items.size();
}
void display() {
if (isEmpty()) {
cout << "Queue is empty" << endl;
return;
}
cout << "Queue: ";
for (int item : items) {
cout << item << " ";
}
cout << endl;
}
};
int main() {
Queue q;
q.enqueue(10);
q.enqueue(20);
q.enqueue(30);
q.display(); // Queue: 10 20 30
cout << "Dequeued: " << q.dequeue() << endl; // 10
cout << "Front: " << q.peek() << endl; // 20
q.display(); // Queue: 20 30
return 0;
}
Java Implementation
import java.util.LinkedList;
class Queue {
private LinkedList<Integer> items;
public Queue() {
items = new LinkedList<>();
}
public void enqueue(int value) {
items.addLast(value);
}
public int dequeue() {
if (isEmpty()) {
throw new IllegalStateException("Queue Underflow");
}
return items.removeFirst();
}
public int peek() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
return items.getFirst();
}
public boolean isEmpty() {
return items.isEmpty();
}
public int size() {
return items.size();
}
public void display() {
if (isEmpty()) {
System.out.println("Queue is empty");
return;
}
System.out.print("Queue: ");
for (int item : items) {
System.out.print(item + " ");
}
System.out.println();
}
public static void main(String[] args) {
Queue q = new Queue();
q.enqueue(10);
q.enqueue(20);
q.enqueue(30);
q.display(); // Queue: 10 20 30
System.out.println("Dequeued: " + q.dequeue()); // 10
System.out.println("Front: " + q.peek()); // 20
q.display(); // Queue: 20 30
}
}
Circular Queue
Problem with simple queue: After dequeuing, front moves forward, wasting space at the beginning.
Solution: Circular queue treats array as circular—when rear reaches end, wrap around to beginning.
Visualization
Initial (size=5):
[_ _ _ _ _]
F,R
After enqueue(10,20,30):
[10 20 30 _ _]
F R
After dequeue() twice:
[_ _ 30 _ _]
F R
After enqueue(40,50,60):
[60 _ 30 40 50]
R F
Wraps around! Front at index 2, rear at index 0.
C Implementation
#include <stdio.h>
#include <stdbool.h>
#define MAX_SIZE 5
typedef struct {
int items[MAX_SIZE];
int front;
int rear;
int count;
} CircularQueue;
void initQueue(CircularQueue* q) {
q->front = 0;
q->rear = -1;
q->count = 0;
}
bool isEmpty(CircularQueue* q) {
return q->count == 0;
}
bool isFull(CircularQueue* q) {
return q->count == MAX_SIZE;
}
void enqueue(CircularQueue* q, int value) {
if (isFull(q)) {
printf("Queue Overflow\n");
return;
}
q->rear = (q->rear + 1) % MAX_SIZE;
q->items[q->rear] = value;
q->count++;
}
int dequeue(CircularQueue* q) {
if (isEmpty(q)) {
printf("Queue Underflow\n");
return -1;
}
int value = q->items[q->front];
q->front = (q->front + 1) % MAX_SIZE;
q->count--;
return value;
}
int peek(CircularQueue* q) {
if (isEmpty(q)) {
printf("Queue is empty\n");
return -1;
}
return q->items[q->front];
}
void display(CircularQueue* q) {
if (isEmpty(q)) {
printf("Queue is empty\n");
return;
}
printf("Queue: ");
int i = q->front;
for (int c = 0; c < q->count; c++) {
printf("%d ", q->items[i]);
i = (i + 1) % MAX_SIZE;
}
printf("\n");
}
int main() {
CircularQueue q;
initQueue(&q);
enqueue(&q, 10);
enqueue(&q, 20);
enqueue(&q, 30);
enqueue(&q, 40);
enqueue(&q, 50);
display(&q); // Queue: 10 20 30 40 50
dequeue(&q);
dequeue(&q);
display(&q); // Queue: 30 40 50
enqueue(&q, 60);
enqueue(&q, 70);
display(&q); // Queue: 30 40 50 60 70
return 0;
}
Linked List-Based Queue
C Implementation
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
typedef struct {
Node* front;
Node* rear;
int count;
} Queue;
void initQueue(Queue* q) {
q->front = NULL;
q->rear = NULL;
q->count = 0;
}
bool isEmpty(Queue* q) {
return q->front == NULL;
}
void enqueue(Queue* q, int value) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = value;
newNode->next = NULL;
if (isEmpty(q)) {
q->front = q->rear = newNode;
} else {
q->rear->next = newNode;
q->rear = newNode;
}
q->count++;
}
int dequeue(Queue* q) {
if (isEmpty(q)) {
printf("Queue Underflow\n");
return -1;
}
Node* temp = q->front;
int value = temp->data;
q->front = q->front->next;
if (q->front == NULL) {
q->rear = NULL;
}
free(temp);
q->count--;
return value;
}
int peek(Queue* q) {
if (isEmpty(q)) {
printf("Queue is empty\n");
return -1;
}
return q->front->data;
}
int size(Queue* q) {
return q->count;
}
void display(Queue* q) {
if (isEmpty(q)) {
printf("Queue is empty\n");
return;
}
printf("Queue: ");
Node* current = q->front;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
int main() {
Queue q;
initQueue(&q);
enqueue(&q, 10);
enqueue(&q, 20);
enqueue(&q, 30);
display(&q);
printf("Dequeued: %d\n", dequeue(&q));
printf("Front: %d\n", peek(&q));
display(&q);
return 0;
}
Priority Queue
Elements dequeued based on priority, not insertion order.
Simple Array-Based Priority Queue
C Implementation:
#include <stdio.h>
#include <stdbool.h>
#define MAX_SIZE 100
typedef struct {
int value;
int priority;
} Element;
typedef struct {
Element items[MAX_SIZE];
int size;
} PriorityQueue;
void initPQ(PriorityQueue* pq) {
pq->size = 0;
}
bool isEmpty(PriorityQueue* pq) {
return pq->size == 0;
}
bool isFull(PriorityQueue* pq) {
return pq->size == MAX_SIZE;
}
void enqueue(PriorityQueue* pq, int value, int priority) {
if (isFull(pq)) {
printf("Priority Queue Overflow\n");
return;
}
int i = pq->size - 1;
// Shift elements with lower priority
while (i >= 0 && pq->items[i].priority < priority) {
pq->items[i + 1] = pq->items[i];
i--;
}
pq->items[i + 1].value = value;
pq->items[i + 1].priority = priority;
pq->size++;
}
int dequeue(PriorityQueue* pq) {
if (isEmpty(pq)) {
printf("Priority Queue Underflow\n");
return -1;
}
int value = pq->items[pq->size - 1].value;
pq->size--;
return value;
}
int peek(PriorityQueue* pq) {
if (isEmpty(pq)) {
printf("Priority Queue is empty\n");
return -1;
}
return pq->items[pq->size - 1].value;
}
void display(PriorityQueue* pq) {
if (isEmpty(pq)) {
printf("Priority Queue is empty\n");
return;
}
printf("Priority Queue (value:priority): ");
for (int i = pq->size - 1; i >= 0; i--) {
printf("(%d:%d) ", pq->items[i].value, pq->items[i].priority);
}
printf("\n");
}
int main() {
PriorityQueue pq;
initPQ(&pq);
enqueue(&pq, 10, 2);
enqueue(&pq, 20, 5);
enqueue(&pq, 30, 1);
enqueue(&pq, 40, 3);
display(&pq); // (20:5) (40:3) (10:2) (30:1)
printf("Dequeued: %d\n", dequeue(&pq)); // 20 (highest priority)
printf("Peek: %d\n", peek(&pq)); // 40
display(&pq);
return 0;
}
Queue Applications
1. Breadth-First Search (BFS)
Graph Traversal:
#include <stdio.h>
#include <stdbool.h>
#define V 5
void BFS(int graph[V][V], int start) {
bool visited[V] = {false};
int queue[V];
int front = 0, rear = 0;
visited[start] = true;
queue[rear++] = start;
printf("BFS traversal: ");
while (front < rear) {
int node = queue[front++];
printf("%d ", node);
for (int i = 0; i < V; i++) {
if (graph[node][i] == 1 && !visited[i]) {
visited[i] = true;
queue[rear++] = i;
}
}
}
printf("\n");
}
int main() {
int graph[V][V] = {
{0, 1, 1, 0, 0},
{1, 0, 0, 1, 1},
{1, 0, 0, 0, 1},
{0, 1, 0, 0, 0},
{0, 1, 1, 0, 0}
};
BFS(graph, 0);
return 0;
}
2. CPU Scheduling (FCFS)
typedef struct {
int processID;
int burstTime;
} Process;
void FCFS_Scheduling(Process processes[], int n) {
int waitTime[n];
int turnAroundTime[n];
waitTime[0] = 0;
for (int i = 1; i < n; i++) {
waitTime[i] = processes[i - 1].burstTime + waitTime[i - 1];
}
for (int i = 0; i < n; i++) {
turnAroundTime[i] = processes[i].burstTime + waitTime[i];
printf("P%d: Wait=%d, TAT=%d\n",
processes[i].processID, waitTime[i], turnAroundTime[i]);
}
}
3. Level Order Tree Traversal
typedef struct TreeNode {
int data;
struct TreeNode* left;
struct TreeNode* right;
} TreeNode;
void levelOrder(TreeNode* root) {
if (root == NULL) return;
TreeNode* queue[100];
int front = 0, rear = 0;
queue[rear++] = root;
while (front < rear) {
TreeNode* node = queue[front++];
printf("%d ", node->data);
if (node->left) queue[rear++] = node->left;
if (node->right) queue[rear++] = node->right;
}
}
Deque (Double-Ended Queue)
Operations at both ends.
C Implementation:
typedef struct {
int items[MAX_SIZE];
int front;
int rear;
int count;
} Deque;
void enqueueFront(Deque* dq, int value) {
if (dq->count == MAX_SIZE) {
printf("Deque Full\n");
return;
}
dq->front = (dq->front - 1 + MAX_SIZE) % MAX_SIZE;
dq->items[dq->front] = value;
dq->count++;
}
void enqueueRear(Deque* dq, int value) {
if (dq->count == MAX_SIZE) {
printf("Deque Full\n");
return;
}
dq->rear = (dq->rear + 1) % MAX_SIZE;
dq->items[dq->rear] = value;
dq->count++;
}
int dequeueFront(Deque* dq) {
if (dq->count == 0) {
printf("Deque Empty\n");
return -1;
}
int value = dq->items[dq->front];
dq->front = (dq->front + 1) % MAX_SIZE;
dq->count--;
return value;
}
int dequeueRear(Deque* dq) {
if (dq->count == 0) {
printf("Deque Empty\n");
return -1;
}
int value = dq->items[dq->rear];
dq->rear = (dq->rear - 1 + MAX_SIZE) % MAX_SIZE;
dq->count--;
return value;
}
Complexity Summary
| Operation | Array Queue | Circular Queue | Linked List Queue | Priority Queue (Array) |
|---|---|---|---|---|
| Enqueue | O(1) | O(1) | O(1) | O(n) |
| Dequeue | O(1) | O(1) | O(1) | O(1) |
| Peek | O(1) | O(1) | O(1) | O(1) |
| Space | Wastes space | Efficient | Dynamic | O(n) |
Common Mistakes
- Not handling wrap-around - Forgetting modulo in circular queue
- Memory leaks - Not freeing nodes in linked list queue
- Wrong empty condition - Incorrect check for empty queue
- Front/rear confusion - Mixing up front and rear pointers
- Not updating count - Forgetting to track size
- Overflow handling - Not checking full condition
- Priority queue insertion - Wrong position for new element
Debugging Tips
- Visualize queue state - Draw front, rear, elements
- Check boundary conditions - Empty, full, single element
- Test wrap-around - In circular queue
- Verify FIFO order - First in should be first out
- Print queue state - After each operation
- Test with small capacity - Easy to trace
- Use assertions - Verify invariants
Mini Exercises
- Implement queue using two stacks
- Reverse first k elements of queue
- Generate binary numbers from 1 to n using queue
- Implement LRU cache using queue
- Find first non-repeating character in stream
- Sliding window maximum using deque
- Interleave first half with second half
- Reverse a queue using recursion
- Sort a queue without extra space
- Implement circular tour (petrol pump problem)
Review Questions
- Why is circular queue more efficient than simple queue?
- When should you use a linked list queue vs array queue?
- How does BFS use a queue?
- What's the difference between a queue and a deque?
- How would you implement a priority queue efficiently?
Reference Checklist
By the end of this chapter, you should be able to:
- Implement queue using arrays and linked lists
- Understand FIFO principle
- Implement circular queue
- Build priority queue
- Apply queues to BFS
- Solve scheduling problems
- Implement deque
- Analyze time and space complexity
Next Steps
Now that you understand queues, Chapter 8 explores hashing—a technique that enables near-constant-time search, insert, and delete operations. You'll learn hash functions, collision resolution, and applications like hash tables and hash maps.
Key Takeaway: Queues implement FIFO ordering with O(1) operations. Circular queues optimize space usage. Priority queues order by importance. Queues are essential for BFS, scheduling, and buffering systems.