DS Unit-4
DS Unit-4
Queues: Introduction to queues: properties and operations, implementing queues using arrays and linked
lists, Applications of queues in breadth-first search, scheduling, etc.
Deques: Introduction to deques (double-ended queues), Operations on deques and their applications.
In data structures, a queue is a linear data structure that follows the First-In-First-Out (FIFO) principle,
meaning that the element added first is the one to be removed first. This structure is similar to a real-world
queue, like a line at a ticket counter.
Properties of a Queue
1. Linear Structure: A queue is a sequential collection of elements, where elements are arranged in a
linear order.
2. FIFO Principle: The first element added to the queue will be the first one to be removed.
Operations on a Queue
1. Enqueue Operation
The enqueue operation adds a new element to the rear of the queue. In a queue, elements are always
added at the rear to maintain the FIFO (First-In-First-Out) order.
This operation helps to insert elements into the queue, and they will be processed in the order they
were added.
How It Works:
1. Before adding an element, the queue is checked if it is full (in case of a fixed-size queue).
2. If the queue is not full, the element is appended at the end (rear).
1
3. If the queue is full, an error message is returned, indicating that no more elements can be
added.
Time Complexity: O(1) (constant time), as adding an element to the rear is done in constant time in
dynamic data structures like a list.
2. Dequeue Operation
The dequeue operation removes an element from the front of the queue. In a queue, elements are
always removed from the front, following the FIFO principle.
This operation serves to retrieve and remove elements from the queue in the order they were added.
How It Works:
1. Before removing an element, the queue is checked if it is empty.
2. If the queue is not empty, the element at the front of the queue is removed and returned.
3. If the queue is empty, an error message is returned, indicating that there are no elements to
remove.
Time Complexity: O(1) for removing from the front in a linked-list implementation, but O(n) in
Python’s list, because removing the first element requires shifting all other elements by one position.
2
3. Peek Operation (Front)
The peek operation allows you to view the element at the front of the queue without removing it.
It’s useful when you need to know which element is next to be processed without actually dequeuing
it.
How It Works:
1. Before retrieving the element, the queue is checked if it is empty.
2. If the queue is not empty, the element at the front is returned.
3. If the queue is empty, a message is returned, indicating that there is no front element to show.
Time Complexity: O(1), as simply viewing the first element doesn’t involve any shifting or
movement of data.
4. Display Operation
The display operation prints all the elements present in the queue in the order they were enqueued.
This operation is useful for visualizing the current state of the queue.
How It Works:
1. The queue is checked if it is empty.
3
2. If the queue is not empty, all elements from front to rear are printed in sequence.
3. If the queue is empty, a message is returned, indicating that the queue is empty.
Time Complexity: O(n), where n is the number of elements in the queue, as each element must be
printed.
5. isEmpty Operation
The isEmpty operation checks whether the queue contains any elements.
This operation is critical before performing operations like dequeue or peek to avoid errors.
How It Works:
1. It simply checks if the length of the queue is zero.
2. If it is, the queue is considered empty and returns True, otherwise, it returns False.
Time Complexity: O(1), as it only checks the length of the queue, which is a constant-time
operation.
6. isFull Operation
The isFull operation checks whether the queue has reached its maximum capacity (for fixed-size
queues).
This operation is essential before performing enqueue in a fixed-size queue to ensure no overflow
occurs.
How It Works:
1. It checks if the current size of the queue is equal to its maximum allowed size.
2. If the size is equal, it returns True (queue is full), otherwise False.
Time Complexity: O(1), as it only checks the current length of the queue, which is constant.
Enqueue(queue, value):
If rear == MAX_SIZE - 1:
Print "Queue Overflow"
Return
If front == -1:
front = 0 // Set front on first insertion
rear = rear + 1
4
queue[rear] = value
Dequeue(queue):
If front == -1 OR front > rear:
Print "Queue Underflow"
Return
value = queue[front]
front = front + 1
Return value
Peek(queue):
If front == -1 OR front > rear:
Print "Queue is Empty"
Return
Return queue[front]
IsEmpty(queue):
If front == -1 OR front > rear:
Return True
Else:
Return False
Define Node:
data
next
Enqueue(value):
Create newNode
[Link] = value
[Link] = NULL
If rear == NULL:
5
front = rear = newNode
Else:
[Link] = newNode
rear = newNode
Dequeue():
If front == NULL:
Print "Queue Underflow"
Return
value = [Link]
front = [Link]
If front == NULL:
rear = NULL // Reset rear when queue becomes empty
Return value
Peek():
If front == NULL:
Print "Queue is Empty"
Return
Return [Link]
IsEmpty():
If front == NULL:
Return True
Else:
Return False
Types of Queues:
There are several types of queues, each suited for different purposes depending on the order in which
elements are processed or how elements are accessed. Here are the main types:
1. Simple Queue (Linear Queue):
6
Description: This is the basic form of a queue where elements are added at the rear (enqueue) and
removed from the front (dequeue).
Order: First-In-First-Out (FIFO).
Example: A printer queue where documents are printed in the order they were received.
2. Circular Queue:
Description: A circular queue is a more efficient version of a simple queue. It uses a circular
structure to prevent the issue of "wasted space" at the front of the queue after elements are dequeued.
Order: First-In-First-Out (FIFO).
Example: Buffers in networking devices or memory buffers in real-time systems.
3. Priority Queue:
Description: In a priority queue, each element is assigned a priority. Elements with higher priority
are dequeued before those with lower priority, regardless of the order they were added.
Order: Not strictly FIFO; depends on the priority of elements.
Example: CPU scheduling in operating systems, or medical emergency response systems (where
patients with critical conditions are treated first).
4. Double-Ended Queue (Deque):
Description: A deque allows insertion and deletion of elements from both ends of the queue — from
the front (enqueue/dequeue) and from the rear (push/pop).
Order: Can operate as both FIFO and LIFO, depending on the operations performed.
7
Example: Palindrome checking, or applications that need both ends of the queue for efficient
processing.
9
Enqueued: 50
Queue contents: 10 20 30 40 50
Dequeued: 10
Dequeued: 20
Queue contents: 30 40 50
Queue is full. Cannot enqueue 60.
Queue contents: 30 40 50
Front item: 30
Implementation of Queues using Linked List
Program:
#include <stdio.h>
#include <stdlib.h>
// Node structure
struct Node
{
int data;
struct Node* next;
};
if (isEmpty())
{
front = rear = newNode;
}
else
{
rear->next = newNode;
rear = newNode;
}
10
printf("Enqueued: %d\n", value);
}
if (front == NULL)
{ // If queue becomes empty, reset rear
rear = NULL;
}
}
11
// Main function
int main()
{
enqueue(10);
enqueue(20);
enqueue(30);
display(); // Output: Queue: 10 20 30
peek(); // Output: Front element: 10
dequeue();
dequeue();
display(); // Output: Queue: 30
return 0;
}
Output:
Enqueued: 10
Enqueued: 20
Enqueued: 30
Queue: 10 20 30
Front element: 10
Dequeued: 10
Dequeued: 20
Queue: 30
C Program to implement Circular Queue
Program:
#include <stdio.h>
#define MAX 5 // Maximum size of the queue
int queue[MAX]; // Array to store queue elements
int front = -1, rear = -1; // Front and rear of the queue
12
// Remove an element from the queue (dequeue)
void dequeue() {
if (isEmpty()) {
printf("Queue is empty. Cannot dequeue.\n");
} else {
printf("\n Dequeued: %d", queue[front]);
if (front == rear) { // If the queue becomes empty after dequeue
front = rear = -1;
} else {
front = (front + 1) % MAX; // Circular increment of front
}
}
}
dequeue();
dequeue();
display(); // Display remaining queue contents
13
enqueue(60); // Enqueue after dequeue
display(); // Display queue after enqueue
return 0;
}
Output:
Enqueued: 10
Enqueued: 20
Enqueued: 30
Enqueued: 40
Enqueued: 50
10 20 30 40 50
Output:Dequeued: 10
Dequeued: 20
30 40 50
Enqueued: 60
30 40 50 60
Front item: 30
Enqueued: 70
30 40 50 60 70
C Program to implement a Simple Printer Queue System using Queue (First Come First Serve Job
Scheduling)
Program:
#include <stdio.h>
#include <stdio.h>
#include <string.h>
#define MAX 20
int jobId[MAX];
char jobName[MAX][50];
int front = -1, rear = -1;
int isFull()
{
return rear == MAX - 1;
}
int isEmpty()
{
return front == -1;
}
void dequeue()
{
if (isEmpty())
{
printf("Queue is empty! Nothing to process.\n");
return;
}
printf("Processing: %d - %s\n", jobId[front], jobName[front]);
if (front == rear)
{
front = rear = -1;
}
else
{
front++;
}
}
void displayQueue()
{
if (isEmpty())
{
printf("Queue is empty.\n");
return;
}
printf("Current Printer Queue:\n");
for (int i = front; i <= rear; i++)
{
printf("Job %d: %s\n", jobId[i], jobName[i]);
}
}
int main()
{
enqueue(1, "[Link]");
enqueue(2, "[Link]");
enqueue(3, "[Link]");
enqueue(4, "[Link]");
enqueue(5, "[Link]");
displayQueue();
15
dequeue();
dequeue();
enqueue(6, "[Link]"); // Will show "Queue is full"
displayQueue();
return 0;
}
Output:
Enqueued: 1 - [Link]
Enqueued: 2 - [Link]
Enqueued: 3 - [Link]
Enqueued: 4 - [Link]
Enqueued: 5 - [Link]
Current Printer Queue:
Job 1: [Link]
Job 2: [Link]
Job 3: [Link]
Job 4: [Link]
Job 5: [Link]
Processing: 1 - [Link]
Processing: 2 - [Link]
Enqueued: 6 - [Link]
Current Printer Queue:
Job 3: [Link]
Job 4: [Link]
Job 5: [Link]
Job 6: [Link]
C Program to perform comparison and check for symmetry using Stack
Program:
#include <stdio.h>
#include <stdbool.h>
// Stack
int stack[MAX];
int top = -1;
// Push function
void push(int value)
{
if (top == MAX - 1)
{
printf("Stack Overflow\n");
return;
}
stack[++top] = value;
}
// Pop function
int pop() {
if (top == -1)
16
{
return -1; // Return an invalid value for error
}
return stack[top--];
}
return true;
}
int main()
{
int arr[MAX], size;
17
return 0;
}
Output 1:
Enter the number of elements: 6
Enter 6 elements:
1
2
3
4
5
6
The array is not symmetric.
Output 2:
Enter the number of elements: 6
Enter 6 elements:
1
2
3
3
2
1
The array is symmetric.
int deque[MAX];
int front = -1, rear = -1;
bool isEmpty()
{
return front == -1;
}
int deleteRear()
{
int val = deque[rear];
if (front == rear)
{
front = rear = -1;
} else {
rear--;
}
return val;
}
int main()
{
int arr[MAX], size;
if (isSymmetric(arr, size))
printf("The array is symmetric.\n");
else
19
printf("The array is not symmetric.\n");
return 0;
}
Output 1:
Enter the number of elements: 5
Enter the 5 elements:
1
2
3
4
5
The array is not symmetric.
Output 2:
Enter the number of elements: 6
Enter the 6 elements:
1
2
3
3
2
1
The array is symmetric.
int jobId[MAX];
char jobName[MAX][50];
int front = -1, rear = -1;
21
if (i == rear)
break;
}
}
{
enqueue(1, "[Link]");
enqueue(2, "[Link]");
enqueue(3, "[Link]");
enqueue(4, "[Link]");
enqueue(5, "[Link]");
Queues play a critical role in various applications, including Breadth-First Search (BFS) and Scheduling
Algorithms.
1. Applications of Queues in Breadth-First Search (BFS)
22
Breadth-First Search (BFS) is a graph traversal algorithm that explores all the vertices of a graph (or nodes
of a tree) level by level, ensuring the shortest path is found when applicable. The core principle of BFS relies
on FIFO (First-In-First-Out) ordering, making a queue the ideal data structure to facilitate this traversal.
How BFS Uses a Queue:
Initialization: BFS begins by enqueuing the starting node into the queue and marking it as visited.
Exploration: The node at the front of the queue is dequeued, and its neighbors are explored.
Queue Update: The unvisited neighbors are enqueued and marked as visited.
Repeat: This continues until the queue is empty, meaning all reachable nodes have been explored.
Key Points:
Shortest Path: BFS guarantees the shortest path in unweighted graphs because it explores all nodes
at the current depth (distance) before moving on to the next depth.
Queue’s Role: The queue ensures that nodes are processed in the order they were discovered,
respecting the level-by-level exploration.
Example Use Cases:
Shortest Path Finding: BFS can be used to find the shortest path in an unweighted graph, such as
finding the shortest number of moves in a chess game or the shortest route in a city grid.
Social Networks: Finding the degree of separation between users, or determining the closest friends.
Web Crawling: Crawlers use BFS to explore websites level by level to gather information.
2. Applications of Queues in Scheduling
Queues are heavily used in Scheduling algorithms, especially in operating systems and task management
systems. Scheduling refers to determining the order in which tasks (or processes) are executed, and queues
ensure that tasks are processed in a fair and efficient manner, often using FIFO or priority-based policies.
Common Scheduling Techniques that Use Queues:
1. First-Come, First-Served (FCFS):
o Description: This is the simplest form of scheduling, where processes are scheduled in the
order they arrive. The first process to arrive is the first to be executed.
o Queue Role: A simple queue (FIFO) is used to maintain the order of processes.
o Example: A printer queue where documents are printed in the order they are received.
2. Round Robin (RR):
o Description: In Round Robin scheduling, each process is assigned a fixed time slice
(quantum). The process is executed for that time slice, and if it doesn’t finish, it is placed at
the end of the queue to wait for the next turn.
o Queue Role: A circular queue is often used to manage the processes. When a process is
executed for its time slice, it either completes or is moved to the back of the queue.
o Example: Time-sharing operating systems, where multiple users share the same CPU.
23
3. Priority Scheduling:
o Description: In this scheme, each process is assigned a priority, and the process with the
highest priority is executed first. If two processes have the same priority, they are scheduled
in FIFO order.
o Queue Role: A priority queue (which is usually implemented as a heap) is used to store and
retrieve processes based on their priority.
o Example: Real-time systems where higher priority tasks (e.g., medical or flight control
systems) must be completed first.
4. Shortest Job Next (SJN) or Shortest Job First (SJF):
o Description: This algorithm prioritizes processes with the shortest execution time, ensuring
that they are completed before longer ones.
o Queue Role: A priority queue or a sorted queue can be used to manage processes based on
their burst time (time to complete).
o Example: Job schedulers in batch processing systems.
25
Breadth First Search
Breadth-first search (BFS) is an algorithm that is used to graph data or searching tree or traversing
structures. The full form of BFS is the Breadth-first search.
The algorithm efficiently visits and marks all the key nodes in a graph in an accurate breadthwise fashion.
This algorithm selects a single node (initial or source point) in a graph and then visits all the nodes adjacent
to the selected node. Remember, BFS accesses these nodes one by one.
Once the algorithm visits and marks the starting node, then it moves towards the nearest unvisited nodes and
analyses them. Once visited, all nodes are marked. These iterations continue until all the nodes of the graph
have been successfully visited and marked.
A graph traversal is a commonly used methodology for locating the vertex position in the graph. It is an
advanced search algorithm that can analyze the graph with speed and precision along with marking the
sequence of the visited vertices. This process enables you to quickly visit each node in a graph without being
locked in an infinite loop.
1. In the various levels of the data, you can mark any node as the starting or initial node to begin
traversing. The BFS will visit the node and mark it as visited and places it in the queue.
2. Now the BFS will visit the nearest and un-visited nodes and marks them. These values are also added
to the queue. The queue works on the FIFO model.
3. In a similar manner, the remaining nearest and un-visited nodes on the graph are analyzed marked
and added to the queue. These items are deleted from the queue as receive and printed as the result.
26
Why do we need BFS Algorithm?
There are numerous reasons to utilize the BFS Algorithm to use as searching for your dataset. Some of the
most vital aspects that make this algorithm your first choice are:
BFS is useful for analyzing the nodes in a graph and constructing the shortest path of traversing
through these.
BFS can traverse through a graph in the smallest number of iterations.
The architecture of the BFS algorithm is simple and robust.
The result of the BFS algorithm holds a high level of accuracy in comparison to other algorithms.
BFS iterations are seamless, and there is no possibility of this algorithm getting caught up in an
infinite loop problem.
Graph traversal requires the algorithm to visit, check, and/or update every single un-visited node in a tree-
like structure. Graph traversals are categorized by the order in which they visit the nodes on the graph.
BFS algorithm starts the operation from the first or starting node in a graph and traverses it thoroughly. Once
it successfully traverses the initial node, then the next non-traversed vertex in the graph is visited and
marked.
Hence, you can say that all the nodes adjacent to the current vertex are visited and traversed in the first
iteration. A simple queue methodology is utilized to implement the working of a BFS algorithm, and it
consists of the following steps:
Step 1)
Each vertex or node in the graph is known. For instance, you can mark the node as V.
Step 2)
27
In case the vertex V is not accessed then add the vertex V into the BFS Queue
Step 3)
Start the BFS search, and after completion, Mark vertex V as visited.
Step 4)
The BFS queue is still not empty, hence remove the vertex V of the graph from the queue.
Step 5)
Retrieve all the remaining vertices on the graph that are adjacent to the vertex V
Step 6)
28
For each adjacent vertex let’s say V1, in case it is not visited yet then add V1 to the BFS queue
Step 7)
BFS will visit V1 and mark it as visited and delete it from the queue.
Step 2)
29
0 or zero has been marked as a root node.
Step 3)
Step 4)
Remaining 0 adjacent and unvisited nodes are visited, marked, and inserted into the queue.
Step 5)
30
Traversing iterations are repeated until all nodes are visited.
Let’s take a look at some of the real-life applications where a BFS algorithm implementation can be highly
effective.
Un-weighted Graphs: BFS algorithm can easily create the shortest path and a minimum spanning
tree to visit all the vertices of the graph in the shortest time possible with high accuracy.
P2P Networks: BFS can be implemented to locate all the nearest or neighboring nodes in a peer to
peer network. This will find the required data faster.
Web Crawlers: Search engines or web crawlers can easily build multiple levels of indexes by
employing BFS. BFS implementation starts from the source, which is the web page, and then it visits
all the links from that source.
Navigation Systems: BFS can help find all the neighboring locations from the main or source
location.
31
Network Broadcasting: A broadcasted packet is guided by the BFS algorithm to find and reach all
the nodes it has the address for.
C Program to implement Breadth First Search (BFS) using array based queue
BFS is commonly used for:
Shortest path finding in unweighted graphs.
Level-order traversal in trees.
Finding connected components in a graph.
Solving puzzles like the 8-puzzle.
Steps of BFS:
1. Start at a specific node (called the root or starting node).
2. Mark it as visited.
3. Enqueue the starting node into a queue.
4. While the queue is not empty:
o Dequeue a node.
o Visit all its unvisited neighbors (nodes connected to it), mark them as visited, and enqueue
them into the queue.
5. Repeat this process until all reachable nodes have been visited.
Properties of BFS:
Level-Order Traversal: BFS explores the graph level by level.
Shortest Path: In an unweighted graph, BFS guarantees the shortest path from the start node to any
other node.
Queue Data Structure: BFS uses a queue to manage the order of node exploration.
BFS Example:
Consider the following unweighted graph:
0
/\
1 2
/ \
3 4
Starting node: 0
BFS Traversal:
Start from 0, visit 0, enqueue its neighbors 1 and 2.
Then visit 1 and 2, enqueue their neighbors 3 and 4 (if not visited).
Continue until all nodes are visited.
32
Order of visiting:
BFS(0) → 0 → 1 → 2 → 3 → 4
Program:
#include <stdio.h>
int queue[SIZE];
int front = -1, rear = -1;
int dequeue()
{
if (front == -1)
{
return -1;
}
return queue[front++];
}
33
}
}
}
}
int main()
{
int graph[SIZE][SIZE]; // Adjacency matrix representation of graph
int visited[SIZE] = {0}; // Array to keep track of visited vertices
int n, start;
return 0;
}
Output 1:
Enter number of vertices: 4
Enter the adjacency matrix (0 or 1):
0111
1010
1101
1010
Enter the starting vertex (0 to 3): 1
BFS traversal: 1 0 2 3
Output 2:
Enter number of vertices: 5
Enter the adjacency matrix (0 or 1):
01100
10110
11001
01001
00110
Enter the starting vertex (0 to 4): 3
34
BFS traversal: 3 1 4 0 2
Scheduling
Scheduling using queues is a common approach in operating systems, networking, and task management
systems. Here’s a breakdown of how it works:
1. Types of Scheduling Using Queues
CPU Scheduling: Queues manage processes waiting for CPU time.
Job Scheduling: Jobs are placed in queues before being assigned resources.
Disk Scheduling: Requests to read/write from a disk are managed in a queue.
Network Scheduling: Packets waiting to be transmitted are queued.
2. Types of Queues in Scheduling
Ready Queue: Holds processes that are ready to execute.
Job Queue: Contains all processes waiting for execution.
Device Queue: Holds processes waiting for I/O operations.
3. Scheduling Algorithms Using Queues
First-Come, First-Served (FCFS): The first process in the queue is executed first.
Round Robin (RR): Each process gets a fixed time slice in a cyclic order.
Priority Scheduling: Processes are scheduled based on priority values.
Shortest Job Next (SJN): The shortest task in the queue gets executed first.
Multilevel Queue Scheduling: Processes are divided into multiple queues based on priority or type.
4. Example: Round Robin Scheduling Using a Queue
Each process gets executed for the given time quantum.
If a process isn't finished, it's placed back in the queue.
The process completes when its remaining time reaches zero.
35
done = 0;
if (remaining_time[i] > quantum) {
printf("Process %d executed for %d units\n", processes[i], quantum);
remaining_time[i] -= quantum;
time += quantum;
} else {
printf("Process %d completed\n", processes[i]);
time += remaining_time[i];
remaining_time[i] = 0;
}
}
}
} while (!done);
}
int main() {
int n = 3, quantum = 4;
int processes[] = {1, 2, 3};
int burst_time[] = {10, 5, 8};
36
int deque[MAX];
int front = -1, rear = -1;
void deleteFront()
{
if (front == -1)
{
printf("Queue is empty.\n");
return;
}
else if(front==rear)
front=rear=-1;
else
printf("Deleted from front: %d\n", deque[front]);
37
front++;
}
void deleteRear()
{
if (rear == -1 && front ==-1)
{
printf("Queue is empty.\n");
return;
}
else if(front==rear)
{
printf("Deleted from rear: %d\n", deque[rear]);
front=rear=-1;
}
else
{
printf("Deleted from rear: %d\n", deque[rear]);
rear=rear-1;
}
}
void display()
{
if (front == -1)
{
printf("Queue is empty.\n");
return;
}
printf("Deque elements: ");
for (int i = front; i <= rear; i++)
{
printf("%d ", deque[i]);
}
printf("\n");
}
int main()
{
int choice, value;
while (1)
{
printf("\n--- Deque Menu ---\n");
printf("1. Insert at Rear\n");
printf("2. Insert at Front\n");
printf("3. Delete from Front\n");
printf("4. Delete from Rear\n");
printf("5. Display\n");
printf("6. Exit\n");
printf("Enter your choice: ");
scanf("%d", &choice);
38
switch (choice)
{
case 1:
printf("Enter value to insert at rear: ");
scanf("%d", &value);
insertRear(value);
break;
case 2:
printf("Enter value to insert at front: ");
scanf("%d", &value);
insertFront(value);
break;
case 3:
deleteFront();
break;
case 4:
deleteRear();
break;
case 5:
display();
break;
case 6:
printf("Exiting program.\n");
return 0;
default:
printf("Invalid choice! Please try again.\n");
}
}
return 0;
}
Applications of Deques
1. Palindrome Checking
Used in problems where you need to find the max/min in every window of size k.
Deque helps maintain candidates efficiently in O(n) time.
In editors, you can undo (pop from one end) or redo (push to other end).
Deques are perfect for this two-way movement.
8. Memory Management
In systems like memory caches or CPU task management, deques help manage entries from both
ends (like LRU cache).
A deque can simulate both a stack (LIFO) and a queue (FIFO) depending on which ends you use.
40