0% found this document useful (0 votes)
26 views17 pages

QUEUES

The document provides a comprehensive overview of queues, a linear data structure that operates on the First In First Out (FIFO) principle. It covers various types of queues including linear, circular, and priority queues, along with their operations such as enqueue and dequeue, and implementations using arrays and linked lists. Additionally, it discusses applications of queues in real-world scenarios and algorithms, such as checking for palindromes and evaluating infix expressions.

Uploaded by

sprincetan
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
26 views17 pages

QUEUES

The document provides a comprehensive overview of queues, a linear data structure that operates on the First In First Out (FIFO) principle. It covers various types of queues including linear, circular, and priority queues, along with their operations such as enqueue and dequeue, and implementations using arrays and linked lists. Additionally, it discusses applications of queues in real-world scenarios and algorithms, such as checking for palindromes and evaluating infix expressions.

Uploaded by

sprincetan
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 17

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.

You might also like