QUEUES - Complete Study Notes
4.7.1 Concept of a Queue
A queue is a linear data structure that follows the First In First Out (FIFO) principle. Elements
are added at one end (rear/back) and removed from the other end (front). Think of it like a line of
people waiting - the first person to join the line is the first person to be served.
Key Characteristics:
FIFO ordering: First element inserted is the first to be removed
Two main operations: Enqueue (insert) and Dequeue (remove)
Two pointers: Front (points to first element) and Rear (points to last element)
Linear structure: Elements are arranged in a sequential manner
Real-world Examples:
People waiting in line at a bank
Print job queues in operating systems
CPU scheduling in operating systems
Breadth-First Search (BFS) in graphs
4.7.2 Checking for Empty or Full Queue
Empty Queue Check:
A queue is empty when:
front == -1 (initial state)
OR front > rear (after all elements are dequeued)
Full Queue Check:
A queue is full when:
rear == MAX_SIZE - 1 (for linear arrays)
(rear + 1) % MAX_SIZE == front (for circular arrays)
#include <iostream>
#define MAX_SIZE 100
class Queue {
private:
int arr[MAX_SIZE];
int front, rear;
public:
Queue() {
front = -1;
rear = -1;
}
bool isEmpty() {
return (front == -1 || front > rear);
}
bool isFull() {
return (rear == MAX_SIZE - 1);
}
void display() {
if (isEmpty()) {
std::cout << "Queue is empty\n";
return;
}
std::cout << "Queue elements: ";
for (int i = front; i <= rear; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
};
4.7.3 Queue Operations
Enqueue Operation
Enqueue adds an element to the rear of the queue.
Algorithm:
1. Check if queue is full
2. If full, display overflow message
3. If empty, set front = 0
4. Increment rear
5. Insert element at rear position
void enqueue(int value) {
if (isFull()) {
std::cout << "Queue Overflow! Cannot insert " << value << std::endl;
return;
}
if (isEmpty()) {
front = 0; // Initialize front for first element
}
rear++;
arr[rear] = value;
std::cout << "Enqueued: " << value << std::endl;
}
Dequeue Operation
Dequeue removes an element from the front of the queue.
Algorithm:
1. Check if queue is empty
2. If empty, display underflow message
3. Store front element
4. Increment front
5. If queue becomes empty, reset front and rear to -1
6. Return stored element
int dequeue() {
if (isEmpty()) {
std::cout << "Queue Underflow! Cannot dequeue from empty queue" <<
std::endl;
return -1;
}
int dequeuedValue = arr[front];
// If queue becomes empty after dequeue
if (front == rear) {
front = rear = -1;
} else {
front++;
}
std::cout << "Dequeued: " << dequeuedValue << std::endl;
return dequeuedValue;
}
Complete Linear Queue Implementation:
#include <iostream>
#define MAX_SIZE 100
class LinearQueue {
private:
int arr[MAX_SIZE];
int front, rear;
public:
LinearQueue() : front(-1), rear(-1) {}
bool isEmpty() {
return (front == -1 || front > rear);
}
bool isFull() {
return (rear == MAX_SIZE - 1);
}
void enqueue(int value) {
if (isFull()) {
std::cout << "Queue Overflow!\n";
return;
}
if (isEmpty()) front = 0;
arr[++rear] = value;
std::cout << "Enqueued: " << value << std::endl;
}
int dequeue() {
if (isEmpty()) {
std::cout << "Queue Underflow!\n";
return -1;
}
int value = arr[front];
if (front == rear) {
front = rear = -1;
} else {
front++;
}
return value;
}
int peek() {
if (isEmpty()) {
std::cout << "Queue is empty!\n";
return -1;
}
return arr[front];
}
void display() {
if (isEmpty()) {
std::cout << "Queue is empty\n";
return;
}
std::cout << "Queue: ";
for (int i = front; i <= rear; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
};
4.7.5 Special Types of Queues
Circular Queues
A circular queue is a queue where the last position is connected to the first position, forming a
circle. This overcomes the limitation of linear queues where space cannot be reused.
Advantages:
Efficient memory utilization
No memory wastage
Fixed size with better space management
Key Differences from Linear Queue:
Uses modulo arithmetic for index calculation
rear = (rear + 1) % MAX_SIZE
front = (front + 1) % MAX_SIZE
#include <iostream>
#define MAX_SIZE 5
class CircularQueue {
private:
int arr[MAX_SIZE];
int front, rear;
int count; // To track number of elements
public:
CircularQueue() : front(0), rear(-1), count(0) {}
bool isEmpty() {
return (count == 0);
}
bool isFull() {
return (count == MAX_SIZE);
}
void enqueue(int value) {
if (isFull()) {
std::cout << "Circular Queue is full!\n";
return;
}
rear = (rear + 1) % MAX_SIZE;
arr[rear] = value;
count++;
std::cout << "Enqueued: " << value << std::endl;
}
int dequeue() {
if (isEmpty()) {
std::cout << "Circular Queue is empty!\n";
return -1;
}
int value = arr[front];
front = (front + 1) % MAX_SIZE;
count--;
return value;
}
void display() {
if (isEmpty()) {
std::cout << "Circular Queue is empty\n";
return;
}
std::cout << "Circular Queue: ";
int i = front;
for (int j = 0; j < count; j++) {
std::cout << arr[i] << " ";
i = (i + 1) % MAX_SIZE;
}
std::cout << std::endl;
}
int size() {
return count;
}
};
Priority Queues
A priority queue is a queue where each element has a priority associated with it. Elements with
higher priority are dequeued before elements with lower priority.
Types:
Max Priority Queue: Higher priority value = higher priority
Min Priority Queue: Lower priority value = higher priority
Implementation Methods:
1. Array-based (simple but inefficient)
2. Heap-based (efficient)
3. Linked list-based
#include <iostream>
#include <vector>
#include <algorithm>
struct PriorityElement {
int data;
int priority;
PriorityElement(int d, int p) : data(d), priority(p) {}
// For max priority queue (higher priority value = higher priority)
bool operator<(const PriorityElement& other) const {
return priority < other.priority;
}
};
class PriorityQueue {
private:
std::vector<PriorityElement> pq;
public:
void enqueue(int data, int priority) {
pq.push_back(PriorityElement(data, priority));
std::push_heap(pq.begin(), pq.end());
std::cout << "Enqueued: " << data << " with priority " << priority <<
std::endl;
}
int dequeue() {
if (pq.empty()) {
std::cout << "Priority Queue is empty!\n";
return -1;
}
std::pop_heap(pq.begin(), pq.end());
PriorityElement top = pq.back();
pq.pop_back();
std::cout << "Dequeued: " << top.data << " (priority: " <<
top.priority << ")" << std::endl;
return top.data;
}
bool empty() {
return pq.empty();
}
void display() {
if (pq.empty()) {
std::cout << "Priority Queue is empty\n";
return;
}
std::cout << "Priority Queue (data:priority): ";
for (const auto& elem : pq) {
std::cout << elem.data << ":" << elem.priority << " ";
}
std::cout << std::endl;
}
};
4.7.7 Implementation of Queues
Array-based Implementation
Advantages:
Simple to implement
Direct access to elements
Memory efficient for known size
Disadvantages:
Fixed size
Memory wastage in linear queues
Insertion/deletion can be expensive
template<typename T>
class ArrayQueue {
private:
T* arr;
int front, rear, capacity, size;
public:
ArrayQueue(int cap) : capacity(cap), front(0), rear(-1), size(0) {
arr = new T[capacity];
}
~ArrayQueue() {
delete[] arr;
}
bool isEmpty() { return size == 0; }
bool isFull() { return size == capacity; }
void enqueue(T value) {
if (isFull()) {
std::cout << "Queue is full!\n";
return;
}
rear = (rear + 1) % capacity;
arr[rear] = value;
size++;
}
T dequeue() {
if (isEmpty()) {
std::cout << "Queue is empty!\n";
return T();
}
T value = arr[front];
front = (front + 1) % capacity;
size--;
return value;
}
T peek() {
if (isEmpty()) {
std::cout << "Queue is empty!\n";
return T();
}
return arr[front];
}
int getSize() { return size; }
};
Linked List Implementation
Advantages:
Dynamic size
No memory wastage
Efficient insertion/deletion
Disadvantages:
Extra memory for pointers
No direct access to elements
template<typename T>
class LinkedQueue {
private:
struct Node {
T data;
Node* next;
Node(T value) : data(value), next(nullptr) {}
};
Node* front;
Node* rear;
int size;
public:
LinkedQueue() : front(nullptr), rear(nullptr), size(0) {}
~LinkedQueue() {
while (!isEmpty()) {
dequeue();
}
}
bool isEmpty() {
return front == nullptr;
}
void enqueue(T value) {
Node* newNode = new Node(value);
if (isEmpty()) {
front = rear = newNode;
} else {
rear->next = newNode;
rear = newNode;
}
size++;
}
T dequeue() {
if (isEmpty()) {
std::cout << "Queue is empty!\n";
return T();
}
Node* temp = front;
T value = front->data;
front = front->next;
if (front == nullptr) {
rear = nullptr;
}
delete temp;
size--;
return value;
}
T peek() {
if (isEmpty()) {
std::cout << "Queue is empty!\n";
return T();
}
return front->data;
}
int getSize() {
return size;
}
void display() {
if (isEmpty()) {
std::cout << "Queue is empty\n";
return;
}
Node* current = front;
std::cout << "Queue: ";
while (current != nullptr) {
std::cout << current->data << " ";
current = current->next;
}
std::cout << std::endl;
}
};
4.7.8 Applications of Queues
1. Check for Palindromes
A palindrome reads the same forwards and backwards. Using a queue and stack together can
help check this efficiently.
#include <iostream>
#include <queue>
#include <stack>
#include <string>
#include <cctype>
bool isPalindrome(std::string str) {
std::queue<char> q;
std::stack<char> s;
// Convert to lowercase and add only alphanumeric characters
for (char c : str) {
if (std::isalnum(c)) {
char lowerC = std::tolower(c);
q.push(lowerC);
s.push(lowerC);
}
}
// Compare characters from queue (front) and stack (top)
while (!q.empty() && !s.empty()) {
if (q.front() != s.top()) {
return false;
}
q.pop();
s.pop();
}
return true;
}
void testPalindrome() {
std::string test1 = "racecar";
std::string test2 = "A man a plan a canal Panama";
std::string test3 = "hello";
std::cout << "\"" << test1 << "\" is " << (isPalindrome(test1) ? "" :
"not ") << "a palindrome\n";
std::cout << "\"" << test2 << "\" is " << (isPalindrome(test2) ? "" :
"not ") << "a palindrome\n";
std::cout << "\"" << test3 << "\" is " << (isPalindrome(test3) ? "" :
"not ") << "a palindrome\n";
}
2. Evaluate Infix Expressions
Queues are used in converting infix expressions to postfix and then evaluating them.
#include <iostream>
#include <queue>
#include <stack>
#include <string>
#include <sstream>
int precedence(char op) {
switch (op) {
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
case '^':
return 3;
default:
return 0;
}
}
bool isOperator(char c) {
return c == '+' || c == '-' || c == '*' || c == '/' || c == '^';
}
std::queue<std::string> infixToPostfix(const std::string& infix) {
std::queue<std::string> output;
std::stack<char> operators;
for (int i = 0; i < infix.length(); i++) {
char c = infix[i];
if (c == ' ') continue;
if (std::isdigit(c)) {
std::string num;
while (i < infix.length() && std::isdigit(infix[i])) {
num += infix[i];
i++;
}
i--; // Adjust for loop increment
output.push(num);
}
else if (c == '(') {
operators.push(c);
}
else if (c == ')') {
while (!operators.empty() && operators.top() != '(') {
output.push(std::string(1, operators.top()));
operators.pop();
}
if (!operators.empty()) operators.pop(); // Remove '('
}
else if (isOperator(c)) {
while (!operators.empty() && operators.top() != '(' &&
precedence(operators.top()) >= precedence(c)) {
output.push(std::string(1, operators.top()));
operators.pop();
}
operators.push(c);
}
}
while (!operators.empty()) {
output.push(std::string(1, operators.top()));
operators.pop();
}
return output;
}
int evaluatePostfix(std::queue<std::string> postfix) {
std::stack<int> operands;
while (!postfix.empty()) {
std::string token = postfix.front();
postfix.pop();
if (token.length() == 1 && isOperator(token[0])) {
int b = operands.top(); operands.pop();
int a = operands.top(); operands.pop();
switch (token[0]) {
case '+': operands.push(a + b); break;
case '-': operands.push(a - b); break;
case '*': operands.push(a * b); break;
case '/': operands.push(a / b); break;
case '^': operands.push(std::pow(a, b)); break;
}
} else {
operands.push(std::stoi(token));
}
}
return operands.top();
}
void testInfixEvaluation() {
std::string infix = "2 + 3 * 4";
std::cout << "Infix: " << infix << std::endl;
std::queue<std::string> postfix = infixToPostfix(infix);
std::cout << "Postfix: ";
std::queue<std::string> temp = postfix;
while (!temp.empty()) {
std::cout << temp.front() << " ";
temp.pop();
}
std::cout << std::endl;
int result = evaluatePostfix(postfix);
std::cout << "Result: " << result << std::endl;
}
3. Build a Queue from Two Stacks
This implementation shows how to create a queue using two stacks, demonstrating the
relationship between these data structures.
#include <iostream>
#include <stack>
class QueueFromStacks {
private:
std::stack<int> stack1; // For enqueue operations
std::stack<int> stack2; // For dequeue operations
public:
void enqueue(int value) {
stack1.push(value);
std::cout << "Enqueued: " << value << std::endl;
}
int dequeue() {
if (stack2.empty()) {
// Transfer all elements from stack1 to stack2
while (!stack1.empty()) {
stack2.push(stack1.top());
stack1.pop();
}
}
if (stack2.empty()) {
std::cout << "Queue is empty!\n";
return -1;
}
int value = stack2.top();
stack2.pop();
return value;
}
bool empty() {
return stack1.empty() && stack2.empty();
}
int front() {
if (stack2.empty()) {
while (!stack1.empty()) {
stack2.push(stack1.top());
stack1.pop();
}
}
if (stack2.empty()) {
std::cout << "Queue is empty!\n";
return -1;
}
return stack2.top();
}
void display() {
if (empty()) {
std::cout << "Queue is empty\n";
return;
}
std::cout << "Queue contents (front to rear): ";
// Temporarily store stack2 contents
std::stack<int> tempStack;
while (!stack2.empty()) {
tempStack.push(stack2.top());
stack2.pop();
}
// Move stack1 to stack2 (reversed order)
while (!stack1.empty()) {
stack2.push(stack1.top());
stack1.pop();
}
// Display stack2 contents (these are in correct order)
while (!stack2.empty()) {
std::cout << stack2.top() << " ";
stack1.push(stack2.top()); // Restore to stack1
stack2.pop();
}
// Restore stack2 contents
while (!tempStack.empty()) {
std::cout << tempStack.top() << " ";
stack2.push(tempStack.top());
tempStack.pop();
}
std::cout << std::endl;
}
};
void testQueueFromStacks() {
QueueFromStacks queue;
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
queue.display();
std::cout << "Dequeued: " << queue.dequeue() << std::endl;
std::cout << "Front: " << queue.front() << std::endl;
queue.display();
queue.enqueue(4);
queue.enqueue(5);
queue.display();
std::cout << "Dequeued: " << queue.dequeue() << std::endl;
std::cout << "Dequeued: " << queue.dequeue() << std::endl;
queue.display();
}
Main Function to Test All Implementations
int main() {
std::cout << "=== Queue Operations Demo ===\n";
// Test Linear Queue
std::cout << "\n1. Linear Queue Test:\n";
LinearQueue lq;
lq.enqueue(10);
lq.enqueue(20);
lq.enqueue(30);
lq.display();
std::cout << "Dequeued: " << lq.dequeue() << std::endl;
lq.display();
// Test Circular Queue
std::cout << "\n2. Circular Queue Test:\n";
CircularQueue cq;
cq.enqueue(1);
cq.enqueue(2);
cq.enqueue(3);
cq.display();
std::cout << "Dequeued: " << cq.dequeue() << std::endl;
cq.enqueue(4);
cq.display();
// Test Priority Queue
std::cout << "\n3. Priority Queue Test:\n";
PriorityQueue pq;
pq.enqueue(10, 1);
pq.enqueue(20, 3);
pq.enqueue(30, 2);
pq.display();
pq.dequeue();
// Test Applications
std::cout << "\n4. Applications:\n";
std::cout << "\nPalindrome Check:\n";
testPalindrome();
std::cout << "\nInfix Expression Evaluation:\n";
testInfixEvaluation();
std::cout << "\nQueue from Two Stacks:\n";
testQueueFromStacks();
return 0;
}
This comprehensive guide covers all aspects of queues including basic operations, different
types, implementations, and practical applications with complete C++ code examples.