0% found this document useful (0 votes)
7 views186 pages

Data Structures SOL

The document is a syllabus for a course on Data Structures, published by the Department of Distance and Continuing Education at the University of Delhi. It covers various topics including arrays, linked lists, stacks, queues, searching and sorting algorithms, recursion, trees, and binary search trees, along with their operations and applications. The content is structured into units and lessons, providing a comprehensive guide for students studying data structures.

Uploaded by

somya21006
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)
7 views186 pages

Data Structures SOL

The document is a syllabus for a course on Data Structures, published by the Department of Distance and Continuing Education at the University of Delhi. It covers various topics including arrays, linked lists, stacks, queues, searching and sorting algorithms, recursion, trees, and binary search trees, along with their operations and applications. The content is structured into units and lessons, providing a comprehensive guide for students studying data structures.

Uploaded by

somya21006
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/ 186

1606-Data Structures [BAP-CA-S2I-Minor] Cover Jan25.

pdf - January 19, 2025


Data Structures

Editorial Board
Prof. Ajay Jaiswal, Ms Aishwarya Anand Arora

Content Writers
.Dr Geetika Vashishta, Ms Aishwarya Anand Arora, Dr Shweta Tyagi

Content Reviewer from the DDCE/COL/SOL


Dr Charu Gupta, Ms Asha Yadav

.Academic Coordinator
Mr Deekshant Awasthi

© Department of Distance and Continuing Education


ISBN: 978-81-19417-18-6
Ist edition: 2024
E-mail: [email protected]
[email protected]

Published by:
Department of Distance and Continuing Education
Campus of Open Learning/School of Open Learning,
University of Delhi, Delhi-110 007

Printed by:
School of Open Learning, University of Delhi

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

Corrections/Modifications/Suggestions proposed by Statutory Body,


DU/Stakeholder/s in the Self Learning Material (SLM) will be incorporated in
the next edition. However, these corrections/modifications/suggestions will be
uploaded on the website https://sol.du.ac.in. Any feedback or suggestions can be
sent to the email- [email protected]

Printed at: Taxmann Publications Pvt. Ltd., 21/35, West Punjabi Bagh,
Printed at: Vikas Publishing House New
Pvt. Ltd. Plot 20/4,
Delhi Site-IV, Industrial
- 110026 Area Sahibabad,
(500 Copies, 2025) Ghaziabad - 201 010 (600 Copies)

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

SYLLABUS
Data Structures
Syllabus Mapping

Unit I
Arrays, Linked Lists, Stacks, Queues, Deques: Arrays: array operations, Lesson 1: Arrays - Building
applications, sorting, two-dimensional arrays, dynamic allocation of arrays; Blocks of Data Structures
Linked Lists: singly linked lists, doubly linked lists, circularly linked lists, Lesson-2: Linked Lists: Dynamic
Stacks: Stack as an ADT, implementing stacks using arrays, implementing Data Structure
stacks using linked lists, applications of stacks; Queues: Queue as an ADT, Lesson-3: Stacks - The LIFO
implementing queues using arrays, implementing queues using linked lists, Data Structure
double-ended Queue as an ADT. Lesson-4: Queue - The FIFO
Data Structure
(1–72)

Unit II
Searching and Sorting: Linear Search, Binary Search, Insertion Sort, and Lesson-5: Searching and
Count Sort. Sorting Algorithms - Efficient
Data Manipulation Techniques
(73–109)

Unit III
Recursion: Recursive Functions, Linear Recursion, Binary Recursion. Lesson-6: Recursion - Iteration
Through Self-Reference
(Pages 111–129)

Unit IV
Trees, Binary Trees: Trees: Definition and Properties, Binary Trees: Definition Lesson-7: Trees - Nurturing
and Properties, Traversal of Binary Trees. Hierarchical Structures in
Computing
(Pages 131–143)

Unit V
Binary Search Trees: insert, delete (by copying), search operations. Lesson-8: Binary Search Trees -
Crafting Ordered Hierarchies
for Efficient Retrieval
Lesson-9: Heap Data Structure
(Pages 145–172)

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

CONTENTS

UNIT I

LESSON 1 ARRAYS - BUILDING BLOCKS OF DATA STRUCTURES 3–19


1.1 Learning Objectives
1.2 Introduction
1.2.1 Key Characteristics of Arrays
1.2.2 Applications of Arrays
1.3 Operation on Arrays
1.4 Multi-dimensional Arrays
1.5 Dynamic Allocation of Arrays
1.6 Summary
1.7 Glossary
1.8 Answers to In-Text Questions
1.9 Self-Assessment Questions
1.10 References
1.11 Suggested Readings
LESSON 2 LINKED LISTS: DYNAMIC DATA STRUCTURE 21–38
2.1 Learning Objectives
2.2 Introduction
2.3 Singly-Linked Lists
2.4 Doubly Linked Lists
2.5 Circularly Linked Lists
2.6 Dynamic Allocation in Linked List
2.7 Summary
2.8 Glossary

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

2.9 Answers to In-Text Questions


2.10 Self-Assessment Questions
2.11 References
2.12 Suggested Readings
LESSON 3 STACKS - THE LIFO DATA STRUCTURE 39–58
3.1 Learning Objectives
3.2 Introduction
3.3 Stack as an ADT
3.4 Implementing Stacks using Arrays
3.5 Implementing stacks using Linked Lists
3.6 Applications of Stacks
3.7 Summary
3.8 Glossary
3.9 Answers to In-Text Questions
3.10 Self-Assessment Questions
3.11 References
3.12 Suggested Readings
LESSON 4 QUEUE - THE FIFO DATA STRUCTURE 59–72
4.1 Learning Objectives
4.2 Introduction
4.3 Queues: Queue as an ADT
4.4 Implementing Queues Using Arrays
4.4.1 Inserting an Element in a Queue Using Arrays
4.4.2 Deleting an Element in a Queue Using Arrays
4.4.3 Drawbacks

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

4.5 Implementing Queues Using Linked Lists


4.5.1 Insert an Element in a Queue Using Linked List
4.5.2 Delete an Element from a Queue Using Linked List
4.6 Double-ended Queue as an ADT
4.7 Summary
4.8 Glossary
4.9 Answers to In-Text Questions
4.10 Self-Assessment Questions
4.11 References
4.12 Suggested Readings

UNIT II

LESSON 5 SEARCHING AND SORTING ALGORITHMS - EFFICIENT DATA


MANIPULATION TECHNIQUES 75–109
5.1 Learning Objectives
5.2 Introduction
5.3 Linear Search
5.4 Binary Search
5.5 Bubble Sort
5.6 Insertion Sort
5.7 Selection Sort
5.8 Merge Sort
5.9 Quick Sort
5.10 Count Sort
5.11 When to use which sorting algorithm?
5.12 Summary
5.13 Glossary

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

5.14 Answers to In-Text Questions


5.15 Self-Assessment Questions
5.16 References
5.17 Suggested Readings

UNIT III

LESSON 6 RECURSION - ITERATION THROUGH SELF-REFERENCE 113–129


6.1 Learning Objectives
6.2 Introduction
6.3 Recursion: Definition and Working
6.3.1 How Recursion Works
6.3.2 The Structure of a Recursive Function
6.3.3 Example of a Recursive Function
6.3.4 Applications of Recursive Algorithms
6.4 Linear Recursion
6.4.1 Example of a Linear Recursive Function: Calculating the Factorial of a
Number
6.5 Binary Recursion
6.5.1 Example of Binary Search
6.5.2 Example of Linked List Traversal Using Recursion
6.5.3 Example of Power Calculation Traversal Using Recursion
6.5.4 Understanding Recursive Calls - A Closer Look
6.5.5 The Call Stack
6.5.6 Handling Recursive Function Calls
6.6 Summary
6.7 Glossary

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

6.8 Answers to In-Text Questions


6.9 Self-Assessment Questions
6.10 References
6.11 Suggested Readings

UNIT IV

LESSON 7 TREES - NURTURING HIERARCHICAL


STRUCTURES IN COMPUTING 133–143
7.1 Learning Objectives
7.2 Introduction
7.3 Trees: Definition and Properties
7.4 Binary Trees: Definition and Properties
7.5 Tree Traversal
7.6 Summary
7.7 Glossary
7.8 Answers to In-Text Questions
7.9 Self-Assessment Questions
7.10 References
7.11 Suggested Readings

UNIT V

LESSON 8 BINARY SEARCH TREES - CRAFTING ORDERED HIERARCHIES


FOR EFFICIENT RETRIEVAL 147–160
8.1 Learning Objectives
8.2 Introduction
8.3 Binary Search Trees

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

8.4 Binary Search Tree Operations


8.4.1 Searching a Binary Search Tree
8.4.2 Insertion in a Binary Search Tree
8.4.3 Deletion from a Binary Search Tree
8.5 Balanced search Tree
8.6 Summary
8.7 Glossary
8.8 Answers to In-Text Questions
8.9 Self-Assessment Questions
8.10 References
8.11 Suggested Readings
LESSON 9 HEAP DATA STRUCTURE 161–172
9.1 Learning Objectives
9.2 Introduction
9.3 Binary Heap
9.3.1 Array Representation of Binary Heap
9.3.2 MAX-HEAPIFY Property
9.3.3 BUILD-MAX-HEAP
9.4 Priority Queue
9.4.1 Max Priority Queue and its Operation
9.4.2 Implementing Priority Queue Using Heap
9.5 Summary
9.6 Glossary
9.7 Self-Assessment Questions
9.8 Answers to In-Text Questions
9.9 References
9.10 Suggested Readings

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
UNIT I

LESSON 1 ARRAYS - BUILDING BLOCKS OF DATA


STRUCTURES

LESSON 2 LINKED LISTS - DYNAMIC DATA


STRUCTURES

LESSON 3 STACKS - THE LIFO DATA STRUCTURE

LESSON 4 QUEUES THE FIFO DATA STRUCTURE


Arrays - Building Blocks of Data Structures

LESSON 1 NOTES

ARRAYS - BUILDING BLOCKS OF DATA


STRUCTURES

Name of Author: Dr Geetika Vashishta


Designation: Assistant Professor,
College of Vocational Studies
Email: [email protected]

Structure
1.1 Learning Objectives
1.2 Introduction
1.2.1 Key Characteristics of Arrays
1.2.2 Applications of Arrays
1.3 Operation on Arrays
1.4 Multi-dimensional Arrays
1.5 Dynamic Allocation of Arrays
1.6 Summary
1.7 Glossary
1.8 Answers to In-Text Questions
1.9 Self-Assessment Questions
1.10 References
1.11 Suggested Readings

1.1 LEARNING OBJECTIVES

 To understand how to organise and store data in a structured manner.


 To acquire the skills to implement algorithms and procedures using arrays.
 To learn to access and retrieve individual elements quickly using indices.
 To learn how to represent and manipulate multi-dimensional data using arrays.

Self-Instructional
Material 3

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

NOTES
1.2 INTRODUCTION

An array is a data structure that allows you to store a fixed-size collection of elements
of the same type. It provides a way to organise related data items under a single name
and allows efficient access to individual elements using an index.
Arrays are a consecutive sequence of memory locations where each element
occupies a specific position. The elements are typically homogeneous, meaning they
have the same data type. For example, an array can store a sequence of integers,
floating-point numbers, characters, or even complex objects.

1.2.1 Key Characteristics of Arrays

 Fixed Size: Arrays have a predetermined size specified at the time of declaration,
which remains constant throughout the program execution.
 Homogeneous Elements: All elements within an array are of the same data
type. This constraint allows for efficient memory allocation and predictable access
to elements.
 Zero-based Indexing: In most programming languages, including C++, arrays
use zero-based indexing. This means that the first element is accessed with an
index of 0, the second element with an index of 1, and so on.
 Contiguous Memory Allocation: Array elements are stored in adjacent
memory locations, ensuring efficient memory utilisation and enabling fast access
to elements using index calculations.

1.2.2 Applications of Arrays

Arrays have several applications in the programming world:


1. Arrays can be used to implement stacks and queues, which are linear data
structures and can be used to perform several operations.
2. Arrays can also implement many other data structures like lists, heap, hash
tables, strings, etc. Implementing these data structures using arrays is
Self-Instructional
4 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Arrays - Building Blocks of Data Structures

comparatively simple and very efficient in terms of space when any modification NOTES
is done.
3. CPU scheduling is a process by which the CPU decides the order in which
various tasks will be executed. Arrays are handy data structures that contain a
list of processes that need to be scheduled for the CPU.
4. Arrays are also used in image processing, as each array cell corresponds to a
pixel of an image.
5. Arrays can also be used in the implementation of a complete binary tree. They
can store the tree’s data value corresponding to a node position inside it.
6. Arrays can also be used in machine learning and data science. They store datasets
that play a significant role in training models that are used for predictions.

1.3 OPERATIONS ON ARRAYS

Arrays support various operations for accessing and manipulating their elements. Some
common operations include:
 Declaration: Defining an array variable, specifying its data type and size.
 Initialisation: Assigning initial values to array elements at the time of declaration
or later using assignment statements or loops.
 Accessing Elements: Retrieving the value of an individual element by using its
index within square brackets.
 Modifying Elements: Updating the value of an element by assigning a new
value to it using the index.
 Traversing: Iterating over the array to perform operations on each element.
 Bounds Checking: Ensuring that array indices are within the valid range to
prevent accessing elements outside the array’s boundaries.
 Array Length: Determining the number of elements in an array using the length
or size property or the sizeof operator.

Self-Instructional
Material 5

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

NOTES

Figure 1.1: Diagrammatic representation of the array

Arrays are widely used in programming for tasks such as storing collections of
data, implementing algorithms, and solving various computational problems efficiently.
They provide a fundamental building block for many other data structures and
algorithms.
Consider a scenario where you need to store and manage the grades of a group
of students in a class. You can use an array to store and process this data efficiently.
Let us say you have a class of 30 students, and you want to store their grades
for a particular subject. Each student’s grade can be represented as a numerical value
ranging from 0 to 100.
Here is how you can use an array to store and manipulate the grades:

#include <iostream>
const int NUM_STUDENTS = 30; // Number of students in the class
int main() {
int grades[NUM_STUDENTS]; // Array to store the grades
// Input: Read grades for each student
for (int i = 0; i < NUM_STUDENTS; i++) {
std::cout << “Enter the grade for student” << (i + 1) <<
“:”;
std::cin >> grades[i];
}
// Output: Display all grades
std::cout << “Grades of all students:” << std::endl;
for (int i = 0; i < NUM_STUDENTS; i++) {
std::cout << “Student” << (i + 1) << “:” << grades[i] <<
std::endl;
Self-Instructional
6 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Arrays - Building Blocks of Data Structures

} NOTES
// Processing: Calculate average grade
int sum = 0;
for (int i = 0; i < NUM_STUDENTS; i++) {
sum += grades[i];
}
double average = static_cast<double>(sum) / NUM_STUDENTS;
// Output: Display average grade
std::cout << “Average grade:” << average << std::endl;
return 0;
}

In this example, we declare an integer array grades with a size of


NUM_STUDENTS, which is set to 30. We then use a loop to read the grades for
each student from the user and store them in the array.
Next, we display all the grades by iterating the array using a loop. After that, we
calculate the average grade by summing up all the grades in the array and dividing the
sum by the total number of students.
Finally, we display the average grade to the user.
Using an array in this scenario allows us to efficiently store and process the
grades of multiple students, making it easy to perform calculations and display the
results. Let us look closer at the process of declaring and initialising arrays.

Declaring and Initialising Arrays

To declare an array, you need to specify the type of its elements, followed by the array
name and the number of elements enclosed in square brackets. Here is an example:
int numbers[5]; // Declares an integer array with 5 elements

You can also initialise an array during declaration by providing a comma-separated list
of values enclosed in curly braces. The number of values must match the size of the
array:
int numbers[5] = {1, 2, 3, 4, 5}; // Initialises the array with
values Self-Instructional
Material 7

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

NOTES Accessing Array Elements

You can access individual elements of an array using the array name followed by the
index enclosed in square brackets. The index ranges from 0 to (size - 1). Here is an
example:
int numbers[5] = {1, 2, 3, 4, 5};
int firstElement = numbers[0]; // Accesses the first element (1)
int thirdElement = numbers[2]; // Accesses the third element (3)

Modifying Array Elements

You can modify the value of an array element by assigning a new value to it using the
assignment operator (=). Here is an example:
int numbers[5] = {1, 2, 3, 4, 5};
numbers[1] = 10; // Modifies the second element to 10
numbers[3] = numbers[0] + numbers[2]; // Modifies the fourth
element to the sum of the first and third elements

Array Bounds and Out-of-Bounds Access

It is important to note that arrays in C++ are zero-indexed, meaning the first element is
at index 0, and the last element is at index (size - 1). Accessing elements outside these
bounds can lead to undefined behaviour and should be avoided.
int numbers[5] = {1, 2, 3, 4, 5};
int invalidElement = numbers[5]; // Accesses an element outside
the array bounds (undefined behaviour)

Iterating over Arrays

You can use loops to iterate over the elements of an array. One common approach is
to use a for loop with the index variable ranging from 0 to (size - 1). Here is an
example:
int numbers[5] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++) {
cout << numbers[i] << “ ”; // Prints each element separated
by a space
Self-Instructional
8 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Arrays - Building Blocks of Data Structures

} NOTES
// Output: 1 2 3 4 5

Some examples of one-dimensional arrays

1. Finding the Maximum Element in an Array:


#include <iostream>
int main() {
int numbers[] = {8, 2, 6, 1, 9, 3, 5, 7, 4}; // Declare and
initialise an integer array
int max = numbers[0]; // Assume the first element as the
maximum
// Find the maximum element
for (int i = 1; i < sizeof(numbers) / sizeof(numbers[0]);
i++) {
if (numbers[i] > max) {
max = numbers[i];
}
}
std::cout << “Maximum element:” << max << std::endl;
return 0;
}

Explanation: In this example, we declare and initialise an integer array numbers. We


assume the first element as the maximum and then iterate through the remaining elements
of the array. We update the maximum value if we find an element greater than the
current maximum. Finally, we display the maximum element.
2. Calculating the Average of Array Elements:
#include <iostream>
int main() {
int numbers[] = {10, 20, 30, 40, 50}; // Declare and initialize
an integer array
int sum = 0;
// Calculate the sum of array elements
for (int i = 0; i < sizeof(numbers) / sizeof(numbers[0]);
i++) { Self-Instructional
Material 9

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

NOTES sum += numbers[i];


}
double average = static_cast<double>(sum) / (sizeof(numbers)
/ sizeof(numbers[0]));
std::cout << “Average:” << average << std::endl;
return 0;
}

Explanation: In this code, we declare and initialise an integer array numbers. We use
a loop to iterate through all the elements of the array and calculate their sum. Finally,
we divide the sum by the total number of elements to obtain the average and display it.
3. Searching for an Element in an Array:
#include <iostream>
int main() {
int numbers[] = {12, 45, 8, 27, 34, 19}; // Declare and
initialise an integer array
int target = 27; // Element to search for
bool found = false;
//Search for the element in the array
for (int i = 0; i < sizeof(numbers) / sizeof(numbers[0]);
i++) {
if (numbers[i] == target) {
found = true;
break;
}
}
// Display the result
if (found) {
std::cout << “Element” << target << “found in the array.”
<< std::endl;
} else {
std::cout << “Element” << target << “not found in the
array.” << std::endl;
}
Self-Instructional return 0;
10 Material
}

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Arrays - Building Blocks of Data Structures

Explanation: In this code, we declare and initialise an integer array numbers. We NOTES
specify a target element (in this case, 27) that we want to search for in the array. Using
a loop, we iterate through the elements and check if any of them match the target
element. If a match is found, we set the found flag to true and exit the loop using break.
Finally, we display whether the target element was found in the array or not.
4. Reversing the Elements of an Array:
#include <iostream>
int main() {
int numbers[] = {1, 2, 3, 4, 5}; // Declare and initialise an
integer array
int start = 0;
int end = sizeof(numbers) / sizeof(numbers[0]) - 1;
// Reverse the elements of the array
while (start < end) {
// Swap elements
int temp = numbers[start];
numbers[start] = numbers[end];
numbers[end] = temp;
start++;
end--;
}
// Display the reversed array
std::cout << “Reversed array:”;
for (int i = 0; i < sizeof(numbers) / sizeof(numbers[0]);
i++) {
std::cout << numbers[i] << “ ”;
}
std::cout << std::endl;
return 0;
}

Explanation: In this example, we declare and initialise an integer array numbers. We


use two variables, start and end, to keep track of the start and end indices of the array.
Self-Instructional
Material 11

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

NOTES Using a while loop, we swap the elements at the start and end indices and increment/
decrement the indices until they meet in the middle. This effectively reverses the order
of the elements in the array. Finally, we display the reversed array.

1.4 MULTI-DIMENSIONAL ARRAYS

C++ also supports multi-dimensional arrays, which are essentially arrays of arrays.
They can represent matrices, grids, or any other tabular data structure. Multi-
dimensional arrays play a crucial role in organising and manipulating complex data
structures that require more than one dimension. They allow you to represent and
process data in a tabular or matrix-like form, where the data is arranged in rows and
columns.
Here are some key roles and use cases of multi-dimensional arrays:
 Matrices and Grids: Multi-dimensional arrays commonly represent
mathematical matrices, grids, or tables. For example, in image processing, a
two-dimensional array can store pixel values, where each element represents
the colour intensity at a specific location.
 Board Games and Puzzles: Multi-dimensional arrays are helpful in
implementing board games, puzzles, or mazes. The elements of the array can
represent the cells or squares of the game board, and each element stores the
state or properties of that particular cell.
 Spatial Data: Multi-dimensional arrays are suitable for storing and processing
spatial data, such as geographical coordinates, three-dimensional models, or
voxel-based representations. Each dimension of the array corresponds to a
specific spatial coordinate, enabling efficient manipulation, and analysis of the
data.
 Scientific Simulations: Multi-dimensional arrays are essential in scientific
simulations, such as computational physics or weather modelling. They can store
data points in multiple dimensions, allowing scientists to analyse and predict
complex systems.
Self-Instructional
12 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Arrays - Building Blocks of Data Structures

 Image Processing and Computer Vision: Multi-dimensional arrays are NOTES


extensively used in image processing and computer vision applications. Images
can be represented as two or three-dimensional arrays, where each element
corresponds to a pixel value. Manipulating such arrays enables operations like
filtering, transformations, and feature extraction.
 Nested Data Structures: Multi-dimensional arrays can be combined with
other data structures to create nested structures. For example, a two-
dimensional array of objects can represent a collection of records or a database
table.
By providing a structured and organised representation of data, multi-dimensional
arrays allow for efficient storage, manipulation, and analysis of complex information.
They play a fundamental role in various domains, including scientific computing, graphics,
data analysis, and artificial intelligence.

Figure 1.2: Diagrammatic representation of the 2D array

Here is an example of a 2D array:


int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};

int element = matrix[1][2]; // Accesses the element at row 1,


column 2 (6)

Self-Instructional
Material 13

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

NOTES Array Size and sizeof Operator

In C++, you can determine the size of an array using the sizeof operator, which returns
the total number of bytes occupied by the array. To get the number of elements, divide
the size by the size of an individual element. Here is an example:
int numbers[5] = {1, 2, 3, 4, 5};
int size = sizeof(numbers) / sizeof(numbers[0]); // Calculates
the number of elements (5)

In-Text Questions
Q1. Which of the following statements is true regarding arrays in programming?
A. Arrays can only store elements of the same data type.
B. Arrays can dynamically change their size during runtime.
C. Arrays cannot be passed to functions in programming languages.
D. Arrays are always sorted automatically.
Q2. In an array with 10 elements indexed from 0 to 9, what is the index of the last
element?
A. 9 B. 10
C. 8 D. 0
Q3. What is the time complexity to access an element in an array by index?
A. O(n) B. O(1)
C. O(log n) D. O(n^2)
Q4. Which of the following methods is used to find the length of an array in many
programming languages?
A. array.size() B. array.length
C. array.count D. array.size
Q5. What term is used to access elements in an array using an index?
A. Mapping B. Traversal
C. Indexing D. Iteration

Self-Instructional
14 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Arrays - Building Blocks of Data Structures

NOTES
1.5 DYNAMIC MEMORY ALLOCATION

Unlike a fixed array, where the array size must be fixed at compile time, dynamically
allocating an array allows us to choose an array length at runtime.
To allocate an array dynamically, we use the array form of new and delete (often
called new[] and delete[]):
#include <cstddef>
#include <iostream>

int main()
{
std::cout << “Enter a positive integer:”;
std::size_t length{};
std::cin >> length;

int* array{ new int[length]{} }; // use array new. Note that


length does not need to be constant!

std::cout << “I just allocated an array of integers of length”


<< length << ‘\n’;
array[0] = 5; // set element 0 to value 5
delete[] array; // use array delete to deallocate array

//We do not need to set the array to nullptr/0 here because it


is going out of scope immediately after this.
return 0;
}

Dynamically deleting arrays

When deleting a dynamically allocated array, we have to use the array version of
delete, which is delete[].
Self-Instructional
Material 15

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

NOTES This tells the CPU to clean up multiple variables instead of a single variable.
One of the most common mistakes that new programmers make when dealing with
dynamic memory allocation is to use delete instead of delete[] when deleting a
dynamically allocated array. Using the scalar version of delete on an array will result in
undefined behaviour, such as data corruption, memory leaks, crashes, or other problems.

Initialising dynamically allocated arrays

If you want to initialise a dynamically allocated array to 0, the syntax is quite simple:
int* array{ new int[length]{} };

There was no easy way to initialise a dynamic array to a non-zero value (initialiser lists
only worked for fixed arrays). This means you had to explicitly loop through the array
and assign element values.
int* array = new int[5];
array[0] = 9;
array[1] = 7;
array[2] = 5;
array[3] = 3;
array[4] = 1;

1.6 SUMMARY

In this lesson, you learned the basics of arrays in C++, including declaration, initialisation,
accessing elements, modifying elements, iterating over arrays, working with multi-
dimensional arrays, and obtaining array size using the sizeof operator. Arrays provide
a powerful and efficient way to handle collections of data in your C++ programs.

Key points

 Arrays are a fundamental data structure in programming that allows you to


store a fixed-size sequence of elements of the same type.
 They provide a convenient way to manage and manipulate collections of related
Self-Instructional data.
16 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Arrays - Building Blocks of Data Structures

 Arrays are declared using square brackets ([]), and their elements are accessed NOTES
using an index starting from 0.
 Elements of an array are stored in contiguous memory locations, enabling efficient
memory utilisation and fast access using index calculations.
 Arrays have a fixed size specified at the time of declaration, and their size remains
constant throughout the program execution.
 Arrays are typically used for homogeneous data types, such as integers, floating-
point numbers, characters, or objects of a specific class.
 Accessing and modifying elements of an array is done using the array name
followed by the index enclosed in square brackets.
 Arrays support operations such as declaration, initialisation, element access,
modification, and traversal using loops.
 It is essential to perform bounds checking to ensure that array indices are within
the valid range to prevent accessing elements outside the array’s boundaries.
 Multi-dimensional arrays allow you to represent and process data in multiple
dimensions, such as matrices, grids, or spatial data.
 Multi-dimensional arrays are widely used in various domains, including image
processing, scientific simulations, board games, and data analysis.
 Arrays provide an efficient way to store and manipulate large amounts of data,
making them a fundamental building block for many other data structures and
algorithms.
Understanding arrays and their operations is essential for effective programming,
as they provide a powerful tool for managing and processing collections of data in a
structured manner.

1.7 GLOSSARY

 Arrays: Arrays are a collection of elements/values stored at contiguous memory


locations. They offer indexed access and have a fixed size once declared.
Self-Instructional
 Element: Individual items stored within an array. Material 17

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

NOTES  Index: A numeric value representing the position of an element within an array.
 Size/Length: The total number of elements that an array can hold, often
determined at its creation.
 Static Array: An array with a fixed size that cannot be changed during runtime.
 Dynamic Array: An array-like data structure that can dynamically resize itself
to accommodate varying numbers of elements.

1.8 ANSWERS TO IN-TEXT QUESTIONS

1. A. Arrays can only store elements of the same data type.


2. A. 9
3. B. O(1)
4. B. array.length
5. C. Indexing

1.9 SELF-ASSESSMENT QUESTIONS

1. Define an array in computer programming and explain its advantages.


2. Differentiate between a one-dimensional array and a multi-dimensional array.
Give examples for both.
3. Explain the concept of ‘index’ in an array and its significance in accessing
elements.
4. Discuss the advantages and limitations of arrays in comparison to other data
structures.
5. Explain the concept of dynamic arrays and how they differ from static arrays.
6. Discuss the time complexity of various operations performed on arrays, such as
searching, insertion, and deletion.
Self-Instructional
18 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Arrays - Building Blocks of Data Structures

7. Describe the process of accessing elements stored in memory locations within NOTES
an array.
8. Explain the concept of contiguous memory allocation in arrays and its impact on
memory usage and access speed.
9. Discuss the importance of array initialisation and its various methods in
programming languages.
10. Describe the process of resizing an array and the challenges associated with it.

1.10 REFERENCES

 Goodrich, M.T., Tamassia, R., & Mount, D., Data Structures and Algorithms
Analysis in C++, 2nd edition, Wiley, 2011.
 Cormen, T.H., Leiserson, C.E., Rivest, R. L., Stein C. Introduction to Algorithms,
4th edition, Prentice Hall of India, 2022.
 Drozdek, A., Data Structures and Algorithms in C++, 4th edition, Cengage
Learning, 2012.

1.11 SUGGESTED READINGS

 Sahni, S. Data Structures, Algorithms and Applications in C++, 2nd Edition,


Universities Press, 2011.
 Langsam Y., Augenstein, M. J., & Tanenbaum, A. M. Data Structures Using C
and C++, Pearson, 2015.

Self-Instructional
Material 19

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Linked Lists: Dynamic Data Structure

LESSON 2 NOTES

LINKED LISTS: DYNAMIC DATA STRUCTURE

Name of Author: Dr Geetika Vashishta


Designation: Assistant Professor,
College of Vocational Studies
Email: [email protected]
Structure
2.1 Learning Objectives
2.2 Introduction
2.3 Singly-Linked Lists
2.4 Doubly Linked Lists
2.5 Circularly Linked Lists
2.6 Dynamic Allocation in Linked List
2.7 Summary
2.8 Glossary
2.9 Answers to In-Text Questions
2.10 Self-Assessment Questions
2.11 References
2.12 Suggested Readings

2.1 LEARNING OBJECTIVES

 To comprehend the node-based structure of linked lists.


 To learn how to insert and delete elements in a linked list efficiently.
 To understand the concept of circularly linked lists.

2.2 INTRODUCTION

A linked list is a fundamental data structure used to store and manage collections of Self-Instructional
Material 21
data. Unlike arrays, which store elements in contiguous memory locations, linked lists

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

NOTES organise data using nodes that are linked together through references or pointers.
Each node in a linked list contains two components: data and a pointer to the next
node in the sequence. The data component holds the value or information being stored,
while the pointer component, often called the “next” pointer, indicates the memory
address of the next node in the list.
The structure of a linked list allows for efficient insertion and deletion operations,
especially when compared to arrays, where resizing can be costly. Since the nodes are
not required to be stored in consecutive memory locations, they can be dynamically
allocated and linked together, allowing for flexibility in managing the list’s size.
There are different types of linked lists, including singly linked lists, doubly linked
lists, and circular linked lists. In a singly linked list, each node contains a reference to
the next node in the sequence. Doubly linked lists, on the other hand, have nodes with
references to both the next and previous nodes, enabling traversal in both directions.
Circular linked lists form a loop, where the last node’s next pointer points back to the
first node, creating a circular structure.
Linked lists offer advantages in scenarios where dynamic data structures are
needed or when frequent insertions and deletions are expected. However, they can be
less efficient than arrays when it comes to random access, as accessing elements
requires traversing the list from the beginning. Nonetheless, various algorithms and
techniques can be applied to optimise linked list operations for specific use cases.
Let us consider a scenario where you are using a music streaming service that
allows you to create and manage playlists. Here is how a linked list can be used to
implement a playlist:
Each song in the playlist can be represented by a node in the linked list. The
node would contain two main fields: the song data (such as the song title, artist, duration,
etc.) and a reference to the next song in the playlist.
Structure for a Node:
struct SongNode {
Song song;
SongNode* next;
};

Self-Instructional
22 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Linked Lists: Dynamic Data Structure

In this example, the Song structure represents the data for a particular song, NOTES
and the next pointer points to the next song in the playlist.

Creating the Playlist

To create a playlist, you would initialise the head pointer to the first song in the list, and
the next pointer of the last song would be set to NULL, indicating the end of the
playlist.

Adding Songs to the Playlist

When you add a new song to the playlist, you create a new node and assign the song
data to it. The next pointer of the current last song node would then point to the newly
created node, making it the new last song in the playlist.

Playing Songs in the Playlist

To play the songs in the playlist, you start from the head of the list and play each song
in order until you reach the end of the list (NULL).

Removing Songs from the Playlist

When you want to remove a song from the playlist, you need to update the next
pointer of the previous song node to skip the node containing the song you want to
remove. Then, you can deallocate the memory for the removed node.

Advantages of Linked List in Playlists

Linked lists offer several advantages in certain situations compared to other data
structures like arrays. Here are some of the key advantages of linked lists:
(i) Dynamic Size: Linked lists can easily grow or shrink in size during runtime.
This is in contrast to arrays, which have a fixed size that needs to be declared in
advance.
(ii) Ease of Insertion and Deletion: Insertion and deletion of elements in a linked
list are generally faster and more efficient than in arrays. In a linked list, adding
or removing elements involves adjusting pointers, while in an array, elements
may need to be shifted to accommodate the change. Self-Instructional
Material 23

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

NOTES (iii) No Pre-allocation of Memory: Linked lists don’t require pre-allocation of


memory for a specific size, unlike arrays. Memory is allocated as needed, which
makes them more memory-efficient in certain scenarios.
(iv) Efficient Memory Utilization: Linked lists can efficiently utilize memory by
allocating space for each element separately and only using as much memory as
needed. This can reduce memory wastage compared to arrays, where a
contiguous block of memory is allocated even if not fully used.
(v) Ease of Implementation: Implementing linked lists is generally simpler than
managing dynamic arrays or other complex data structures. This simplicity can
be beneficial in terms of development time and code maintenance.
(vi) No Need for Resizing: Since linked lists don’t require resizing when elements
are added or removed, they avoid the overhead associated with resizing arrays,
making them more suitable for certain scenarios.
(vii) Ease of Merging and Splitting: Linked lists can be easily merged or split
without the need for moving large blocks of memory, as is often required in
arrays.
(viii) Support for Non-contiguous Memory: Linked lists can be used to represent
data that is scattered throughout the memory. This is in contrast to arrays, which
require contiguous memory locations.
(ix) Dynamic Data Structures: Linked lists are dynamic data structures that can
be adapted to various requirements, making them versatile in different scenarios.
It’s important to note that while linked lists have these advantages, they also
come with certain drawbacks. For example, random access to elements is less efficient
compared to arrays, and they require additional memory for storing pointers. The
choice of data structure depends on the specific requirements of the application and
the operations it needs to perform frequently.
By implementing a playlist as a linked list, music player applications can efficiently
manage and manipulate playlists, providing users with a seamless and customisable
music listening experience.

Self-Instructional
24 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Linked Lists: Dynamic Data Structure

NOTES
Note: Linked lists are a fundamental data structure in computer science, commonly
used to store and manipulate collections of data.
Unlike arrays, linked lists provide dynamic memory allocation, allowing efficient
insertion and deletion of elements. This lesson will cover the basic concepts of linked
lists and their implementation and provide examples and code snippets for better
understanding.

2.3 SINGLY LINKED LIST

A linked list has one or more nodes. Each node contains some data and a pointer (or
reference or link) to the next node in the list. The first node of the linked list is named
as the head. Correspondingly, the last node containing NULL indicates the end of the
list. Linked lists can be either singly (each node points to the next node) or doubly
linked (each node points to both the previous and next nodes).

Figure 2.1: Representation of a Singly Linked List

Figure 2.2: Representation of a Doubly Linked List

Self-Instructional
Material 25

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

NOTES Linked List Implementation

We’ll start by defining a basic structure for a node in a singly linked list:
struct Node {
int data;
Node* next;
};

Here, the data field represents the content stored in the node, and ‘next’ is a
pointer to the next or the subsequent node in the list.
To create a linked list, we need to initialize the head node and ensure the last
node points to NULL:
Node* head = NULL;

Insertion Operations

Inserting elements is a common operation in linked lists. We’ll cover three scenarios:
inserting at the beginning, end, and in the middle.

a. Insertion at the Beginning:

To add a new node at the beginning, we create a new node, assign its data, and update
the next pointer to the current head.
Finally, we update the head to point to the new node.

Figure 2.3: Insertion at the Beginning of a Linked List

Self-Instructional
26 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Linked Lists: Dynamic Data Structure

void insertAtBeginning(int value) { NOTES


Node* newNode = new Node;
newNode->data = value;
newNode->next = head;
head = newNode;
}

b. Insertion at the End:

To add a new node at the end, we have traverse through the list from the head node
until we reach the last node.
Then, we create a new node, assign its data, and update the next pointer of the
last node so that it now points to the newly created node.

Figure 2.4: Insertion at the end of a Linked List

void insertAtEnd(int value) {


Node* newNode = new Node;
newNode->data = value;
newNode->next = NULL;

if (head == NULL) {
head = newNode;
return;
}

Node* current = head;


Self-Instructional
Material 27

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

NOTES while (current->next != NULL) {


current = current->next;
}

current->next = newNode;
}

c. Insertion in the Middle:

To add a new node in the middle of the linked list, we have to first locate the desired
position.
Starting from the head node, we have to traverse the list until we reach the
desired position. Now, create a new node with the specific value to be added. Also,
and update the next pointers accordingly.

Figure 2.5: Insertion in the middle of a Linked List

void insertInMiddle(int value, int position) {


Node* newNode = new Node;
newNode->data = value;

Node* current = head;


int currentPosition = 1;

while (current != NULL && currentPosition < position) {

Self-Instructional
28 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Linked Lists: Dynamic Data Structure

current = current->next; NOTES


currentPosition++;
}

if (current == NULL) {
// Position exceeds the size of the list
// Handle the error or insert at the end
return;
}

newNode->next = current->next;
current->next = newNode;
}

Deletion Operations

Similar to insertion, deletion is a common operation. We’ll cover deletion from the
beginning, end, and middle of the list.

a. Deletion from the Beginning:

To delete the first node of a given linked list, we have to update the head pointer so
that it now points to the second node of the list. After doing this, de-allocate memory
for the old head.
void deleteFromBeginning() {
if (head == NULL) {
// Handle the error, list is empty
return;
}

Node* temp = head;


head = head->next;
delete temp;
}
Self-Instructional
Material 29

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

NOTES b. Deletion from the End:

To delete the last node, we traverse the list until we reach the second-to-last node.
We update its next pointer to NULL and deallocate memory for the last node.
void deleteFromEnd() {
if (head == NULL) {
// Handle the error, list is empty
return;
}

if (head->next == NULL) {
// Only one node in the list
delete head;
head = NULL;
return;
}

Node* current = head;


while (current->next->next != NULL) {
current = current->next;
}

delete current->next;
current->next = NULL;
}

c. Deletion from the Middle:

To delete a node from the middle, we need to locate the desired position and then do
the following steps:
 Find the node that just comes prior to the node to be deleted.
 Change the next of the previous node.
 Free or de-allocate the memory occupied by the node to be deleted.
Self-Instructional
30 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Linked Lists: Dynamic Data Structure

We traverse the list until we find the node before the position, update its next NOTES
pointer, and deallocate memory.

Figure 2.6: Deletion from the middle of a Linked List

void deleteFromMiddle(int position) {


if (head == NULL) {
// Handle the error, list is empty
return;
}

if (position == 1) {
deleteFromBeginning();
return;
}

Node* current = head;


int currentPosition = 1;

while (current != NULL && currentPosition < position -


1) {
current = current->next;
currentPosition++;
}

Self-Instructional
Material 31

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

NOTES if (current == NULL || current->next == NULL) {


// Position exceeds the size of the list
// Handle the error or delete from the end
return;
}

Node* temp = current->next;


current->next = current->next->next;
delete temp;
}

Traversal and Display

To traverse a linked list, we start from the head and move through each node, printing
its data until we reach NULL.
void displayList() {
Node* current = head;
while (current != NULL) {
cout << current->data << “ “;
current = current->next;
}
cout << endl;
}
current = current->next;
currentPosition++;
}

if (current == NULL || current->next == NULL) {


//position exceeds the size of the list
// Handle the error or delete from the end
return;
}

Self-Instructional
32 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Linked Lists: Dynamic Data Structure

Node* temp = current->next; NOTES


current->next = current->next->next;
delete temp;

2.4 DOUBLY LINKED LISTS

In this type of linked list, each node holds two pointer fields. A node is divided into
three parts. The first part points to the previous note, the second part holds the note
value and the last part Points to the next node. Pointers exist between adjacent notes
in both directions. The list can be traversed either in the forward direction or in the
backward direction.

Doubly linked lists are more convenient than singly-linked lists since we maintain
links for bi-directional traversing, which is the primary advantage of this list. Links in
both forward and backward directions allow references to the previous and the next
node in the sequence of notes very efficiently.

2.5 CIRCULARLY LINKED LISTS

A circular list is a list in which the Link field of the last note is made to point to the start
or the first note of the list.
In this list, each node is divided into two parts. The first part contains information
about the current node and the next part points to the next node. The last node points
back to the start of the list, which is why it is called a circular linked list.
In this type of linked list, the address of the last note contains the address of the
first node.
Self-Instructional
Material 33

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

NOTES

In-Text Questions
Q1. In a singly linked list, how many pointers does each node contain?
A. One B. Two
C. Three D. None
Q2. Which of the following is an advantage of a doubly linked list over a singly
linked list?
A. Efficient memory usage
B. Simplicity in traversal
C. Ability to traverse in both directions.
D. Lower time complexity for insertion
Q3. What is the time complexity to insert a node at the end of a singly linked list
with ‘n’ nodes?
A. O(1) B. O(log n)
C. O(n) D. O(n^2)
Q4. Which node in a linked list contains a null reference?
A. Last node B. Middle node
C. First node D. Any node in the list
Q5. What is the term used for a linked list where the last node points to the first
node, forming a circular structure?
A. Linear linked list B. Circular linked list.
C. Doubly linked list. D. Circular doubly linked list.

Self-Instructional
34 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Linked Lists: Dynamic Data Structure

NOTES
2.6 MEMORY ALLOCATION IN LINKED LIST

Together with the linked lists in memory, a particular list, which consists of unused
memory cells, is maintained. This list, which has its pointer, is called the list of available
space, the free storage list or the free pool.

This free storage list will also be called the AVAIL list.

Overflow and Underflow Conditions

Overflow will occur with linked lists when AVAIL = NULL, and there is an insertion.
Underflow will occur with linked lists when START = NULL and there is a
deletion.

2.7 SUMMARY

Linked lists are a versatile data structure for efficient insertion and deletion operations.
Understanding the implementation and operations of linked lists is crucial for mastering
Self-Instructional
Material 35

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

NOTES more complex data structures. A linked list is a dynamic data structure that consists of
nodes, each containing data and a reference to the next node in the sequence. This
structure allows for efficient memory utilisation and flexible data management. The key
points about linked lists include:

Key points:

 Flexibility and dynamic memory allocation: Linked lists provide flexibility in


managing data with dynamic memory allocation, allowing for efficient insertion
and deletion operations.
 Efficient insertion and deletion: Linked lists excel at performing insertions and
deletions at the beginning or end of the list with a time complexity of O(1),
making them suitable for scenarios where frequent modifications are required.
 Traversal and searching: Traversing a linked list takes O(n) time complexity, as
each node must be visited sequentially. Searching for a specific element also
requires traversing the list, which can be time-consuming for large lists.
 Different types of linked lists: Singly linked lists, doubly linked lists, and circular
linked lists offer various functionalities and performance characteristics to suit
different requirements.
 Memory overhead: Linked lists require additional memory to store pointers or
references, resulting in higher memory usage compared to other data structures
like arrays. This should be considered when memory efficiency is a concern.
 Comparisons with arrays: Linked lists offer advantages over arrays in terms of
dynamic memory allocation and efficient insertions/deletions, but arrays excel in
random access operations and can be more space-efficient for specific scenarios.
 Limitations: Linked lists have limitations, such as inefficient random access, slower
traversal compared to arrays, and increased memory overhead due to storing
additional pointers.
 Real-world applications: Linked lists find applications in various areas, including
implementing stacks, queues, graphs, and memory management systems.
 Choosing the proper data structure: The selection of a data structure should be
based on specific requirements. Linked lists are suitable for scenarios where
Self-Instructional
36 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Linked Lists: Dynamic Data Structure

efficient insertions/deletions and dynamic memory allocation are critical, while NOTES
other structures may be more suitable for different needs.
 Continual learning: Understanding the characteristics, strengths, and limitations
of linked lists is essential for designing efficient algorithms and data structures.
Further exploration and study can deepen knowledge and improve problem-
solving skills.

2.8 GLOSSARY

 Linked List: A linear collection of elements where each element points to the
next one in the sequence using pointers.
 Node: An individual element in a linked list containing both data and a reference
to the next node.
 Singly Linked List: A linked list where each node points only to the next node
in the sequence.
 Doubly Linked List: A linked list where each node has pointers to both the
next and previous nodes.
 Head: The first node of a linked list.
 Tail: The last node of a linked list.

2.9 ANSWERS TO IN-TEXT QUESTIONS

1. A. One
2. C. Ability to traverse in both directions.
3. C. O(n)
4. A. Last node
5. B. Circular linked list.
Self-Instructional
Material 37

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structures

NOTES
2.10 SELF-ASSESSMENT QUESTIONS

1. Define a linked list and explain its different types.


2. Compare and contrast linked lists with arrays. Discuss their advantages and
limitations.
3. Explain the concept of a doubly linked list and how it differs from a singly linked
list.
4. Discuss the importance of a head pointer in a linked list and its role in traversing
the list.
5. Explain the operations of insertion and deletion in a linked list, highlighting their
time complexities.
6. Describe the concept of a circular linked list and its applications.

2.11 REFERENCES

 Goodrich, M.T., Tamassia, R., & Mount, D., Data Structures and Algorithms
Analysis in C++, 2nd edition, Wiley, 2011.
 Cormen, T.H., Leiserson, C.E., Rivest, R. L., Stein C. Introduction to Algorithms,
4th edition, Prentice Hall of India, 2022.
 Drozdek, A., Data Structures and Algorithms in C++, 4th edition, Cengage
Learning, 2012.

2.12 SUGGESTED READINGS

 Sahni, S. Data Structures, Algorithms and Applications in C++, 2nd Edition,


Universities Press, 2011.
 Langsam Y., Augenstein, M. J., & Tanenbaum, A. M. Data Structures Using C
Self-Instructional
38 Material and C++, Pearson, 2015.

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Stacks - The LIFO Data Structure

LESSON 3 NOTES

STACKS - THE LIFO DATA STRUCTURE

Name of Author: Dr Geetika Vashishta


Designation: Assistant Professor,
College of Vocational Studies
Email: [email protected]

Structure
3.1 Learning Objectives
3.2 Introduction
3.3 Stack as an ADT
3.4 Implementing Stacks using Arrays
3.5 Implementing stacks using Linked Lists
3.6 Applications of Stacks
3.7 Summary
3.8 Glossary
3.9 Answers to In-Text Questions
3.10 Self-Assessment Questions
3.11 References
3.12 Suggested Readings

3.1 LEARNING OBJECTIVES

 To define what a stack is and understand its basic characteristics.


 To learn the fundamental operations associated with stacks, including push
(inserting an element onto the stack) and pop (removing the top element from
the stack).
 To understand the concept of an Abstract Data Type and how a stack fits into
this abstraction.
 To learn how to implement a stack using arrays and linked list.
Self-Instructional
Material 39

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES
3.2 INTRODUCTION

Imagine you are at a cafeteria, and you see a stack of plates on a table. Each plate is
placed on top of another, forming a stack. Here, the topmost plate is the only accessible
one, and you can perform two primary operations: adding a plate to the stack or
removing a plate from the Stack.
 Push Operation: When you want to add a plate to the stack, you take a new
plate and place it on top of the stack. This action is similar to pushing an element
onto a stack in programming. The new plate becomes the topmost plate, and
any existing plates remain underneath it. By adding plates one by one, you keep
extending the stack vertically.
 Pop Operation: Now, let us say you want to remove a plate from the stack.
You can only remove the topmost plate, as the plates underneath are not
accessible until the top plate is removed. You lift the topmost plate from the
stack and place it on your tray or table. This action corresponds to the pop
operation in programming. The plate that was popped off is no longer part of
the stack, and you can access the plate beneath it if needed.
 Last-In-First-Out (LIFO) Principle: The concept of a stack follows the Last-
In-First-Out (LIFO) principle. It means that the last plate added to the Stack is
the first one to be removed. In our cafeteria example, if you add plates in the
order A, B, and C, then C would be the topmost plate. When you start removing
plates, C would be the first one to be removed, followed by B and then A. This
principle ensures that the most recently added element is always accessible and
processed first.
 Stack Overflow and Underflow: In the cafeteria scenario, there is a limit to
the number of plates you can stack. If you keep adding plates beyond the
maximum capacity, you will encounter a stack overflow situation. Similarly, if
you try to remove a plate from an empty stack, you will face a stack underflow
situation. These scenarios reflect the limitations and constraints of stacks in
programming as well.
Overall, the Stack of plates in a cafeteria serves as a tangible example of a
Self-Instructional
40 Material stack data structure. Understanding how plates are added and removed and the LIFO

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Stacks - The LIFO Data Structure

principle associated with them can help in comprehending the concept of stacks in NOTES
programming.

3.3 STACKS AS AN ADT

A stack is a linear data structure that follows the Last-In-First-Out (LIFO) principle.
It can be visualised as a stack of plates, where the topmost plate is the only accessible
one. In programming, a stack allows operations only at one end, known as the top.
Elements are added or removed from the top of the stack, similar to stacking or
unstacking plates.

Stack Operations

The primary operations performed on a stack are as follows:


a) Push: Adds an element to the top of the Stack.
b) Pop: Removes the topmost element from the Stack.
c) Top/Peek: Returns the value of the topmost element without removing it.
d) Empty: Checks if the Stack is empty.
e) Size: Returns the number of elements in the Stack.

Self-Instructional
Material 41
Figure 3.1: Diagrammatic Representation of a Stack

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES
3.4 IMPLEMENTATION OF STACK USING ARRAYS

In C++, stacks can be implemented using arrays or linked lists. Here, we will focus on
implementing stacks using arrays.

Figure 3.2: Demonstration of Push and Pop Operations

3.4.1 Stack Implementation using Arrays

To implement a stack using an array, we need to maintain a variable to track the


topmost element’s index and an array to store the elements. Let us create a basic
stack class using an array and implement the stack operations.
#include <iostream>
using namespace std;
const int MAX_SIZE = 100; // Maximum size of the stack
class Stack {
private:
int top; //index of the topmost element
int elements[MAX_SIZE]; // Array to store elements
public:

Self-Instructional Stack() {
42 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Stacks - The LIFO Data Structure

top = -1; //Initialising top as -1 to indicate an empty NOTES


stack
}
bool isEmpty() {
return (top == -1);
}
bool isFull() {
return (top == MAX_SIZE - 1);
}
void push(int value) {
if (isFull()) {
cout << “Stack overflow! Cannot push element” <<
value << endl;
return;
}
elements[++top] = value;
cout << “Element” << value << “ pushed to the stack.” <<
endl;
}

void pop() {
if (isEmpty()) {
cout << “Stack underflow! Cannot pop element.” <<
endl;
return;
}
int poppedElement = elements[top--];
cout << “Popped element:” << poppedElement << endl;
}
int peek() {
if (isEmpty()) {
cout << “Stack is empty!” << endl;
return -1; // Returning -1 as an error value
}
Self-Instructional
Material 43

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES return elements[top];


}
int size() {
return (top + 1);
}
};
int main() {
Stack stack;
stack.push(10);
stack.push(20);
stack.push(30);
cout << “Stack size:” << stack.size() << endl;
cout << “Top element:” << stack.peek() << endl;
stack.pop();
stack.pop();
cout << “Stack size after popping:” << Stack.size() << endl;
cout << “Top element after popping:” << Stack.peek() << endl;
return 0;
}

Explanation

The Stack class has private member variables, top and elements, to maintain the top
index and store the stack elements, respectively.
The constructor initialises top as -1, indicating an empty stack.
The isEmpty() function checks if the stack is empty by comparing the value of
top with -1.
The isFull() function checks if the stack is full by comparing the value of top with
the maximum size of the stack.
The push() function adds an element to the stack if it is not full. It increments top
and assigns the value to the corresponding index in elements[].

Self-Instructional
44 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Stacks - The LIFO Data Structure

The pop() function removes the topmost element from the stack if it is not NOTES
empty. It retrieves the element at the index top, decrements the top, and prints the
popped element.
The peek() function returns the value of the topmost element without removing
it. It checks if the stack is empty and returns -1 as an error value.
The size() function returns the number of elements in the stack by adding 1 to
the top.
In the main() function, we create an instance of the stack class and perform
stack operations.

Sample Output
Element 10 pushed to the Stack.
Element 20 pushed to the Stack.
Element 30 pushed to the Stack.
Stack size: 3
Top element: 30
Popped element: 30
Popped element: 20
Stack size after popping: 1
Top element after popping: 10

3.5 IMPLEMENTING STACKS USING LINKED LISTS

Linked lists provide an efficient way to implement stacks dynamically, allowing for
easy insertion and deletion of elements.

Node Structure

To implement a stack using a linked list, we define a node structure. Each node represents
an element in the stack and contains two components: the data value and a pointer to
the next node. The node structure can be defined as follows:
Self-Instructional
Material 45

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES class Node {


public:
int data;
Node* next;
Node(int value) {
data = value;
next = nullptr; }
};

Top Pointer
In a linked list-based stack implementation, we maintain a pointer called ‘top’, which
points to the topmost element of the stack. Initially, when the stack is empty, the top
pointer is set to nullptr.
Node* top = nullptr;

Push Operation
The push operation adds a new element to the top of the stack. To implement push,
we create a new node, assign the data value, and update the next pointer to point to
the current top node. Finally, we set the top pointer to the newly added node.
void push(int value) {
Node* newNode = new Node(value);
newNode->next = top;
top = newNode;
}

Pop Operation

The pop operation removes the topmost element from the stack. To avoid any errors,
we need to check if the stack is empty before performing the pop operation. To pop
an element, we first store the address of the top node in a temporary pointer, update
the top pointer to the next node, delete the temporary node, and return the popped
element.

Self-Instructional
46 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Stacks - The LIFO Data Structure

void pop() { NOTES


if (isEmpty()) {
// Stack underflow error
return; }
Node* temp = top;
top = top->next;
delete temp;
}

Peek Operation

The peek operation retrieves the value of the topmost element without removing it
from the stack. Similar to the pop operation, we need to check if the stack is empty. If
not, we return the data value of the top node.
int peek() {
if (isEmpty()) {
//Stack is empty
return -1; // Or any other appropriate error value
}
return top->data;
}
//isEmpty operation:
//The isEmpty operation checks if the Stack is empty by verifying
if the top pointer is nullptr.
bool isEmpty() {
return (top == nullptr);
}

Time Complexity

The time complexity of the push, pop, and peek operations in a linked list-based stack
implementation is O(1) since they involve constant-time operations.

Self-Instructional
Material 47

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES By understanding the theory behind implementing a stack using linked lists, you can
leverage this data structure to solve various programming problems efficiently. Let us
have a look at the complete program:
#include <iostream>
using namespace std;
class Node {
public:
int data;
Node* next;

Node(int value) {
data = value;
next = nullptr;
}
};

class Stack {
private:
Node* top; //pointer to the top of the Stack

public:
Stack() {
top = nullptr; //Initialising top as nullptr to indicate
an empty stack
}

bool isEmpty() {
return (top == nullptr);
}

void push(int value) {


Node* newNode = new Node(value);
newNode->next = top;
Self-Instructional top = newNode;
48 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Stacks - The LIFO Data Structure

cout << “Element” << value << “ pushed to the stack.” << NOTES
endl;
}

void pop() {
if (isEmpty()) {
cout << “Stack underflow! Cannot pop element.” <<
endl;
return;
}
Node* temp = top;
int poppedElement = temp->data;
top = top->next;
delete temp;
cout << “Popped element:” << poppedElement << endl;
}

int peek() {
if (isEmpty()) {
cout << “Stack is empty!” << endl;
return -1; // Returning -1 as an error value
}
return top->data;
}

int size() {
int count = 0;
Node* current = top;
while (current != nullptr) {
count++;
current = current->next;
}

Self-Instructional
Material 49

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES return count;


}
};

int main() {
Stack stack;
stack.push(10);
stack.push(20);
stack.push(30);
cout << “Stack size:” << stack.size() << endl;
cout << “Top element:” << stack.peek() << endl;
stack.pop();
stack.pop();
cout << “Stack size after popping:” << Stack.size() << endl;
cout << “Top element after popping:” << Stack.peek() << endl;
return 0;
}

In this implementation, we use a linked list to represent the stack. Each element of the
stack is stored in a node, which contains the data value and a pointer to the next node.
 The Node class represents a node in the linked list. It contains a data member
to store the value and a next pointer to point to the next node in the stack.
 The Stack class has a private member variable top, which is a pointer to the top
node of the stack. The constructor initialises the top as nullptr to indicate an
empty stack.
 The isEmpty() function checks if the stack is empty by checking if the top is
nullptr.
 The push() function adds a new element to the top of the stack. It creates a new
node, assigns the value, updates the next pointer to the current top node, and
sets the top pointer to the new node.
 The pop() function removes the topmost element from the stack. It checks if the
stack is empty, assigns the top node to a temporary pointer, updates the top
Self-Instructional
pointer to the next node, deletes the temporary node, and prints the popped
50 Material element.

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Stacks - The LIFO Data Structure

 The peek() function returns the value of the topmost element without removing NOTES
it. It checks if the Stack is empty and returns -1 as an error value.
 The size() function calculates the number of elements in the stack by traversing
the linked list and counting the nodes.
 In the main() function, we create an instance of the stack class and perform
stack operations.
This implementation of a stack using a linked list provides dynamic memory
allocation and allows the stack to grow and shrink as elements are pushed and popped.

Stack Overflow

Stack Overflow is a situation that can occur when a stack data structure exceeds its
maximum capacity, resulting in an error. Let us explore this concept further with code
examples. In programming, a stack is typically implemented using arrays or linked
lists. The size of an array-based stack is predetermined, meaning it has a fixed capacity.
When attempting to push elements beyond this capacity, a stack overflow error occurs.
Consider the following code snippet that demonstrates a stack implementation using
an array:
#include <iostream>
#define MAX_SIZE 5

class Stack {
private:
int arr[MAX_SIZE];
int top;

public:
Stack() {
top = -1; //Initialising top as -1 to indicate an empty
stack
}

bool isFull() {
Self-Instructional
return (top == MAX_SIZE - 1); Material 51

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES }

void push(int value) {


if (isFull()) {
std::cout << “Stack overflow! Cannot push element”
<< value << std::endl;
return;
}
arr[++top] = value;
std::cout << “Element” << value << “ pushed to the stack.”
<< std::endl;
}
};

In the above code, the isFull() function checks if the stack is full by comparing
the value of top with MAX_SIZE - 1. If the condition is true, it means the Stack is full.
The push() function adds an element to the stack if it is not full. It increments top
and assigns the value to the corresponding index in the arr[] array. However, before
pushing, it checks if the stack is already full. If it is, a stack overflow error message is
displayed.
Now, let us illustrate a scenario where a stack overflow occurs:
int main() {
Stack stack;
stack.push(10);
stack.push(20);
stack.push(30);
stack.push(40);
stack.push(50);
stack.push(60); // Trying to push beyond the maximum capacity
return 0;
}

In the above example, we attempt to push six elements into the stack, which has
a maximum capacity of five. After pushing elements 10, 20, 30, 40, and 50, the next
Self-Instructional
52 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Stacks - The LIFO Data Structure

push operation stack.push(60) exceeds the Stack’s capacity. Consequently, a stack NOTES
overflow error message will be displayed.
Stack overflow errors are critical because they can lead to program crashes or
unexpected behaviour. It is crucial to handle stack capacity appropriately and ensure
that elements are pushed within the Stack’s limits.
Remember, understanding stack overflow situations helps in designing robust
programs and managing data structures effectively.

3.6 APPLICATIONS OF STACKS

Stacks find applications in various domains due to their Last In, First Out (LIFO)
nature and efficient operations. Some common applications of stacks are briefed below:

1. Function Call Management

Stacks are used to manage function calls in programming languages. When a function
is called, its information is pushed onto the call stack, and it is popped off when the
function returns.

2. Expression Evaluation

Stacks are employed in evaluating expressions, especially in converting infix expressions


to postfix form and subsequently calculating the result.

3. Undo Mechanisms

Many applications, such as text editors and graphic design software, use stacks to
implement undo functionalities. Each operation gets pushed onto the stack, enabling a
stepwise reversal.

4. Backtracking Algorithms

Backtracking algorithms, like those used in maze-solving or chess game simulations,


often utilise stacks to keep track of the choices and backtrack when needed.
Self-Instructional
Material 53

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES 5. Browser History

Stacks are employed in maintaining the backward and forward navigation history in
web browsers.

6. Memory Management

Some programming languages use a call stack for managing memory, especially for
local variables and function calls.

7. Parentheses Matching

Stacks are used to check the balance of parentheses in mathematical expressions. An


open parenthesis is pushed onto the stack, and a closing parenthesis pops it off.

8. Algorithmic Problems

Various algorithms, such as Depth - First Search (DFS) in graph traversal or tower of
Hanoi, can be efficiently solved using stacks.

9. Postfix Notation

Stacks are employed in converting infix expressions to postfix (Reverse Polish Notation)
for easier evaluation.

10. Expression Parsing

Compilers and interpreters use stacks to parse and evaluate mathematical expressions
during the compilation or interpretation process.

11. Task Management in Operating Systems

Operating systems use stacks to manage the execution of processes and tasks, including
keeping track of function calls within a process.

12. Undo/Redo Features in Software

Beyond simple undo, stacks can be extended to implement redo features, allowing
Self-Instructional users to revert and reapply multiple actions.
54 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Stacks - The LIFO Data Structure

13. Syntax Parsing NOTES

Stacks are used in syntax parsing during the compilation of programming languages to
ensure correct syntax structure.

14. Call Stack in Recursion

Recursion heavily relies on stacks, where each recursive call pushes a new frame onto
the call stack.

15. Simulation of Real-world Scenarios

Stacks are employed in simulating real-world scenarios where items are stacked or
processed in a Last In, First Out manner.
The versatility of stacks makes them a fundamental and widely applicable data
structure in computer science, playing a crucial role in various algorithms, systems,
and applications.

In-Text Questions
Q1. Which data structure follows the Last in First out (LIFO) principle?
A. Queue B. Stack
C. Array D. Linked List
Q2. What operation in a stack adds an element to the top of the Stack?
A. Push B. Pop
C. Insert D. Enqueue

3.7 SUMMARY

In this lesson, we introduced the concept of stacks and implemented a basic stack
class in C++ using arrays. We covered stack operations such as push, pop, peek,
isEmpty, and size. A stack is a fundamental data structure that adheres to the Last In,
First Out (LIFO) principle. Elements are added or removed from one end, commonly
Self-Instructional
Material 55

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES referred to as the “top”. The primary operations include “push”, which adds an element
to the top of the stack, and “pop”, which removes the top element. The “peek” operation
allows viewing the top element without removal. Stacks are used in various applications,
such as managing function calls in a call stack, evaluating mathematical expressions in
reverse Polish notation, and implementing undo mechanisms. They are pivotal for
maintaining order and tracking program execution flow, making them a versatile tool in
computer science and programming.

Key Points

 Stacks are a fundamental data structure that follows the Last-In-First-Out


(LIFO) principle.
 They are commonly used in various algorithms and applications, including function
calls, expression evaluation, backtracking, and more.
 Stacks can be implemented using arrays or linked lists, each with its advantages
and considerations.
 Array-based stacks offer constant-time access and have a fixed size, while
linked list-based stacks provide dynamic memory allocation and flexibility.
 Stack operations include push (adding an element), pop (removing the top
element), peek (accessing the top element), isEmpty (checking if the Stack is
empty), and size (determining the number of elements).
 Stacks have a variety of applications in programming, such as undo-redo
mechanisms, browser history, depth-first search algorithms, and handling
recursive function calls.
 Understanding stacks and their implementation allows for efficient problem-
solving and algorithm design, particularly in situations where the order of elements
is crucial.
 Stacks offer simplicity and efficiency in managing and manipulating data,
making them an essential concept in computer science and software
development.

Self-Instructional
56 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Stacks - The LIFO Data Structure

NOTES
3.8 GLOSSARY

 Stack: A linear data structure that follows the Last In, First Out (LIFO) principle
for adding and removing elements.
 Push: The operation to add an element to the top of the Stack.
 Pop: The operation to remove the top element from the Stack.
 Peek/Top: Accessing the top element of the stack without removing it.
 Empty Stack: A stack is considered empty when it contains no elements.
 Full Stack: Some stack implementations have a maximum capacity; a stack is
considered full when it reaches this capacity.
 Stack Overflow: A situation where elements cannot be pushed onto a stack
because it has reached its maximum capacity.
 Stack Underflow: A situation where elements cannot be popped from a stack
because it is empty.

3.9 ANSWERS TO IN-TEXT QUESTIONS

1. B. Stack
2. A. Push

3.10 SELF-ASSESSMENT QUESTIONS

1. Define a stack and explain its fundamental operations.


2. Differentiate between a stack and a queue, highlighting their key characteristics.
3. Explain the concept of LIFO (Last In, First Out) and its relevance in stacks.
4. Discuss the applications of stacks in computer science and real-life scenarios.
Self-Instructional
Material 57

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES 5. Describe the implementation of a stack using arrays and linked lists. Highlight
their advantages and limitations.
6. Explain the process of pushing and popping elements in a stack and their
associated time complexities.

3.11 REFERENCES

 Goodrich, M.T., Tamassia, R., & Mount, D., Data Structures and Algorithms
Analysis in C++, 2nd edition, Wiley, 2011.
 Cormen, T.H., Leiserson, C.E., Rivest, R. L., Stein C. Introduction to Algorithms,
4th edition, Prentice Hall of India, 2022.
 Drozdek, A., Data Structures and Algorithms in C++, 4th edition, Cengage
Learning, 2012.

3.12 SUGGESTED READINGS

 Sahni, S. Data Structures, Algorithms and Applications in C++, 2nd Edition,


Universities Press, 2011.
 Langsam Y., Augenstein, M. J., & Tanenbaum, A. M. Data Structures Using C
and C++, Pearson, 2015.

Self-Instructional
58 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Queue - The FIFO Data Structure

LESSON 4 NOTES

QUEUE - THE FIFO DATA STRUCTURE

Name of Author: Ms Aishwarya Anand Arora


Designation: Assistant Professor,
Department of Computer Science, School of
Open Learning, University of Delhi
Email: [email protected]

Structure
4.1 Learning Objectives
4.2 Introduction
4.3 Queues: Queue as an ADT
4.4 Implementing Queues Using Arrays
4.4.1 Inserting an Element in a Queue Using Arrays
4.4.2 Deleting an Element in a Queue Using Arrays
4.4.3 Drawbacks
4.5 Implementing Queues Using Linked Lists
4.5.1 Insert an Element in a Queue Using Linked List
4.5.2 Delete an Element from a Queue Using Linked List
4.6 Double-ended Queue as an ADT
4.7 Summary
4.8 Glossary
4.9 Answers to In-Text Questions
4.10 Self-Assessment Questions
4.11 References
4.12 Suggested Readings

4.1 LEARNING OBJECTIVES

 To understand the core concept of queues, which follows the First In, First Out
order.
Self-Instructional
Material 59

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES  To learn the enqueue operation (adding an element to the rear) and dequeue
operation (removing an element from the front).
 To learn about double-ended queues that support operations at both ends.

4.2 INTRODUCTION

Queues, a fundamental data structure in computer science, embody the principle of


First In, First Out (FIFO), where the first element added is the first to be removed.
Comparable to waiting in line at a grocery store, elements join a queue, patiently
awaiting their turn for processing. The core operations, enqueue (adding an element)
and dequeue (removing an element), form the backbone of queues. Beyond the linear
structure, circular queues address space utilisation concerns. In contrast, priority queues
allow elements to be managed based on assigned priorities. As an essential concept,
queues find applications in various domains, from efficiently exploring graphs using
Breadth-First Search (BFS) to coordinating tasks in multi-threaded environments.
Understanding queues is key to building efficient algorithms, managing resources, and
solving real-world problems systematically.
Queues or linear list of elements. They work on the concept of first in, first out.
The elements in a queue are inserted from the rear end and deleted from the front.
When elements are added to a queue, the rear is incremented by one. Whenever any
element is deleted from the queue, the front is incremented by one.

Working of Queue

Queue operations work as follows:


 Two pointers FRONT and REAR.
 FRONT track the first element of the queue.
 REAR track the last element of the queue.

Self-Instructional
60 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Queue - The FIFO Data Structure

NOTES

4.3 QUEUES: QUEUE AS AN ADT

A queue is a useful data structure in programming as it is similar to a canteen queue


outside a canteen where the first person entering the queue is the first person who gets
food from the canteen.
It follows the first in, first out (FIFO) rule, where the item that goes in first is the
item that comes out first. In terms of programming, adding elements in a queue is
called Enqueue, and removing elements from the queue is called DeQueue.
Queues can be implemented in any programming language like C, C++, Java
and many more.
Queue (ADT) is an object that allows various operations:
1. Enqueue: Elements are added to the queue.
2. DeQueue: In this operation, elements are removed from the front of the queue.
3. Is empty: It checks whether the queue is empty or not.
4. IsFull: It checks whether the queue is full or not.
5. Peep: It gets the value of the front of the queue without removing any element.

Self-Instructional
Material 61

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES
4.4 IMPLEMENTING QUEUES USING ARRAYS

We can easily represent queues using a linear array. Two variables can be used, which
are front and rear. They are implemented in the case of every queue, and the front and
rear variables point to the position from where elements can be inserted and deleted in
the queue.
In the figure below, we can see that there is an empty array that represents a
queue. When three elements, A, B, and C, are inserted into the queue, the value of the
front is 1, and the rear is 3.
When the Dequeue operation is performed, and element A is deleted. The value
of the front becomes 2, and the rear becomes 3.
Again, when three more elements are inserted, D, E, and F, the value of the
front becomes 2, and the rear is incremented and becomes 6.
When again another element, G, is inserted, notice that because there is no
more place left in the queue, it checks the initial position, and because it is empty, the
value of the rear becomes 1, and the front is at 2.

4.4.1 Inserting an Element in a Queue Using Arrays

You can insert any element in a queue by first checking if the queue is already full and
comparing the front and the rear values. If the rear is at the last position and the front
is at the first position, then the queue is full, and no element can be inserted in this
queue. This is known as an overflow error, if the item to be inserted takes the first
position then in this case front and rear are equal to 0. Likewise, if more elements are
inserted, then the rear value gets incremented by one.

Algorithm to Insert an Element in a Queue Using Arrays


Step 1: IF REAR = MAX - 1
Write OVERFLOW
Go to step 2
[END OF IF]
Self-Instructional
62 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Queue - The FIFO Data Structure

Step 2: IF FRONT = -1 and REAR = -1 NOTES


SET FRONT = REAR = 0
ELSE
SET REAR = REAR + 1
[END OF IF]
Step 3: Set QUEUE[REAR] = NUM
Step 4: EXIT

Program to Insert an Element in a Queue Using Arrays


void insert (int queue[], int max, int front, int rear, int item)
{
if (rear + 1 == max)
{
printf(“overflow”);
}
else
{
if(front == -1 && rear == -1)
{
front = 0;
rear = 0;
}
else
{
rear = rear + 1;
}
queue[rear]=item;
}
}

4.4.2 Deleting an Element in a Queue Using Arrays

You can delete an element from the queue by first checking if there is an underflow
error or not. When the front value is greater than the rear value, then the queue is Self-Instructional
Material 63

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES empty, and no element can be deleted from this queue; this is an underflow error.
Otherwise, keep increasing the value of the front by one as one element is deleted
from the queue; the front value is incremented by one as elements are deleted from the
front end of the queue.

Algorithm to Delete an Element from a Queue Using Arrays


Step 1: IF FRONT = -1 or FRONT > REAR
Write UNDERFLOW
ELSE
SET VAL = QUEUE[FRONT]
SET FRONT = FRONT + 1
[END OF IF]
Step 2: EXIT

Program to Delete an Element from a Queue Using Arrays


int delete (int queue[], int max, int front, int rear)
{
int y;
if (front == -1 || front > rear)

{
printf(“underflow”);
}
else
{
y = queue[front];
if(front == rear)
{
front = rear = -1;
else
front = front + 1;

}
Self-Instructional
64 Material return y;

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Queue - The FIFO Data Structure

} NOTES
}

4.4.3 Drawbacks

Several techniques are used for creating queues, out of which array implementation is
an easy technique. However, some drawbacks are associated with this technique.
1. Memory wastage: The space that the array utilises to store elements of the
queue can never be reused to store the elements of that queue because the
elements can only be inserted at the front end. The value in the front might be
high so that all the spaces before that can never be filled.
2. Deciding the size of the array: One of the most common problems while
using array implementation in queue is that the size of the array needs to be
declared in advance, due to which the queue cannot be extended at runtime
depending upon the problem statement. As the size of the array is fixed, it is
impossible to extend its size if required.

4.5 IMPLEMENTING QUEUES USING LINKED LISTS

There are a number of drawbacks that are associated with the array implementation of
the queue. One alternative to this implementation is the linked list implementation of the
queue. The storage requirement of a linked representation of a queue is higher than that
of an array. However, dynamic allocation is possible in this representation. In this
representation, each note of the queue consists of two parts: a data part and a link part.

Self-Instructional
Material 65

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES Each element of the queue points to the immediate next element which is placed
in the memory. In this representation, two pointers are maintained in the memory: the
front pointer and the rear pointer. The front pointer contains the address of the starting
element of the queue, and the rear pointer contains the address of the last element,
which is there in the queue.
Insertion and deletion are performed at the rear and the front, respectively, like
the array representation. Although the pointers need to be maintained and updated
accordingly, if both the rear and the front are null, then it indicates that the queue is
empty.
There are a number of operations that can be performed on queues, and two
basic operations that can be implemented on linked queues are insert and delete.

4.5.1 Insert an Element in a Queue Using Linked List

The insert operation modifies the cube by adding an element at the end of the queue.
All the elements in the queue are added from the rare position, which increments the
rare value by one. The new element is the last element of the queue. First, memory is
allocated to the new note; we can insert the element into the empty Q by incrementing
the front by one if there is no element in the queue. In another scenario, when Q
contains a few elements and the front is not equal to null, then we need to update the
last Notes pointer.

Algorithm to Insert an Element in a Queue Using Linked List


Step 1: Allocate the space for the new node PTR
Step 2: SET PTR -> DATA = VAL
Step 3: IF FRONT = NULL
SET FRONT = REAR = PTR
SET FRONT -> NEXT = REAR -> NEXT = NULL
ELSE
SET REAR -> NEXT = PTR
SET REAR = PTR
SET REAR -> NEXT = NULL
[END OF IF]
Self-Instructional
66 Material Step 4: END

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Queue - The FIFO Data Structure

Program to Insert an Element in a Queue Using Linked List NOTES


void insert(struct node *ptr, int item; )
{

ptr = (struct node *) malloc (sizeof(struct node));


if(ptr == NULL)
{
printf(“\nOVERFLOW\n”);
return;
}
else
{
ptr -> data = item;
if(front == NULL)
{
front = ptr;
rear = ptr;
front -> next = NULL;
rear -> next = NULL;
}
else
{
rear -> next = ptr;
rear = ptr;
rear->next = NULL;
}
}
}

4.5.2 Delete an Element from a Queue Using Linked List

The deletion operation allows you to remove elements that were inserted in the queue.
Self-Instructional
Elements are deleted from the first from the front position of the queue. If the front is Material 67

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES equal to null, which means that the list is empty, then in this case, it is an underflow
error, and no element can be deleted from the queue; otherwise, the front pointer is
shifted to the next node, and the front-most node is deleted.

Algorithm to Delete an Element from a Queue Using Linked List


Step 1: IF FRONT = NULL
Write “ Underflow ”
Go to Step 5
[END OF IF]
Step 2: SET PTR = FRONT
Step 3: SET FRONT = FRONT -> NEXT
Step 4: FREE PTR
Step 5: END

Program to Delete an Element from a Queue using Linked List


void delete (struct node *ptr)
{
if(front == NULL)
{
printf(“\nUNDERFLOW\n”);
return;
}
else
{
ptr = front;
front = front -> next;
free(ptr);
}
}
Self-Instructional
68 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Queue - The FIFO Data Structure

NOTES
4.6 DOUBLE-ENDED QUEUE AS AN ADT

A queue is a data structure in which whatever comes in first goes out first. It follows
the FIFO (first in, first out) policy. When any element needs to be inserted in a queue,
it needs to be done from the rear end or the tail of the queue. In contrast, deletion is
done from the other end, which is the front end or the head of the queue. In the real
world, queues are used in various scenarios like a Canteen, a ticket queue outside a
cinema hall, and more.
A dequeue stands for a double-ended queue. It is also a linear data structure
like a queue where insertion and deletion operations both are performed from both
ends. It is a generalised version of a queue in which there is no restriction as to from
where the elements need to be inserted or deleted; there are two types of dequeue:
Input-restricted Queue and Output-restricted queue.

In an input-restricted queue, insertion operation is performed only from one


end, which can be either rear or front. In contrast, deletion operation can be performed
from both ends.
In an output-restricted queue, deletion operation can be performed only from
one end, which is either front or rear. In contrast, insertion operations can be performed
from both ends.
There are a number of applications for dequeue:
1. They can be used in both stacks and queues, as they support both operations
2. Dequeue can also be used as a palindrome checker. It can read a string from
both ends, and in a palindrome, the string should be the same when read from
either of the two ends.

Self-Instructional
Material 69

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES
In-Text Questions
Q1. In a queue, which element gets removed first?
A. Element at the front B. Element at the rear
C. Random element D. Element at the center
Q2. Which of the following is an example of a non-linear data structure?
A. Stack B. Queue
C. Linked List D. Binary Tree
Q3. What is the data structure used to implement a queue using two stacks?
A. Linked List B. Array
C. Queue D. Stack

4.7 SUMMARY

Queues and Deques (Double-ended queues) are pivotal data structures that govern
the orderly processing of elements in a computer program. Queues adhere to the First
In, First Out (FIFO) principle, where elements are enqueued at the rear and dequeued
from the front, mimicking real-world scenarios like waiting lines. This structure is
fundamental for tasks like breadth-first search in graphs or managing processes in an
orderly manner. On the other hand, Deques extend this functionality, allowing operations
at both ends and enabling flexibility in handling data. Deques are particularly useful in
scenarios where elements need to be added or removed from both the front and rear,
providing a versatile tool for various algorithms and applications. Both queues and
deques play integral roles in data processing, synchronisation, and memory management,
offering structured solutions to a wide array of programming challenges.

4.8 GLOSSARY

 Queue: A linear data structure that follows the First In, First Out (FIFO) principle
Self-Instructional
70 Material for adding and removing elements.

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Queue - The FIFO Data Structure

 Enqueue: Adding an element to the rear/end of the Queue. NOTES


 Dequeue: Removing an element from the front/head of the Queue.
 Front: The first element in the Queue.
 Rear/End: The last element in the Queue.
 Deque: A generalised queue data structure that allows insertion and deletion at
both ends (front and rear).
 Insert Front: Adding an element to the front of the deque.
 Insert Rear: Adding an element to the rear of the deque.
 Remove Front: Removing an element from the front of the deque.
 Remove Rear: Removing an element from the rear of the deque.
 Circular Queue: A type of queue where the rear and front pointers wrap
around, forming a circle.

4.9 ANSWERS TO IN-TEXT QUESTIONS

1. A. Element at the front


2. D. Binary Tree
3. D. Stack

4.10 SELF-ASSESSMENT QUESTIONS

1. Define a queue and explain its fundamental operations.


2. Differentiate between a queue and a stack, highlighting their key characteristics.
3. Discuss the concept of FIFO (First In, First Out) and its relevance in queues.
4. Explain the applications of queues in computer science and real-life scenarios.
5. Describe the implementation of a queue using arrays and linked lists. Highlight
their advantages and limitations. Self-Instructional
Material 71

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES 6. Explain the process of enqueue and dequeue operations in a queue and their
associated time complexities.
7. Discuss the concept of priority queues and their implementation using different
data structures.

4.11 REFERENCES

 Goodrich, M.T., Tamassia, R., & Mount, D., Data Structures and Algorithms
Analysis in C++, 2nd edition, Wiley, 2011.
 Cormen, T.H., Leiserson, C.E., Rivest, R. L., Stein C. Introduction to Algorithms,
4th edition, Prentice Hall of India, 2022.
 Drozdek, A., Data Structures and Algorithms in C++, 4th edition, Cengage
Learning, 2012.

4.12 SUGGESTED READINGS

 Sahni, S. Data Structures, Algorithms and Applications in C++, 2nd Edition,


Universities Press, 2011.
 Langsam Y., Augenstein, M. J., & Tanenbaum, A. M. Data Structures Using C
and C++, Pearson, 2015.

Self-Instructional
72 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
UNIT II

LESSON 5 SEARCHING AND SORTING


ALGORITHMS - EFFICIENT DATA
MANIPULATION TECHNIQUES
Searching and Sorting Algorithms - Efficient Data Manipulation Techniques

LESSON 5 NOTES

SEARCHING AND SORTING ALGORITHMS -


EFFICIENT DATA MANIPULATION TECHNIQUES

Name of Author: Dr Geetika Vashishta


Designation: Assistant Professor,
College of Vocational Studies
Email: [email protected]
Structure
5.1 Learning Objectives
5.2 Introduction
5.3 Linear Search
5.4 Binary Search
5.5 Bubble Sort
5.6 Insertion Sort
5.7 Selection Sort
5.8 Merge Sort
5.9 Quick Sort
5.10 Count Sort
5.11 When to use which sorting algorithm?
5.12 Summary
5.13 Glossary
5.14 Answers to In-Text Questions
5.15 Self-Assessment Questions
5.16 References
5.17 Suggested Readings

5.1 LEARNING OBJECTIVES

 To learn and implement searching algorithms.


 To learn and implement sorting algorithms.
Self-Instructional
Material 75

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES
5.2 INTRODUCTION

In the realm of computer science and algorithmic design, the twin concepts of searching
and sorting stand as the cornerstones of efficient data manipulation. Searching involves
the quest for a specific element within a dataset, akin to locating a needle in a haystack.
Conversely, sorting is the art of arranging elements in a particular order, transforming
chaos into structured patterns. Both searching and sorting algorithms play pivotal roles
in shaping the efficiency and functionality of computer programs. The quest for optimal
solutions to fundamental challenges has motivated the development of a rich array of
algorithms, each tailored to specific scenarios and datasets. As we embark on the
exploration of searching and sorting, we delve into the core strategies employed to
streamline information retrieval and organization, unlocking the door to enhanced
computational efficiency and problem-solving skills.

5.3 LINEAR SEARCH

Searching is a fundamental operation in computer science that involves finding a specific


element or value within a collection of data. In this lesson, we will explore various
searching techniques implemented in C++. We will cover linear search, binary search,
and the concept of time complexity for each technique. We will also provide code
examples to illustrate their usage.

Definition
Linear search, also known as sequential search, is a simple searching technique that
sequentially examines each element in a collection until the target element is found or
the entire collection is traversed. It is applicable to both ordered and unordered data
sets.
Here is the algorithm for Linear Search:
LinearSearch(arr, target):
 Let n be the length of the array.

Self-Instructional
76 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Searching and Sorting Algorithms - Efficient Data Manipulation Techniques

 For i from 0 to n-1: (n iterations) NOTES


 If arr[i] equals target, return i. (Target found at index
i)
 If the loop exits, the target value is not present in the
array. Return -1. (Target not found)

The LinearSearch function takes an array ‘arr’ and a target value ‘target’ as
input. It iterates through each element in the array using a loop.
In each iteration, the algorithm compares the element arr[i] with the target value.
If they are equal, the target is found, and the function returns the index i where the
target was found.
If the loop completes without finding the target, it means the target value is not
present in the array. In this case, the function returns -1 to indicate that the target was
not found.

Figure 5.1: Diagrammatic Representation of Finding an Element 54 Sequentially

Code Example
#include <iostream>
using namespace std;
int linearSearch(int arr[], int size, int target) { for (int i
= 0; i < size; i++) {
if (arr[i] == target) {
return i; // Element found at index i
}
}
return -1; // Element not found
}

int main() {
int arr[] = {10, 20, 30, 40, 50};
int target = 30; Self-Instructional
Material 77
int size = sizeof(arr) / sizeof(arr[0]);

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES int result = linearSearch(arr, size, target);


if (result == -1) {
cout << “Element not found.” << endl;
} else {
cout << “Element found at index:” << result << endl;
}
return 0;
}

In the above example, the linearSearch function performs a linear search on the
given array arr[] of size 5 to find the target element. If the element is found, the function
returns the index of the element; otherwise, it returns -1. The main function demonstrates
how to use the linearSearch function.
Time Complexity: The time complexity of linear search is O(n), where n is the
number of elements in the collection. In the worst-case scenario, a linear search may
need to traverse the entire collection.

5.4 BINARY SEARCH

Binary search is an efficient searching technique applicable to sorted collections. It


repeatedly divides the search space in half until the target element is found or the
search space is empty. Binary search is based on the principle that the target element
can only be present in either the left or right subarray, depending on the comparison
with the middle element.
Here is the algorithm for Binary Search:

BinarySearch(arr, target)
 Let n be the length of the array.
 Set left = 0 and right = n - 1.
 While left <= right:
o Set mid = (left + right) / 2.
o If arr[mid] equals target, return mid. (Target found)
Self-Instructional
78 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Searching and Sorting Algorithms - Efficient Data Manipulation Techniques

o If arr[mid] is less than target, update left = mid + 1. NOTES


(Target is in the right half)
o If arr[mid] is greater than the target, update right = mid
- 1. (Target is in the left half)
 If the loop exits, the target value is not present in the
array. Return -1. (Target not found)

The BinarySearch function takes a sorted array arr and a target value target as
input. It initializes two pointers, left and right, which represent the current search space
within the array. The algorithm uses a while loop to narrow down the search space
repeatedly.
In each iteration, the algorithm calculates the middle index, mid by averaging left
and right. It compares the value at arr[mid] with the target value. If they are equal, the
target is found, and the function returns the index mid.
If arr[mid] is less than the target, it means the target is in the right half of the
remaining search space. In this case, the algorithm updates left to mid + 1 to discard
the left half.
If arr[mid] is greater than the target, it means the target is in the left half of the
remaining search space. The algorithm updates right to mid - 1 to discard the right half.
The loop continues until either the target value is found (returning the
corresponding index) or the search space is exhausted (left becomes greater than
right), indicating that the target is not present in the array. In the latter case, the function
returns -1 to indicate that the target was not found.

Figure 5.2: Diagrammatic Representation of Finding an


Element Using Binary Search
Self-Instructional
Material 79

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES Binary Search Code Example


#include <iostream>
using namespace std;

int binarySearch(int arr[], int low, int high, int target) {


while (low <= high) {
int mid = low + (high - low) / 2;
if (arr[mid] == target) {
return mid; // Element found at index mid
} else if (arr[mid] < target) {
low = mid + 1; // Search in the right subarray
} else {
high = mid - 1; // Search in the left subarray
}
}
return -1; // Element not found
}

int main() {
int arr[] = {10, 20, 30, 40, 50};
int target = 30;
int size = sizeof(arr) / sizeof(arr[0]);
int result = binarySearch(arr, 0, size - 1, target);
if (result == -1) {
cout << “Element not found.” << endl;
} else {
cout << “Element found at index:” << result << endl;
}
return 0;
}

In the above example, the binarySearch function performs a binary search on


the given sorted array arr[] within the range of low to high to find the target element. If
Self-Instructional
the element is found, the function returns the index of the element; otherwise, it
80 Material returns -1. The main function demonstrates how to use the binarySearch function.

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Searching and Sorting Algorithms - Efficient Data Manipulation Techniques

Time Complexity: The time complexity of binary search is O(log n), where n is NOTES
the number of elements in the sorted collection. Binary search reduces the search
space by half in each iteration, making it highly efficient for large datasets.

5.5 BUBBLE SORT

Sorting is a fundamental operation in computer science that involves arranging elements


in a specific order. In this lesson, we will explore various sorting techniques implemented
in C++. We will cover popular algorithms such as bubble sort, insertion sort, selection
sort, merge sort, quicksort, and their time complexities. Code examples will be provided
to illustrate the implementation and usage of each sorting technique.
Bubble sort is a simple comparison-based sorting algorithm that repeatedly
compares adjacent elements and swaps them if they are in the wrong order. The
process is repeated until the entire collection is sorted. Here is the algorithm for
Bubble Sort:
1. BubbleSort(arr):
 Let n be the length of the array.
 For i from 0 to n-1: (n-1 iterations)
o For j from 0 to n-i-2: (n-i-1 iterations)
 If arr[j] > arr[j+1], swap arr[j] and arr[j+1].

The BubbleSort function takes an array arr as input and performs the sorting in
place. It uses two nested loops to iterate through the array. The outer loop (i) controls
the number of passes needed to sort the array, which is one less than the total number
of elements. The inner loop (j) compares adjacent elements and swaps them if they
are out of order.
In each iteration of the inner loop, the algorithm compares arr[j] and arr[j+1]. If
arr[j] is greater than arr[j+1], indicating that they are in the wrong order, the algorithm
swaps them by assigning arr[j+1] to arr[j] and arr[j] to arr[j+1]. This swapping operation
places the larger element towards the end of the array. The algorithm continues this
process until the inner loop completes one pass through the remaining unsorted portion
of the array.
Self-Instructional
Material 81

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES The outer loop repeats this process, gradually reducing the number of unsorted
elements by one in each iteration. After n-1 iterations, the largest element will have moved
to its correct position at the end of the array. At this point, the entire array is sorted.

Figure 5.3: Diagrammatic Representation of Sorting Using Bubble Sort

Code Example
#include <iostream>
using namespace std;

void bubbleSort(int arr[], int size) {


for (int i = 0; i < size - 1; i++) {
for (int j = 0; j < size - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr[j], arr[j + 1]);
}
}
}
}

int main() {
int arr[] = {64, 25, 12, 22, 11};
int size = sizeof(arr) / sizeof(arr[0]);
bubbleSort(arr, size);
Self-Instructional
82 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Searching and Sorting Algorithms - Efficient Data Manipulation Techniques

cout << “Sorted array:”; NOTES


for (int i = 0; i < size; i++) {
cout << arr[i] << “ ”;
}
cout << endl;
return 0;
}

In the above example, the bubbleSort function performs bubble sort on the
given array arr[] of size, size. The nested loops compare adjacent elements and swap
them if they are in the wrong order. The main function demonstrates how to use the
bubbleSort function.
Time Complexity: The time complexity of bubble sort is O(n^2), where n is the
number of elements in the collection. Bubble sort has poor performance for large
datasets.

5.6 INSERTION SORT

Insertion sort is a simple sorting algorithm that builds the final sorted array one element
at a time. It iterates over the collection, shifting elements to the right to find the correct
position for each element.
Here is the algorithm for Insertion Sort:
1. InsertionSort(arr):
 Let n be the length of the array.
 For i from 1 to n-1: (n-1 iterations)
o Set currentElement = arr[i].
o Set j = i - 1.
o While j >= 0 and arr[j] > currentElement:
 Shift arr[j] to the right by one position: arr[j+1] =
arr[j].
 Decrement j by 1.
o Place currentElement at its correct position: arr[j+1] =
currentElement. Self-Instructional
Material 83

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES The InsertionSort function takes an array arr as input and performs the sorting
in place. It uses a loop to iterate through the array from the second element to the last
element. In each iteration, it selects the current element (currentElement) from the
unsorted portion and compares it with the elements in the sorted portion (from index 0
to j, where j starts at i-1).
While j is greater than or equal to 0 and the element at index j is greater than the
currentElement, the algorithm shifts the element at index j to the right by one position
(making space for currentElement) by assigning arr[j+1] = arr[j]. It then decrements j
by 1 to continue comparing with the previous elements in the sorted portion.
Once the while loop is exited, the correct position for currentElement is found,
and it is placed at index j+1 (i.e., arr[j+1] = currentElement).
By repeatedly inserting each element into its correct position, Insertion Sort
gradually builds the sorted array. The sorted portion grows by one element in each
iteration until all elements are sorted.

Figure 5.4: Diagrammatic Representation of Sorting Using Insertion Sort

Code Example
#include <iostream>
using namespace std;
void insertionSort(int arr[], int size) {
for (int i = 1; i < size; i++) {
int key = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;

Self-Instructional
}
84 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Searching and Sorting Algorithms - Efficient Data Manipulation Techniques

arr[j + 1] = key; NOTES


}
}

int main() {
int arr[] = {64, 25, 12, 22, 11};
int size = sizeof(arr) / sizeof(arr[0]);
insertionSort(arr, size);
cout << “Sorted array:”;
for (int i = 0; i < size; i++) {
cout << arr[i] << “ ”;
}
cout << endl;
return 0;
}

In the above example, the insertionSort function performs insertion sort on the
given array arr[] of size, size. It iterates through the collection, comparing each element
with the previous elements and shifting them to the right until the correct position is
found for the current element. The main function demonstrates how to use the
insertionSort function.
Time Complexity: The time complexity of insertion sort is O(n^2), where n is
the number of elements in the collection. Insertion sort performs well for small or
partially sorted datasets.

5.7 SELECTION SORT

Selection sort is an in-place comparison-based sorting algorithm that divides the


collection into two parts: the sorted part and the unsorted part. It repeatedly selects
the smallest (or largest) element from the unsorted part and swaps it with the element
at the beginning of the unsorted part.

Self-Instructional
Material 85

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES Here is the algorithm for Selection Sort:


1. SelectionSort(arr):
 Let n be the length of the array.
 For i from 0 to n-2: (n-1 iterations)
o Find the index of the minimum element in the unsorted
portion of the array (i.e., from i to n-1).
o Swap the minimum element with the element at index i.
The SelectionSort function takes an array arr as input and performs the sorting
in place. It uses a loop to iterate through the array from the beginning to the second-
to-last element. In each iteration, it finds the index of the minimum element in the
unsorted portion of the array (starting from i to the end of the array). Once the minimum
element is found, it is swapped with the element at index i, effectively placing the
smallest element in its correct position at the beginning of the sorted portion.
The selection of the minimum element in each iteration reduces the size of the
unsorted portion by one element. After n-1 iterations, the entire array becomes sorted.
Note that the last element is automatically in its correct position since all other elements
have been sorted.

Self-Instructional
86 Material
Figure 5.5: Diagrammatic Representation of Sorting Using Selection Sort

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Searching and Sorting Algorithms - Efficient Data Manipulation Techniques

Code Example NOTES


#include <iostream>
using namespace std;

void selectionSort(int arr[], int size) {


for (int i = 0; i < size - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < size; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
swap(arr[i], arr[minIndex]);
}
}

int main() {
int arr[] = {64, 25, 12, 22, 11};
int size = sizeof(arr) / sizeof(arr[0]);
selectionSort(arr, size);
cout << “Sorted array:”;
for (int i = 0; i < size; i++) {
cout << arr[i] << “ ”;
}
cout << endl;
return 0;
}

In the above example, the selectionSort function performs selection sort on the
given array, arr[] of size, size. It iterates through the collection, finding the minimum
element from the unsorted part and swapping it with the element at the beginning of the
unsorted part. The main function demonstrates how to use the selectionSort function.
Time Complexity: The time complexity of the selection sort is O(n^2), where n
is the number of elements in the collection. Selection sort performs the same number
Self-Instructional
of comparisons irrespective of the initial order of the elements. Material 87

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES
5.8 MERGE SORT

Merge sort is a divide-and-conquer sorting algorithm that recursively divides the


collection into smaller halves until each half contains a single element. It then merges
the smaller halves to create a sorted array. The algorithm can be described using a
recursive function. Here is the algorithm for merge sort:
1. MergeSort(arr, left, right):
 If left < right:
o Set mid = (left + right) / 2.
o Call MergeSort(arr, left, mid). (Sort the left half)
o Call MergeSort(arr, mid + 1, right). (Sort the right
half)
o Call Merge(arr, left, mid, right). (Merge the two sorted
halves)
2. Merge(arr, left, mid, right):
 Set n1 = mid - left + 1 and n2 = right - mid. (Sizes of the
two subarrays)
 Create temporary arrays L[1...n1] and R[1...n2].
 Copy data from arr[left...mid] to L[1...n1].
 Copy data from arr[mid+1...right] to R[1...n2].
 Set i = 1, j = 1, and k = left. (Indexes for merging)
 While i <= n1 and j <= n2:
o If L[i] <= R[j], set arr[k] = L[i] and increment i.
o Otherwise, set arr[k] = R[j] and increment j.
o Increment k.
 Copy the remaining elements of L[], if there are any.
 Copy the remaining elements of R[], if there are any.

The MergeSort function is the entry point for the algorithm. It takes an array arr
and the indices left and right, which represent the range of elements to be sorted. The
function first checks if left is less than right to ensure that there is more than one
element in the subarray. If so, it calculates the middle index, mid and performs three
recursive calls: one to sort the left half, one to sort the right half, and one to merge the
Self-Instructional
88 Material two sorted halves using the Merge function.

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Searching and Sorting Algorithms - Efficient Data Manipulation Techniques

The Merge function takes the array, arr and the indices left, mid, and right. It NOTES
first determines the sizes of the two subarrays to be merged, n1 and n2. Then, it
creates temporary arrays L and R and copies the respective elements from the original
array. After that, it iterates through the subarrays, comparing elements from L and R
and merging them back into the original array, arr in sorted order. Finally, if any elements
are remaining in either L or R, they are copied back into arr.
By repeatedly dividing the array into smaller halves and merging them back
together, Merge Sort achieves a time complexity of O(n log n) in the average and
worst cases, where n is the number of elements in the array.

Figure 5.6: Diagrammatic Representation of Sorting Using Merge Sort

Code Example
#include <iostream>
using namespace std;
Self-Instructional
Material 89

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES void merge(int arr[], int left, int middle, int right) {
int i, j, k;
int n1 = middle - left + 1;
int n2 = right - middle;

int L[n1], R[n2];

for (i = 0; i < n1; i++)


L[i] = arr[left + i];
for (j = 0; j < n2; j++)
R[j] = arr[middle + 1 + j];

i = 0;
j = 0;
k = left;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = R[j];
j++;
}
k++;
}

while (i < n1) {


arr[k] = L[i];
i++;
k++;
}

while (j < n2) {


Self-Instructional
90 Material arr[k] = R[j];

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Searching and Sorting Algorithms - Efficient Data Manipulation Techniques

j++; NOTES
k++;
}
}

void mergeSort(int arr[], int left, int right) {


if (left < right) {
int middle = left + (right - left) / 2;
mergeSort(arr, left, middle);
mergeSort(arr, middle + 1, right);
merge(arr, left, middle, right);
}
}

int main() {
int arr[] = {64, 25, 12, 22, 11};
int size = sizeof(arr) / sizeof(arr[0]);
mergeSort(arr, 0, size - 1);
cout << “Sorted array:”;
for (int i = 0; i < size; i++) {
cout << arr[i] << “ ”;
}
cout << endl;
return 0;
}

In the above example, the merge function merges two sorted halves of the
array, while the mergeSort function recursively divides the array into smaller halves
and merges them. The main function demonstrates how to use the mergeSort function.
Time Complexity: The time complexity of merge sort is O(n log n), where n is the
number of elements in the collection. Merge sort provides a stable and efficient sorting
solution for large datasets.

Self-Instructional
Material 91

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES
5.9 QUICK SORT

Quick sort is a divide-and-conquer sorting algorithm that selects an element as a pivot


and partitions the collection into two sub-arrays, one with elements smaller than the
pivot and one with elements greater than the pivot. It recursively sorts the sub-arrays.
The Quick Sort algorithm is a widely used divide-and-conquer sorting algorithm that
follows the following steps:
1. Choose a Pivot
 Select a pivot element from the array. The pivot can be chosen in various ways,
such as selecting the first, last, or middle element of the array.
2. Partitioning
 Rearrange the array in such a way that all elements smaller than the pivot are
moved to the left of the pivot, and all elements greater than the pivot are moved
to the right of the pivot.
 The pivot element will be in its final sorted position.
 The partitioning process divides the array into two sub-arrays, one containing
elements smaller than the pivot (left partition) and the other containing elements
greater than the pivot (right partition).
 The position of the pivot is often called the partitioning index.
3. Recursion
 Apply the Quick Sort algorithm recursively to the left and right partitions created
in the previous step.
 This involves selecting new pivot elements for each partition and performing
partitioning until each partition contains only one element (which is already in its
sorted position).
4. Combine
 Since each partition is sorted individually, no additional combining step is
necessary.
Self-Instructional
92 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Searching and Sorting Algorithms - Efficient Data Manipulation Techniques

 The partitions are already sorted in place, and the original array is now fully NOTES
sorted.
The above steps are typically implemented using the following pseudo-code:
function quickSort(arr, low, high):
if low < high:
pivotIndex = partition(arr, low, high) // Partition the
array
quickSort(arr, low, pivotIndex - 1) // Recursively
sort the left partition
quickSort(arr, pivotIndex + 1, high) // Recursively
sort the right partition

function partition(arr, low, high):


pivot = arr[high] // Choose the pivot element (e.g.,
last element)
i = low - 1 // Index of smaller element
for j = low to high - 1:
if arr[j] <= pivot:
i++
swap(arr[i], arr[j]) // Swap elements smaller than
the pivot with the element at index i
swap(arr[i + 1], arr[high]) // Place the pivot element in its
sorted position
return i + 1 // Return the partitioning index

// Usage:
quickSort(arr, 0, size - 1) // Call the quickSort function to
sort the entire array

Let us understand with the help of an example where we have to sort the list of
elements: {10, 42, 15, 12, 37, 7, 9}.
Let us go through the steps:
1. Partitioning
 The pivot element is chosen as 9.
Self-Instructional
Material 93

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES  The partitioning process rearranges the elements in such a way that all elements
smaller than the pivot are moved to the left, and all elements greater than the
pivot are moved to the right.
 After the partitioning process, the array may look like this: [7, 9, 10, 42, 37,
15, 12]
 The pivot element, 9, is now in its final sorted position.
 All elements to the left of the pivot (7) are smaller than the pivot.
 All elements to the right of the pivot (10, 42, 37, 15, 12) are greater than the
pivot.
2. Recursion
 At this point, we have two partitions: [7] and [10, 42, 37, 15, 12].
 We recursively apply the Quick Sort algorithm on each partition.
For the partition [7]:
 Since it contains only one element, no further sorting is required.
For the partition [10, 42, 37, 15, 12]:
 We choose a new pivot element, let us say 15.
 Perform the partitioning process again:
o The elements to the left of the pivot (10, 12) are smaller.
o The elements to the right of the pivot (42, 37) are greater.
After the partitioning process, the partition [10, 12, 15, 42, 37] is obtained.
3. Recursion:
 We now have two partitions: [7] and [10, 12, 15, 42, 37].
 Again, we recursively apply the Quick Sort algorithm on each partition.
For the partition [7]:
 No further sorting is required since it contains only one element.
For the partition [10, 12, 15, 42, 37]:
 We choose a new pivot element, let us say 37.
Self-Instructional
94 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Searching and Sorting Algorithms - Efficient Data Manipulation Techniques

 Perform the partitioning process: NOTES


o The elements to the left of the pivot (10, 12, 15) are smaller.
o The elements to the right of the pivot (42) are greater.
After the partitioning process, the partition [10, 12, 15, 37, 42] is obtained.
4. Recursion:
 We now have two partitions: [7] and [10, 12, 15, 37, 42].
 For each partition, since they contain only one element, no further sorting is
required.
5. Final Result:
 After completing the recursion and sorting on each partition, we obtain the final
sorted array: [7, 9, 10, 12, 15, 37, 42].
The Quick Sort algorithm divides the input array into smaller partitions based
on the chosen pivot element and recursively applies the partitioning process until each
partition contains only one element. It combines the sorted partitions to obtain the final
sorted array.

Figure 5.7: Diagrammatic Representation of Sorting Using Quick Sort

Note: The choice of the pivot element can affect the performance of the Quick
Sort algorithm. In the example, the first pivot chosen was 9, but different choices
may lead to different partitioning and sorting outcomes. Self-Instructional
Material 95

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES Code Example


#include <iostream>
using namespace std;

int partition(int arr[], int low, int high) {


int pivot = arr[high];
int i = low - 1;

for (int j = low; j <= high - 1; j++) {


if (arr[j] < pivot) {
i++;
swap(arr[i], arr[j]);
}
}
swap(arr[i + 1], arr[high]);
return i + 1;
}

void quickSort(int arr[], int low, int high) {


if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}

int main() {
int arr[] = {64, 25, 12, 22, 11};
int size = sizeof(arr) / sizeof(arr[0]);
quickSort(arr, 0, size - 1);
cout << “Sorted array:”;
for (int i = 0; i < size; i++) {
Self-Instructional
96 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Searching and Sorting Algorithms - Efficient Data Manipulation Techniques

cout << arr[i] << “ ”; NOTES


}
cout << endl;
return 0;
}

In the above example, the partition function selects a pivot and partitions the
array into two sub-arrays. The quickSort function recursively sorts the sub-arrays by
calling the partition function. The main function demonstrates how to use the quickSort
function.
Time Complexity: The average time complexity of quick sort is O(n log n), where n
is the number of elements in the collection. Quick sort is efficient and widely used in
practice.

5.10 COUNT SORT

Counting Sort is a non-comparison-based sorting algorithm that operates by counting


the number of occurrences of each element in the input data. It requires knowledge of
the range of input values and is particularly efficient when the range is not significantly
larger than the number of elements to be sorted.
Let us go through the Counting Sort step by step and show how the array is sorted in
memory:
Initial Array: [4, 2, 2, 8, 3, 3, 1]
Step 1: Identify the range of input values
Determine the range of values in the input array. This helps in creating an auxiliary
array to store the count of each element.
Identify the range of input values:
MAX = 8
max_val = 8
min_val = 1

Self-Instructional
Material 97

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES Step 2: Count the occurrences of each element.


Create an auxiliary array (called count array) to store the count of each element in the
input array. The length of this array is MAX+1 with all elements = 0. Traverse the input
array and increment the count for each element in the count array.
Create a count array MAX+1=9.
count = [0, 0, 0, 0, 0, 0, 0, 0, 0] size = max+1
and count occurrences
count = [0, 1, 2, 2, 1, 0, 0, 0, 1]

Step 3: Calculate cumulative counts


Modify the count array to store the cumulative count of elements. This step helps
determine the position of each element in the sorted output.
Update the count array to store cumulative counts.
count = [0, 1, 3, 5, 6, 6, 6, 6, 7]

Step 4: Build the sorted array:


Traverse the original array from right to left.
Find the index of each element of the original array in the count array. This gives
the cumulative count. Place the element at the index calculated as shown in the figure
below.

After placing each element in its correct position, decrease its count by one.

Self-Instructional
98 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Searching and Sorting Algorithms - Efficient Data Manipulation Techniques

Count Sort Program NOTES


#include <iostream>
#include <vector>

using namespace std;

void countingSort(vector<int>& arr) {


// Step 1: Identify the range of input values
int max_val = *max_element(arr.begin(), arr.end());
int min_val = *min_element(arr.begin(), arr.end());

// Step 2: Count the occurrences of each element


vector<int> count(max_val - min_val + 1, 0);
for (int num: arr) {
count[num - min_val]++;
}

// Step 3: Calculate cumulative counts


for (int i = 1; i < count.size(); ++i) {
count[i] += count[i - 1];
}

// Step 4: Build the sorted array


vector<int> sortedArray(arr.size());
for (int i = arr.size() - 1; i >= 0; — —i) {
sortedArray[count[arr[i] - min_val] - 1] = arr[i];
count[arr[i] - min_val]— —;
}

// Step 5: Copy the sorted array back to the original array


arr = sortedArray;
}
Self-Instructional
Material 99

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES int main() {


vector<int> inputArray = {4, 2, 2, 8, 3, 3, 1};

cout << “Original Array:”;


for (int num: inputArray) {
cout << num << “ ”;
}
cout << endl;

countingSort(inputArray);

cout << “Sorted Array:”;


for (int num: inputArray) {
cout << num << “ ”;
}
cout << endl;

return 0;

5.11 WHEN TO USE WHICH SORTING ALGORITHM?

Choosing the appropriate sorting algorithm depends on various factors, such as the
size of the data set, the desired time complexity, and the characteristics of the data.
Here are some guidelines on when to use which sorting algorithm:

Bubble Sort

 Bubble sort is simple to understand and implement, but it has poor performance
compared to other sorting algorithms.
 It is suitable for small data sets or nearly sorted data.
 Bubble sort is not recommended for large or unsorted data sets due to its time
complexity of O(n^2).
Self-Instructional
100 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Searching and Sorting Algorithms - Efficient Data Manipulation Techniques

Insertion Sort NOTES

 Insertion sort is efficient for small data sets or partially sorted data.
 It has a time complexity of O(n^2), but it performs better than bubble sort.
 Insertion sort is useful when the data set is already partially sorted or when the
number of elements is small.

Selection Sort

 Selection sort is easy to implement, but it has a time complexity of O(n^2).


 It is suitable for small data sets or when minimizing the number of swaps is
important.
 Selection sort performs the same number of comparisons regardless of the
initial order of the elements.

Merge Sort

 Merge sort has a time complexity of O(n log n), making it efficient for large data
sets.
 It is a stable sorting algorithm and is suitable for sorting linked lists.
 Merge sort is useful when a stable sort is required or when dealing with large
data sets.

Quick Sort

 Quick sort has an average time complexity of O(n log n) and performs well in
practice.
 It is suitable for large data sets and is often used as a standard sorting
algorithm.
 Quick sort is not stable, meaning the order of equal elements may change.

Self-Instructional
Material 101

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES Other Sorting Algorithms

 Radix sort: Efficient for sorting integers with a fixed number of digits.
 Counting sort: Efficient for sorting integers within a specific range.
 Heap sort: Efficient for large data sets and guarantees a time complexity of
O(n log n).
Remember that the choice of sorting algorithm depends on the specific
requirements of your application. Consider the size and characteristics of the data, as
well as the desired time complexity, to select the most suitable algorithm. It is also
important to analyse the average and worst-case scenarios of each algorithm to ensure
optimal performance.

In-Text Questions
Q1. What is the time complexity of the linear search algorithm in the worst-case
scenario?
A. O(log n) B. O(n)
C. O(n^2) D. O(1)
Q2. In a list of 10 elements, what is the maximum number of comparisons needed
to find an element using linear search?
A. 5 B. 9
C. 3 D. 10
Q3. What is the prerequisite for applying the binary search algorithm on a list of
elements?
A. Elements should be sorted.
B. Elements should be in random order.
C. Elements should be in descending order.
D. Elements should be unique.
Q4. What is the time complexity of the binary search algorithm in the worst-case
scenario?
A. O(log n) B. O(n)
Self-Instructional C. O(n^2) D. O(1)
102 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Searching and Sorting Algorithms - Efficient Data Manipulation Techniques

Q5. In a sorted list of 16 elements, what is the maximum number of comparisons NOTES
needed to find an element using binary search?
A. 4 B. 5
C. 8 D. 16
Q6. What is the middle index of an array with 9 elements (assuming 0-based
indexing) when using binary search?
A. 3 B. 4
C. 5 D. 8
Q7. Which search algorithm is faster for finding an element in a sorted array?
A. Linear search
B. Binary search
C. Both have the same speed
D. Depends on the size of the array
Q8. Binary search works by repeatedly dividing the search interval by what value?
A. 2 B. 3
C. 10 D. The element to be searched
Q9. What is the main advantage of binary search over linear search?
A. It requires fewer comparisons.
B. It works on unsorted arrays.
C. It has a lower time complexity.
D. It can search for multiple elements simultaneously.
Q10. Which search algorithm is used when the elements are stored in a linked list?
A. Linear search
B. Binary search
C. Both A and B
D. None, linked lists cannot be searched
Q11. How does the insertion sort algorithm sort a list of elements?
A. It repeatedly divides the list into sublists.
B. It swaps adjacent elements until the list is sorted.
Self-Instructional
Material 103

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES C. It sorts the list by selecting the largest element first.


D. It builds the final sorted array one element at a time by comparing elements
and shifting them if needed.
Q12. What is the time complexity of insertion sort for sorting ‘n’ elements in the
worst-case scenario?
A. O(n) B. O(n log n)
C. O(n^2) D. O(1)
Q13. In the insertion sort algorithm, if an element is smaller than the previous element,
what operation is performed?
A. The elements are swapped.
B. The element is inserted at the beginning of the list.
C. The element is moved to the end of the list.
D. The elements are sorted using a different algorithm.
Q14. What is the primary requirement for Counting Sort to be applicable to an
array of elements?
A. Elements should be in descending order.
B. Elements should be in ascending order.
C. Elements should be within a known range.
D. Elements should be negative integers.
Q15. What is the time complexity of Counting Sort for sorting ‘n’ elements within a
specific range (k)?
A. O(n log n) B. O(n^2)
C. O(k) D. O(n + k)
Q16. In Counting Sort, what does the counting phase involve?
A. Sorting the elements based on comparisons
B. Counting the occurrences of each unique element
C. Swapping adjacent elements to sort the array
D. Dividing the array into smaller subarrays
Q17. For which scenario is Counting Sort most suitable?
Self-Instructional A. Large array with random values
104 Material
B. Array with limited or known range of values

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Searching and Sorting Algorithms - Efficient Data Manipulation Techniques

C. Array with all elements being identical NOTES


D. Array with elements in reverse order
Q18. What is the space complexity of Counting Sort?
A. O(n) B. O(n^2)
C. O(k) D. O(n + k)

5.12 SUMMARY

Searching techniques play a vital role in data retrieval. In this lesson, we explored
linear search and binary search, two commonly used search techniques. Linear search
is suitable for unordered collections, while binary search is efficient for sorted collections.
Understanding these techniques and their time complexities helps in choosing the
appropriate method for different scenarios. By implementing and utilizing these searching
techniques, you can efficiently locate desired elements within datasets, leading to
optimized and effective algorithms.
Sorting techniques are crucial for organizing data in a specific order. In this
lesson, we explored popular sorting algorithms, including bubble sort, insertion sort,
selection sort, merge sort and quicksort. Each technique has its own advantages and
time complexities. By implementing and understanding these sorting algorithms, you
can efficiently sort data in various scenarios. Choosing the appropriate sorting technique
based on the data size and properties is essential for optimal performance.

5.13 GLOSSARY

 Searching: The process of finding a specific element within a collection of


items.
 Linear Search: A searching algorithm that checks every element in a
collection sequentially until the target element is found or the entire collection
is traversed.
Self-Instructional
Material 105

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES  Binary Search: A searching algorithm applicable to sorted collections that


repeatedly divides the search interval by half until the target element is found or
determined as not present.
 Hashing: A technique that maps keys or elements to specific locations in a data
structure, such as a hash table, using a hash function to enable rapid retrieval.
 Hash Table: A data structure that stores keys or elements using a hash function
to map them to specific locations for efficient retrieval.
 Search Space: The set of all possible solutions or elements that can be searched
to find the target element.
 Sorting: The process of arranging elements in a specific order, such as numerical
or lexicographical, within a collection.
 Bubble Sort: A simple sorting algorithm that repeatedly steps through the list,
compares adjacent elements, and swaps them if they are in the wrong order.
 Selection Sort: A sorting algorithm that repeatedly selects the minimum element
from an unsorted portion and swaps it with the first unsorted element.
 Insertion Sort: A sorting algorithm that builds the final sorted array or list one
element at a time by comparing and inserting elements in their correct positions.
 Merge Sort: A divide-and-conquer sorting algorithm that divides the input
array into smaller sub-arrays, sorts them individually and then merges them
back together.
 Quick Sort: A divide-and-conquer sorting algorithm that selects a pivot element,
partitions the array around the pivot and recursively sorts the sub-arrays.
 Heap Sort: A sorting algorithm that uses the properties of a heap data structure
to remove the maximum element and reconstruct the heap repeatedly.
 Stable Sorting: A sorting algorithm that preserves the relative order of equal
elements in the sorted output.
 In-place Sorting: Sorting algorithms that use a constant amount of extra memory
for rearranging elements.
 External Sorting: Sorting algorithms designed for large datasets that do not fit
into the main memory and require external storage.
Self-Instructional
106 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Searching and Sorting Algorithms - Efficient Data Manipulation Techniques

NOTES
5.14 ANSWERS TO IN-TEXT QUESTIONS

1. B. O(n)
2. D. 10
3. A. Elements should be sorted
4. A. O(log n)
5. A. 4
6. A. 3
7. B. Binary search
8. A. 2
9. C. It has a lower time complexity.
10. A. Linear search
11. D. It builds the final sorted array one element at a time by comparing elements
and shifting them if needed.
12. C. O(n^2)
13. A. The elements are swapped
14. C. Elements should be within a known range
15. D. O(n + k)
16. Counting the occurrences of each unique element
17. B. Array with a limited or known range of values
18. C. O(k)

5.15 SELF-ASSESSMENT QUESTIONS

1. Define searching in the context of computer science and explain its significance.
2. Discuss the difference between linear search and binary search. Compare their
Self-Instructional
time complexities and conditions for usage. Material 107

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES 3. Explain the working principle of the binary search algorithm and its efficiency in
searching sorted arrays.
4. Discuss the concept of hashing in searching algorithms and its application in
hash tables.
5. Explain the concept of interpolation search and discuss its advantages over
binary search.
6. Describe the sequential search method and discuss its effectiveness in different
scenarios.
7. Explain the working principle of the insertion sort algorithm. Discuss its time
complexity and its best and worst-case scenarios.
8. Discuss the key steps involved in the insertion sort algorithm and how it sorts
elements within an array.
9. Describe the process of insertion sort using an example array and illustrate the
step-by-step sorting process.
10. Explain the advantages and limitations of insertion sort compared to other sorting
algorithms.
11. Describe the counting sort algorithm and its working principle. Discuss its time
complexity and the scenarios where it performs efficiently.
12. Explain the counting phase and the rearranging phase in the counting sort
algorithm.
13. Discuss the implementation of counting sort and how it handles sorting elements
with non-negative integer keys.
14. Compare and contrast counting sort with other sorting algorithms in terms of
efficiency and applicability.

5.16 REFERENCES

 Goodrich, M.T., Tamassia, R., & Mount, D., Data Structures and Algorithms
Analysis in C++, 2nd edition, Wiley, 2011.
Self-Instructional
108 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Searching and Sorting Algorithms - Efficient Data Manipulation Techniques

 Cormen, T.H., Leiserson, C.E., Rivest, R. L., Stein C. Introduction to Algorithms, NOTES
4th edition, Prentice Hall of India, 2022.
 Drozdek, A., Data Structures and Algorithms in C++, 4th edition, Cengage
Learning, 2012.

5.17 SUGGESTED READINGS

 Sahni, S. Data Structures, Algorithms and Applications in C++, 2nd Edition,


Universities Press, 2011.
 Langsam Y., Augenstein, M. J., & Tanenbaum, A. M. Data Structures Using C
and C++, Pearson, 2015.

Self-Instructional
Material 109

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
UNIT III

LESSON 6 RECURSION - ITERATION THROUGH


SELF-REFERENCE
Recursion - Iteration Through Self-Reference

LESSON 6 NOTES

RECURSION - ITERATION THROUGH


SELF-REFERENCE

Name of Author: Dr Geetika Vashishta


Designation: Assistant Professor,
College of Vocational Studies
Email: [email protected]
Structure
6.1 Learning Objectives
6.2 Introduction
6.3 Recursion: Definition and Working
6.3.1 How Recursion Works
6.3.2 The Structure of a Recursive Function
6.3.3 Example of a Recursive Function
6.3.4 Applications of Recursive Algorithms
6.4 Linear Recursion
6.4.1 Example of a Linear Recursive Function: Calculating the Factorial of a
Number
6.5 Binary Recursion
6.5.1 Example of Binary Search
6.5.2 Example of Linked List Traversal Using Recursion
6.5.3 Example of Power Calculation Traversal Using Recursion
6.5.4 Understanding Recursive Calls - A Closer Look
6.5.5 The Call Stack
6.5.6 Handling Recursive Function Calls
6.6 Summary
6.7 Glossary
6.8 Answers to In-Text Questions
6.9 Self-Assessment Questions
6.10 References
6.11 Suggested Readings

Self-Instructional
Material 113

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES
6.1 LEARNING OBJECTIVES

 To understand how a function can call itself, breaking a problem into simpler
subproblems.
 Develop proficiency in solving problems using recursive approaches.

6.2 INTRODUCTION

Recursion, a powerful programming concept, involves the definition of a function in


terms of itself. In a recursive function, the problem is solved by breaking it down into
smaller, simpler instances of the same problem. Imagine you want to make a sandwich,
and you have a set of instructions to follow. Let us break down the process step by
step:

Instructions for making a sandwich

a) Take two slices of bread.


b) Spread mayonnaise on one slice of bread.
c) Add lettuce and tomato on top of the mayonnaise.
d) Place the other slice of bread on top.
Now, let us think about how recursion relates to making a sandwich. The process
of making a sandwich can be broken down into smaller subtasks. In this case, the
steps of spreading mayonnaise and adding lettuce and tomato can be considered as
subtasks.

Recursive Perspective

a) Take two slices of bread.


b) Spread mayonnaise on one slice of bread.
c) Make a sandwich with the remaining steps.
Self-Instructional
114 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Recursion - Iteration Through Self-Reference

In step c, we introduce recursion. Making a sandwich is a repetitive process NOTES


that involves performing the same set of instructions repeatedly until a base case is
reached. In this case, the base case is having only one step left (placing the other slice
of bread), which does not require further recursion.

Recursive Perspective (continued)

a) Make a sandwich with the remaining steps.


b) Add lettuce and tomato on top of the mayonnaise.
c) Place the other slice of bread on top.
In step c, the process of making a sandwich is called again, but with a smaller
set of instructions. The instructions for spreading mayonnaise and adding lettuce and
tomato are the same, but the context changes. The recursive call performs the same
steps with a reduced scope, bringing us closer to the base case.
The recursion continues until we reach the base case, where only one step is
left. At this point, the recursive calls start returning, and the sandwich is assembled
step by step until the final result is obtained.
This example illustrates how recursion can be applied in a real-life context. By
breaking down a complex task into smaller, repetitive subtasks, we can solve problems
efficiently and concisely. Just like making a sandwich, recursive processes often involve
performing the same set of actions with different contexts until a base case is reached.

6.3 RECURSION: DEFINITION AND WORKING

Recursion is a powerful programming technique where a function calls itself to solve a


problem. It involves breaking down a complex problem into smaller subproblems and
solving them independently. Each recursive call solves a smaller version of the original
problem until a base case is reached, which does not require further recursion. Recursion
provides an elegant and concise solution for problems that exhibit repetitive and self-
similar patterns.

Self-Instructional
Material 115

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES 6.3.1 How Recursion Works

The fundamental idea behind recursion is the concept of a function calling itself. When
a function is called, a new instance of the function is created, and control is transferred
to the new instance. The new instance executes its code, which may include calling the
same function again. This process continues until a base case is encountered, at which
point the recursion stops, and the function calls start returning their results.

6.3.2 The Structure of a Recursive Function

A recursive function typically consists of two parts: the base case(s) and the recursive
case(s). The base case defines the simplest version of the problem that can be solved
directly without further recursion. The recursive case defines how the function calls
itself to solve a smaller subproblem. It also specifies how the results of these recursive
calls are combined to obtain the final result.

6.3.3 Example of a Recursive Function

Let us illustrate recursion with an example of calculating the factorial of a number. The
factorial of a non-negative integer n, denoted as n! is the product of all positive integers
less than or equal to n.
#include <iostream>

int factorial(int n) {
if (n == 0)
return 1; // Base case: factorial of 0 is 1
else
return n * factorial(n - 1); // Recursive case: n! = n * (n-
1)!
}

int main() {
int number;
std::cout << “Enter a non-negative integer:”;
Self-Instructional
116 Material std::cin >> number;

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Recursion - Iteration Through Self-Reference

std::cout << “Factorial of” << number << “ is ” << factorial(number) NOTES
<< std::endl;
return 0;
}

In this example, the factorial function calculates the factorial of a given number
n. If n is 0, the base case is triggered, and the function returns 1. Otherwise, it recursively
calls itself with n - 1 and multiplies the result by n. This process continues until n
becomes 0 and the base case is reached.

6.3.4 Applications of Recursive Algorithms

Recursive algorithms find application in various domains and problem-solving scenarios.


Here are some common applications of recursive algorithms:
 Searching and Traversing: Recursive algorithms are used for searching and
traversing data structures like trees, graphs, and linked lists. Examples include
depth-first search (DFS) and breadth-first search (BFS) algorithms, which
explore nodes or vertices in a graph recursively.
 Sorting: Some sorting algorithms utilize recursion. One example is the quicksort
algorithm, which recursively divides an array into smaller subarrays, sorts them
and combines them to obtain the final sorted array.
 Tree and Graph Operations: Recursive algorithms are often employed to
perform operations on tree and graph structures. For instance, recursively
calculating the height or depth of a tree, finding paths between nodes, or
determining if a graph contains a cycle.
 Fractals and Graphics: Recursive algorithms are used to generate fractal
patterns, such as the famous Mandelbrot set. Fractals exhibit self-similarity and
are created by recursively applying mathematical transformations.
 Divide and Conquer: Many divide and conquer algorithms use recursion.
They involve dividing a problem into smaller subproblems, solving them
recursively, and combining the results. Examples include binary search, merge
sort, and the fast Fourier transform (FFT).
 Backtracking: Backtracking algorithms explore all possible solutions by making
Self-Instructional
choices and backtracking when a choice leads to a dead end. Examples include Material 117

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES solving puzzles like Sudoku or the N-Queens problem or finding paths in a
maze.
 Dynamic Programming: Dynamic programming often employs recursion to
solve optimization problems by breaking them down into overlapping
subproblems. Recursive calls are combined with memoization (caching computed
results) to avoid redundant computations. Examples include the Fibonacci
sequence, the knapsack problem, and the longest common subsequence
problem.
 Parsing and Syntax Analysis: Recursive descent parsing is a top-down parsing
technique that uses recursive functions to analyse and parse the structure of a
grammar or language. Each function corresponds to a production rule in the
grammar, allowing for recursive handling of nested structures.

Comparison

Linear Recursion: In the factorial example, the function breaks down the problem
into one smaller instance by making a single recursive call.
Binary Recursion: In the Fibonacci example, the function breaks down the problem
into two smaller instances by making two recursive calls.
The key distinction is in the number of recursive calls made, and this choice depends
on the nature of the problem and how it can be effectively divided into subproblems.

6.4 LINEAR RECURSION

Linear recursion refers to a situation where a function makes a single recursive call in
its definition.

6.4.1 Example of a Linear Recursive Function: Calculating the Factorial


of a Number

// Linear recursion: factorial of a number


int factorial(int n) {
Self-Instructional
118 Material
if (n == 0 || n == 1) {

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Recursion - Iteration Through Self-Reference

return 1; NOTES
} else {
return n * factorial(n - 1);
}
}

int main() {
int n = 5;
std::cout << “Factorial of” << n << “ is:” << factorial(n) <<
std::endl;
return 0;
}

In the linear recursion example, we calculate the factorial of a number. The


recursive function factorial makes only one recursive call (factorial(n - 1)) to break
down the problem into smaller instances. The base case is when n is 0 or 1, where the
function returns 1.

6.5 BINARY RECURSION

Binary recursion involves a function making two recursive calls in its definition.

Example of a Fibonacci series

The Fibonacci series has many interesting properties and appears in various fields,
including mathematics, nature, and computer science. It exhibits self-similarity and can
be found in patterns found in nature, such as the growth of plants and the arrangement
of leaves on stems. In computer science, the Fibonacci series is frequently used as an
example to demonstrate recursion, dynamic programming, and algorithmic analysis.

Note: The Fibonacci series is a sequence of numbers in which each number is the
sum of the two preceding ones. It starts with 0 and 1, and each subsequent
number is the sum of the previous two numbers. The Fibonacci series follows the
pattern:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34,... Self-Instructional
Material 119

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES To generate the Fibonacci series, the first two numbers (0 and 1) are explicitly
given. Then, each subsequent number is calculated by adding the two previous numbers.

Figure 6.1: Computing Fibonacci Series for Input = 5

F(5) = F(4) + F(3)


= F(3) + F(2) + F(2) + F(1)
= F(2) + F(1) + F(1) + F(0) + F(1) + F(0) + 1
= F(1) + F(0) + 1 + 1 + 0 + 1 + 0 + 1
= 1+0+1+1+0+1+0+1
= 5
Let us compute the Fibonacci Sequence using recursion using the following code:
int fibonacci(int n) {
if (n <= 1)
return n; // Base case: Fibonacci of 0 and 1 is the number
itself
else
return fibonacci(n - 1) + fibonacci(n - 2); // Recursive
case: Fibonacci(n) = Fibonacci(n-1) + Fibonacci(n-2)
}

Explanation: The Fibonacci function calculates the nth Fibonacci number. The base
case is when n is 0 or 1, where the function returns n itself. For larger values of n, the
function recursively calls itself with n-1 and n-2, combining the results to calculate the
Fibonacci number.
Self-Instructional
120 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Recursion - Iteration Through Self-Reference

6.5.1 Example of Binary Search NOTES

Binary search is a highly efficient algorithm for searching in sorted collections, especially
when the number of elements is large. It is widely used in various applications, including
searching in databases, finding elements in sorted arrays, and implementing other
algorithms like interpolation search.
To perform Binary Search using recursion, write the following code:
int binarySearch(int arr[], int left, int right, int target) {
if (left > right)
return -1; // Base case: element not found
else {
int mid = (left + right) / 2;
if (arr[mid] == target)
return mid; // Base case: element found at mid index
else if (arr[mid] > target)
return binarySearch(arr, left, mid - 1, target); // Recursive
case: search in left half
else
return binarySearch(arr, mid + 1, right, target); // Recursive
case: search in right half
}
}

Explanation: The binarySearch function performs a binary search on a sorted array.


It compares the target value with the middle element and recursively searches either
the left or right half of the array based on the comparison. The function uses base
cases to handle scenarios where the target is found, or the search range becomes
invalid.

6.5.2 Example of Linked List Traversal Using Recursion

To quickly recapitulate, a linked list is a data structure used to store a collection of


elements, where each element is connected to the next element through a “link” or a
“pointer”. Unlike an array, which uses contiguous memory to store elements, a linked
list uses dynamic memory allocation and does not require elements to be stored at Self-Instructional
Material 121
consecutive locations in memory.

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES In a linked list, each element is represented by a node. A node contains two components:
the data it holds and a pointer/reference to the next node in the sequence. The first
node in the list is called the head, and the last node points to null or is referred to as the
tail, as shown below:
Node Node Node
+———+———+ +———+———+ +———+———+
| Data | Next | —> | Data | Next | —> | Data | Next |—> NULL
+———+———+ +———+———+ +———+———+
Head Tail
In this example, each node consists of a data field (which can hold any value)
and a next pointer that points to the next node in the list. The last node’s next pointer
is null, indicating the end of the list.
To implement Linked List using recursion, write the following code:
struct Node {
int data;
Node* next;
};

void traverseLinkedList(Node* node) {


if (node == nullptr)
return; // Base case: end of the linked list
else {
cout << node->data << “ ”; // Process the current node
traverseLinkedList(node->next); // Recursive case: traverse to
the next node
}
}

Explanation: The traverseLinkedList function recursively traverses a linked list and


prints the data of each node. It uses a base case where the function returns when it
reaches the end of the linked list (i.e., the node is nullptr). In the recursive case, the
function processes the current node and recursively calls itself with the next node.
Self-Instructional
122 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Recursion - Iteration Through Self-Reference

6.5.3 Example of Power Calculation Traversal Using Recursion NOTES

double power(double base, int exponent) {


if (exponent == 0)
return 1; // Base case: any number to the power of 0 is 1
else
return base * power(base, exponent - 1); // Recursive case:
base^exponent = base * base^(exponent-1)
}

Explanation: The power function calculates the result of raising a base number to an
exponent. If the exponent is 0, the base case is triggered, and the function returns 1.
Otherwise, it recursively calls itself with the base and exponent - 1, multiplying the
base with the result of the recursive call to calculate the power.

6.5.4 Understanding Recursive Calls – A Closer Look!

When a function calls itself, a new instance of the function is created, and a separate
set of local variables and parameters are allocated for each instance. The control flow
of the program moves to the new instance, and the original instance waits for the result
of the recursive call. Each instance maintains its own set of local variables and executes
its code independently.

6.5.5 The Call Stack

As recursive calls occur, they are stacked on top of each other in a data structure
called the call stack. The call stack keeps track of the execution context of each
function call, including the values of local variables and the return address. Once the
base case is reached, the recursive calls start returning their results in reverse order,
with each instance passing its result back to the instance that called it. This process
continues until the original instance receives the final result.

6.5.6 Handling Recursive Function Calls

It is essential to ensure that a recursive function terminates and does not result in an
infinite loop. To achieve this, the base case(s) must be well-defined and reachable Self-Instructional
Material 123

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES from the recursive case(s). Without a proper base case, the recursion would continue
indefinitely, eventually causing a stack overflow error.
Here is an example that illustrates the importance of a well-defined base case in ensuring
the termination of a recursive function:
#include <iostream>

void countDown(int n) {
if (n <= 0) {
std::cout << “Blastoff!” << std::endl;
} else {
std::cout << n << “ ”; countDown(n - 1); // Recursive call
with a smaller value
}
}
int main() {
countDown(5);
return 0;
}

Explanation: In this example, we have a recursive function countDown that counts


down from a given number n until it reaches 0. The base case is when n is less than or
equal to 0, where the function prints “Blastoff!” and returns, terminating the recursion.
In the recursive case, the function prints the current value of n and then calls itself with
n-1, reducing the value by 1. This process continues until the base case is reached, at
which point the recursion stops.

Without the base case, the recursive function would continue indefinitely, resulting
in an infinite loop. In this example, the base case ensures that the recursion terminates
when n becomes 0 or negative. As a result, the countdown progresses in a well-
defined manner, printing the numbers in descending order until “Blastoff!” is reached.
By providing a clear and reachable base case, we prevent the recursion from
causing a stack overflow error, as the function knows when to stop and unwind the
recursive calls.

Self-Instructional
124 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Recursion - Iteration Through Self-Reference

These are just a few examples of how recursive algorithms find application in NOTES
various domains. Recursive thinking and problem-solving skills are essential for
programmers to solve complex problems and optimize solutions efficiently.

In-Text Questions
Q1. What is recursion in programming?
A. A programming language feature used for creating loops.
B. A process in which a function calls itself directly or indirectly.
C. A way to limit the number of function calls.
D. A programming technique to sort arrays.
Q2. What is the essential condition for a recursive function to terminate?
A. The function must have a ‘for’ loop inside.
B. The function must call another function.
C. The function must not return any value.
D. The function must have a base case.
Q3. What is the name given to the function that calls itself in a recursive process?
A. Calling function B. Recursive function
C. Base function D. Termination function
Q4. Which data structure is commonly used to implement recursion?
A. Queue B. Stack
C. Array D. Linked List
Q5. What is the term used to describe a scenario where a function directly or
indirectly calls itself in a never-ending loop?
A. Infinite loop B. Segmentation fault
C. Stack overflow D. Recursion limit

6.6 SUMMARY

Recursion is a powerful technique that allows us to solve complex problems by breaking Self-Instructional
them down into simpler subproblems. By using recursive calls and defining appropriate Material 125

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES base cases, we can elegantly solve problems that exhibit repetitive and self-similar
patterns. Understanding recursion is crucial for every programmer, as it provides a
powerful tool for solving a wide range of computational problems.

Key points

 Breaking down complex problems: Recursion allows us to tackle complex


problems by breaking them down into smaller, more manageable subproblems.
By dividing a large problem into smaller parts, we can focus on solving each
subproblem independently. This simplifies the overall problem-solving process
and improves code readability.
 Solving repetitive and self-similar patterns: Recursion is particularly useful
for solving problems that exhibit repetitive or self-similar patterns. By defining a
recursive function, we can apply the same set of instructions to solve each
subproblem. This eliminates the need for repetitive code and leads to more
concise and elegant solutions.
 Recursive calls and base cases: Recursive calls are a fundamental aspect of
recursion. They involve invoking the same function within itself to solve
subproblems. However, to ensure the recursion terminates, it is crucial to define
appropriate base cases. Base cases act as stopping conditions that indicate
when the recursion should stop and return a result. Without well-defined base
cases, the recursion may lead to infinite loops or stack overflow errors.
 Wide applicability: Recursion is a versatile tool applicable to a wide range of
computational problems. It can be used in various domains, such as mathematics,
algorithms, data structures, and problem-solving in general. Understanding
recursion and its application empowers programmers to approach different
problem types efficiently and creatively.

Comparison

Linear Recursion: In the factorial example, the function breaks down the problem
into one smaller instance by making a single recursive call.
Binary Recursion: In the Fibonacci example, the function breaks down the problem
into two smaller instances by making two recursive calls.
Self-Instructional
126 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Recursion - Iteration Through Self-Reference

The key distinction is in the number of recursive calls made, and this choice NOTES
depends on the nature of the problem and how it can be effectively divided into
subproblems.
In conclusion, recursion offers a powerful approach to problem-solving, allowing
programmers to break down complex problems, handle repetitive patterns, and elegantly
solve computational challenges. It is an essential concept for programmers to grasp,
as it provides a valuable tool that can be applied across diverse problem domains.

6.7 GLOSSARY

 Recursion: A programming technique in which a function calls itself directly or


indirectly to solve a problem.
 Base Case: The condition in a recursive function that terminates the recursion
by providing a result without further recursive calls.
 Recursive Call: The act of a function invoking itself within its definition to
solve smaller instances of the same problem.
 Stack Overflow: A situation in which the call stack exceeds its maximum limit
due to excessive recursive function calls.
 Tail Recursion: A special case of recursion where the recursive call is the last
operation within the function, optimizing memory usage in certain programming
languages.
 Indirect Recursion: A situation where function A calls function B, which in
turn calls function A (or another function that eventually calls A), creating a loop
of recursive calls.
 Recursive Data Structure: Data structures that contain a smaller instance of
the same data structure within themselves, such as recursive linked lists or trees.
 Mutual Recursion: A scenario where two or more functions call each other in
a circular manner.
 Memoization: A technique used to improve the efficiency of recursive algorithms
by caching the results of function calls and reusing them instead of recalculating. Self-Instructional
Material 127

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES  Infinite Recursion: A situation where a recursive function lacks a proper


termination condition, causing it to run indefinitely.

6.8 ANSWERS TO IN-TEXT QUESTIONS

1. B. A process in which a function calls itself directly or indirectly.


2. D. The function must have a base case.
3. B. Recursive function
4. B. Stack
5. C. Stack overflow

6.9 SELF-ASSESSMENT QUESTIONS

1. Explain the concept of recursion and how it differs from iteration in programming.
2. Discuss the base case in recursion and its importance in preventing infinite loops.
3. Explain the process of a recursive function calling itself and the role of the call
stack in managing recursive calls.
4. Discuss the advantages and limitations of using recursion in programming.
5. Explain the concept of tail recursion and its significance in optimizing recursive
functions.
6. Describe the process of factorial calculation using recursion and illustrate it with
an example.
7. Discuss the application of recursion in solving problems like the Tower of Hanoi
and the Fibonacci sequence.
8. Explain the concept of indirect recursion and provide an example to illustrate it.
9. Discuss the factors to consider when choosing between iterative and recursive
approaches for problem-solving.
Self-Instructional
128 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Recursion - Iteration Through Self-Reference

10. Explain the role of recursion in data structures like trees and graphs and its NOTES
applications in algorithms.

6.10 REFERENCES

 Goodrich, M.T., Tamassia, R., & Mount, D., Data Structures and Algorithms
Analysis in C++, 2nd edition, Wiley, 2011.
 Cormen, T.H., Leiserson, C.E., Rivest, R. L., Stein C. Introduction to Algorithms,
4th edition, Prentice Hall of India, 2022.
 Drozdek, A., Data Structures and Algorithms in C++, 4th edition, Cengage
Learning, 2012.

6.11 SUGGESTED READINGS

 Sahni, S. Data Structures, Algorithms and Applications in C++, 2nd Edition,


Universities Press, 2011.
 Langsam Y., Augenstein, M. J., & Tanenbaum, A. M. Data Structures Using C
and C++, Pearson, 2015.

Self-Instructional
Material 129

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
UNIT IV

LESSON 7 TREES - NURTURING HIERARCHICAL


STRUCTURES IN COMPUTING
Trees - Nurturing Hierarchical Structures in Computing

LESSON 7 NOTES

TREES - NURTURING HIERARCHICAL


STRUCTURES IN COMPUTING

Name of Author: Dr Shweta Tyagi


Designation: Assistant Professor,
Shyama Prasad Mukherjee College
Email: [email protected]
Structure
7.1 Learning Objectives
7.2 Introduction
7.3 Trees: Definition and Properties
7.4 Binary Trees: Definition and Properties
7.5 Tree Traversal
7.6 Summary
7.7 Glossary
7.8 Answers to In-Text Questions
7.9 Self-Assessment Questions
7.10 References
7.11 Suggested Readings

7.1 LEARNING OBJECTIVE

 To understand the concept of a Binary search tree as a data structure.


 To use a Binary search tree for insertion, deletion, and searching of items.
 To learn to balance the tree structure in order to maintain the complexity of
operations.
 To analyse the time complexity of the algorithms.

Self-Instructional
Material 133

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES
7.2 INTRODUCTION

Objects that have a hierarchical structure can be organized easily with the help of a
Binary search tree. Moreover, this data structure is used to store and maintain a sorted
list of numbers. The structure of a tree with n nodes helps to search for the presence of
a number in O(log n) time.

7.3 TREE: DEFINITION AND PROPERTIES

A tree consists of nodes and arcs. The nodes of a tree are connected to each
other with arcs. The top node of the tree is called the root node, and the bottom
node is called the leaf node with no child nodes. Some examples of trees are
given in Figure 7.1.

(a) (b) (c)

Figure 7.1 Examples of Trees

Self-Instructional
134 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Trees - Nurturing Hierarchical Structures in Computing

NOTES
7.4 BINARY TREE: DEFINITION AND PROPERTIES

If each node of a tree has a maximum of two child nodes, then it is called a binary tree.
Trees shown in Figure 7.1 (a) and (b) are examples of binary trees, whereas Figure
7.1 (c) is not a binary tree.
In a binary tree, each child is categorized as either a left child or a right child.
The quantity of leaves in a binary tree is a crucial factor in determining the expected
efficacy of a sorting method. A leaf node in a binary tree is known as a terminal node
whose pointer to its children is null.
A node’s level, according to the definition of a binary tree, is equal to the path
length it took to reach there plus one. Therefore, we can conclude that the root will be
present at level 1, its offspring at level 2, and so forth. We are aware that every binary
tree only has one root (20 = 1) at level 1, two children (21 = 2) at level 2, a maximum
of four children (22 = 4) at level 3, and so on. This leads us to the conclusion that every
level i + 1 has a maximum of (2i) nodes. Any tree that meets this requirement is
referred to as a complete binary tree.
A binary tree’s internal nodes are those that have two children but do not meet
the criteria for being a leaf node or the root. The total number of edges on the longest
path from the root node to the leaf node determines the height or maximum depth of a
binary tree. In other words, the greatest number of edges from the root to the farthest
leaf node determines the height of a binary tree.
There are a variety of different operations that can be performed on binary trees.
 Creation of Binary tree
 Deletion of a node in a Binary tree
 Finding the number of nodes in a binary tree
 Finding the height of a binary tree
 Finding the number of leaves in a binary tree
 Deleting a Binary tree

Self-Instructional
Material 135

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES Properties of a Binary Tree

A binary tree is a tree data structure in which each node has at most two children,
which are referred to as the left child and the right child. Here are some key properties
of a binary tree:
 Root: The topmost node in a binary tree is called the root. It is the starting point
for traversing the tree.
 Node: Each element in a binary tree is called a node. Each node can have at
most two children.
 Parent and Child: A node in a binary tree is a parent to its left and right
children. Conversely, the left and right children are considered the children of
the parent node.
 Leaf Nodes: Nodes that do not have any children are called leaf nodes or
leaves. They are the nodes at the bottom of the tree.
 Internal Nodes: Nodes that have at least one child are called internal nodes.
They are not leaf nodes.
 Depth: The depth of a node is the length of the path from the root to that node.
The depth of the root is 0.
 Height (or Depth of the Tree): The height of a tree is the length of the longest
path from the root to a leaf. It is also the depth of the deepest node. The height
of an empty tree is typically considered to be -1.
 Subtree: A subtree of a node is a tree formed by a node and all its descendants.
 Binary Search Tree (BST): If a binary tree follows the property that for each
node, all elements in its left subtree are less than the node, and all elements in its
right subtree are greater than the node, then it is called a binary search tree.
 Traversal: Binary trees can be traversed in various ways, including in-order,
pre-order, and post-order traversals.
 Balanced Binary Tree: A binary tree is balanced if the height of the left and
right subtrees of any node differ by at most one. Balanced trees help maintain
efficient search and insertion operations.

Self-Instructional
136 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Trees - Nurturing Hierarchical Structures in Computing

 Full Binary Tree: A binary tree is full if every node has either 0 or 2 children. NOTES
No node has only one child.
 Complete Binary Tree: A binary tree is complete if all levels, except possibly
the last, are completely filled, and all nodes at the last level are as left as possible.
These properties and characteristics make binary trees versatile data structures
with applications in various algorithms and data storage structures.

7.5 TREE TRAVERSAL

Binary tree traversal refers to the process of visiting each node in a binary tree exactly
once, following a specific order.
There are three common types of binary tree traversals:
In-order, pre-order, and post-order.
Let us explore each of these traversal methods with an example.
Consider the following binary tree:
1
/\
2 3
/\
4 5

In-Order Traversal

In in-order traversal, we visit the left subtree, then the root, and finally the right subtree.
In-Order: 4, 2, 5, 1, 3
Pre-Order Traversal
In pre-order traversal, we visit the root, then the left subtree, and finally the right
subtree.
Pre-Order: 1, 2, 4, 5, 3
Self-Instructional
Material 137

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES Post-Order Traversal


In post-order traversal, we visit the left subtree, then the right subtree, and finally the
root.
Post-Order: 4, 5, 2, 3, 1

Example Code in C++:


#include <iostream>

struct Node {
int data;
Node* left;
Node* right;

Node(int value) : data(value), left(nullptr), right(nullptr)


{}
};

void inOrderTraversal(Node* root) {


if (root != nullptr) {
inOrderTraversal(root->left);
std::cout << root->data << “ ”;
inOrderTraversal(root->right);
}
}

void preOrderTraversal(Node* root) {


if (root != nullptr) {
std::cout << root->data << “ ”;
preOrderTraversal(root->left);
preOrderTraversal(root->right);
}
}
Self-Instructional
138 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Trees - Nurturing Hierarchical Structures in Computing

void postOrderTraversal(Node* root) { NOTES


if (root != nullptr) {
postOrderTraversal(root->left);
postOrderTraversal(root->right);
std::cout << root->data << “ ”;
}
}

int main() {
// Constructing the binary tree
Node* root = new Node(1);
root->left = new Node(2);
root->right = new Node(3);
root->left->left = new Node(4);
root->left->right = new Node(5);

// Perform traversals
std::cout << “In-Order Traversal:”;
inOrderTraversal(root);
std::cout << “\n”;

std::cout << “Pre-Order Traversal:”;


preOrderTraversal(root);
std::cout << “\n”;

std::cout << “Post-Order Traversal:”;


postOrderTraversal(root);
std::cout << “\n”;

return 0;
}

Self-Instructional
Material 139

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES
In-Text Questions
Q1. What is the maximum number of children a node can have in a binary tree?
A. 0 B. 1
C. 2 D. 3
Q2. In a binary tree, which node is at the topmost position?
A. Root node B. Leaf node
C. Internal node D. Sibling node
Q3. What is the height of a binary tree with only one node?
A. 0 B. 1
C. 2 D. Undefined
Q4. Which traversal visits the root node between the traversal of its left and right
subtrees?
A. Preorder traversal B. Inorder traversal
C. Postorder traversal D. Level order traversal
Q5. In a binary tree, what is the maximum number of nodes possible at the ‘k’
level?
A. 2^k B. k
C. 2^k - 1 D. k^2

7.6 SUMMARY

A hierarchical data structure consisting of nodes connected by edges/branches. It


consists of a root node, internal nodes, and leaf nodes. Each node can have zero or
more child nodes.

Types of Trees

 Binary Tree: A tree in which each node has at most two children: a left child
Self-Instructional
and a right child.
140 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Trees - Nurturing Hierarchical Structures in Computing

 Binary Search Tree (BST): A binary tree where the left child of a node contains NOTES
a value less than the node’s value, and the right child contains a value greater
than the node’s value.
 Full Binary Tree: A binary tree in which each node has either zero or two
children.
 Complete Binary Tree: A binary tree in which all levels are completely filled
except possibly the last level, and nodes are filled from left to right.
 Perfect Binary Tree: A binary tree in which all internal nodes have two children
and all leaf nodes are at the same level.
Traversal Techniques: Inorder Traversal: Traverse left subtree, visit root, traverse
right subtree. Preorder Traversal: Visit root, traverse left subtree, traverse right subtree.
Postorder Traversal: Traverse left subtree, traverse right subtree, visit root.

7.7 GLOSSARY

 Binary Tree: A tree data structure in which each node has at most two children,
typically referred to as the left child and right child.
 Binary Search Tree (BST): A binary tree in which the left child of a node
contains a value smaller than the node’s value, and the right child contains a
value greater than the node’s value, allowing efficient searching, insertion, and
deletion.
 Complete Binary Tree: A binary tree in which all levels except possibly the
last are completely filled, and all nodes at the last level are as left as possible.
 Full Binary Tree: A binary tree in which every node other than the leaves has
two children.
 Perfect Binary Tree: A binary tree in which all internal nodes have exactly
two children, and all leaf nodes are at the same level.
 Balanced Binary Tree: A binary tree in which the height of the two subtrees
of every node never differs by more than one.
Self-Instructional
Material 141

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES  Traversal: The process of visiting and accessing all nodes in a tree data structure
in a specific order, such as in-order, pre-order, or post-order traversal.

7.8 ANSWERS TO IN-TEXT QUESTIONS

1. C. 2
2. A. Root node
3. A. 0
4. A. Preorder traversal
5. A. 2^k

7.9 SELF-ASSESSMENT QUESTIONS

1. If h is the height of a binary tree, find


i. Maximum number of leaves
ii. Maximum number of nodes
iii. Maximum number of nodes at level i
2. Create binary search trees using an insertion algorithm for the elements
i. [12, 5, 23, 5, 28, 18, 4, 9, 10, 32, 20, 11]
ii. [2, 25, 13, 11, 20, 16, 14, 10, 18, 35, 26, 41]
iii. [5, 17, 13, 34, 41, 33, 14, 19, 26, 37, 20, 9]
iv. [8, 13, 33, 16, 29, 38, 14, 7, 5, 12, 25, 20]
3. From the binary search tree generated in Q.2, delete node 20 using both deletion
algorithms and find node 5 using a searching algorithm. Show all steps of
respective algorithms.
4. How many distinct binary search trees can be constructed out of 4 distinct
keys?
Self-Instructional
142 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Trees - Nurturing Hierarchical Structures in Computing

5. A set of n unique elements and an unlabelled binary tree with n nodes are provided NOTES
to us. How many ways can we fill the tree with the supplied set to make it a
binary search tree?

7.10 REFERENCES

 Goodrich, M.T., Tamassia, R., & Mount, D., Data Structures and Algorithms
Analysis in C++, 2nd edition, Wiley, 2011.
 Cormen, T.H., Leiserson, C.E., Rivest, R. L., Stein C. Introduction to Algorithms,
4th edition, Prentice Hall of India, 2022.
 Drozdek, A., Data Structures and Algorithms in C++, 4th edition, Cengage
Learning, 2012.

7.11 SUGGESTED READINGS

 Sahni, S. Data Structures, Algorithms and Applications in C++, 2nd Edition,


Universities Press, 2011.
 Langsam Y., Augenstein, M. J., & Tanenbaum, A. M. Data Structures Using C
and C++, Pearson, 2015.

Self-Instructional
Material 143

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
UNIT V

LESSON 8 BINARY SEARCH TREES - CRAFTING


ORDERED HIERARCHIES FOR
EFFICIENT RETRIEVAL

LESSON 9 HEAP DATA STRUCTURE


Binary Search Trees - Crafting Ordered Hierarchies for Efficient Retrieval

LESSON 8 NOTES

BINARY SEARCH TREES - CRAFTING ORDERED


HIERARCHIES FOR EFFICIENT RETRIEVAL

Name of Author: Dr Shweta Tyagi


Designation: Assistant Professor,
Shyama Prasad Mukherjee College
Email: [email protected]
Structure
8.1 Learning Objectives
8.2 Introduction
8.3 Binary Search Trees
8.4 Binary Search Tree Operations
8.4.1 Searching a Binary Search Tree
8.4.2 Insertion in a Binary Search Tree
8.4.3 Deletion from a Binary Search Tree
8.5 Balanced search Tree
8.6 Summary
8.7 Glossary
8.8 Answers to In-Text Questions
8.9 Self-Assessment Questions
8.10 References
8.11 Suggested Readings

8.1 LEARNING OBJECTIVES

 To understand the properties of a binary search tree.


 To learn how searching is optimized in a binary search tree due to its ordered
structure.

Self-Instructional
Material 147

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES
8.2 INTRODUCTION

Binary Search Trees (BSTs) stand as fundamental data structures in the realm of
computer science and are widely employed in various applications such as databases,
compilers, and file systems. A Binary Search Tree is a hierarchical structure that
organizes elements in a manner that allows for efficient search, insertion, and deletion
operations. It embodies the principles of binary search, providing a balanced and
ordered arrangement of data that facilitates rapid retrieval.
The Binary Search Tree are important because it optimizes the efficiency of
operations performed on a dataset. Traditional linear data structures, such as arrays or
linked lists, may have linear search times for various operations. However, Binary
Search Trees offer a logarithmic time complexity for search operations, enabling quicker
access to elements in a large dataset. This efficiency is due to the property of BSTs,
where each node has at most two children, and the left subtree of a node contains
values less than the node’s value, while the right subtree contains values greater than
the node’s value.
BSTs are also important in maintaining an ordered structure, making them
particularly advantageous for tasks like sorting and range queries. The logarithmic
time complexity ensures that even as the dataset grows, the performance of these
operations remains acceptable.
Additionally, Binary Search Trees find applications in scenarios where dynamic
data modification is a common requirement. The self-adjusting nature of certain variants,
such as Balanced Binary Search Trees (e.g., AVL trees or Red-Black trees), ensures
that the tree remains balanced, preventing degradation of performance over time. This
adaptability is important in scenarios where frequent insertions or deletions are required.
In essence, the Binary Search Tree form a foundational data structure that aligns
with the core principles of efficiency, order, and adaptability. Its utilization extends
across various computing domains, making it a key concept in the toolkit of any
computer scientist or software engineer.

Self-Instructional
148 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Binary Search Trees - Crafting Ordered Hierarchies for Efficient Retrieval

NOTES
8.3 BINARY SEARCH TREE

A binary search tree is a binary tree, as the name implies, that also has unique ordering
characteristics. Some properties separate a binary search tree from a regular binary
tree.
 All values stored in the right subtree of the tree are more than or equal to the
root node.
 All values stored in the left subtree of the tree are less than the root node.
 Both subtrees of each node are also binary search trees, that is, they have the
above two properties.
A binary search tree has a parent node that stores a key value that is bigger than
its left child’s key value but smaller than its right child’s key value. Figure 8.1 depicts
an example of a binary search tree. Because of the ordering and hierarchy, binary
search trees are more widely employed in searching algorithms than other data
structures.

Figure 8.1: Example of a Binary Search Tree

The binary search tree alike binary tree has a root node and every node in the
binary search tree is represented by its key value and two pointers for the left child and
right child of the node. Binary search trees provide an excellent structure for searching Self-Instructional
Material 149

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES a list and, at the same time, for inserting and deleting data into the list. Some basic
operations on a binary search tree are traversal of the tree, searching a node, inserting
a node and deletion of a node.

8.4 BINAY SEARCH TREE OPERATIONS

8.4.1 Searching a Binary Search Tree

Finding a particular element or node within a binary search tree is referred to as searching
it. However, because the elements in a binary search tree are stored in a certain order,
finding a specific node in a binary search tree is rather simple. A binary search tree can
be implemented using several data structures like an array, linked list, queue, stack,
etc., but every structure has limitations on its implementation. Given below are the
steps to find a key (k) or a node in a binary search tree (T), which is implemented
using a linked list.
Binary-Search-Tree(T, k)
if(root(T) == NULL) // Tree is empty
return(FALSE)
elseif(root(T).key == k) // Value k matches with the value
at the root node of the tree
return(TRUE)
elseif(root(T).key < k) // Value k is greater than the alue
at the root node of the tree

Binary-Search-Tree(Right(T).k) // Search the value k


in the right subtree
else // Value k is less than the value at the root node of
the tree
Binary-Search-Tree(Left(T).k) // Search the value k
in the left subtree

The number of comparisons that occurred when looking for the favourable
node determines the time complexity of the search. Recursion or iteration nodes
Self-Instructional
150 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Binary Search Trees - Crafting Ordered Hierarchies for Efficient Retrieval

encountered during the process generate a path from the root node. The number of NOTES
comparisons depends on how many nodes are encountered along this path. Therefore,
the complexity depends on the length of the path leading to the node plus one. If we
look at the algorithm, we will see that the comparison of the key value is either done
with the left child or the right child. This says that the search operation will perform one
comparison at each level. If we talk about the worst case, the key value would be
present at the leaf node, and we would have to traverse the whole tree to find the
node. So, the total number of comparisons will be equal to the tree’s height, and it
would take us O(lg n) time to find the element. In the best case, the element will be
found at the root, and traversing the whole tree would be unnecessary. It will take us
O(1)to locate the node.

8.4.2 Insertion in a Binary Search Tree

The insert function is used to add a new element at a proper location in a binary search
tree. The insert function must be created in such a way that it consistently violates the
binary search tree’s property. Insertion in a binary tree also resorts to comparisons to
find its right location in the tree. With key value , a tree node must be located, and the
new node must be attached to it in order to insert a new node. Steps to insert a node
with key value in the tree are as follows:
Insert-Binary-Search-Tree (T, k)
New-Node.key = k
Left(New-Node) = NULL
Right(New-Node) = NULL
if (root(T) == NULL)
root(T)=New-Node
return (T)
elseif (root(T).key < k)
Left(root(T)) = Insert-Binary-Search-Tree(Left(root(T)),k)
elseif (root(T).key > k)
Right(root(T)) = Insert-Binary-Search-Tree(Right(root(T)),k)

Like the searching algorithm, the total number of comparisons for looking at the
desired location will be equal to the tree’s height, and it would take us times to find the
Self-Instructional
Material 151

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES position to insert an element. In the best case, the element will be found at the root,
and it will take to insert the node.

8.4.3 Deletion from a Binary Search Tree

Another action required to maintain a binary search tree is deleting a node. A node
from a binary search tree can be removed using the delete function. However, we
must remove a node from a binary search tree in a way that does not break its basic
properties. Deleting a leaf node in a tree is the simplest one. We perform a delete
operation on the node, and no swapping will be necessary. In case of deleting a node
with one child, the node to be deleted must be swapped with its child node and then
deleted like a regular leaf node. Deleting a node that has two children and multiple
successors is where it becomes tricky. Since deleting the node could harm the binary
search tree property, steps should be taken to prevent it.
In case of deleting an internal node, we will have to swap the node with its in-
order successor and make it the leaf node to delete it. There are two approaches to
deleting a node in a binary search tree.

1. Deletion by Merging

The two subtrees of the node are combined into one tree in this method, which is then
joined to the parent of the node. Since every right node is greater than its left sibling, to
merge, we find a left node with the greatest key value and make it the parent of the
right child. The left subtree’s rightmost node is the required node. By navigating this
subtree and following the right pointers up until null is reached, it can be found. As a
result, this node will not have any right children, and setting the right pointer of the
rightmost node to the right subtree will not put the original tree’s binary search trees’
attribute in jeopardy. Figure 8.2 depicts an example of the deletion of a node in a
binary search tree by the method of merging.

Self-Instructional
152 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Binary Search Trees - Crafting Ordered Hierarchies for Efficient Retrieval

16 12
NOTES

10 14
12 21

10 14 19 27 13 21

Delete node
16
19 27
13 24

24
25

25

Figure 8.2: Deletion of node 16 in a Binary Search Tree by Merging

Now, to understand the complete working of deletion by merging method, the algorithm
is given below.
Delete-by-Merge-Binary-Search-Tree(T,node)
if (node  NULL)
if(Right(ode) == NULL) // Right subtree is empty
temp = node
node = Left(node)
elseif(Left(node) == NULL) // Left subtree is empty
temp = node
node = Right(node)
else // Node has both children
temp = Left(node)
while(Right(temp) NULL)
temp = Right(temp)
Right(temp) = Right(node)
temp = node
node = Left(node)
Free(temp) Self-Instructional
Material 153

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES 2. Deletion By copying

Deletion by copying is a simpler algorithm than deletion by merging. In deletion by


copying, we find the inorder successor, copy its content into the node to be deleted
and delete the inorder successor. This algorithm does not increase the height of the
tree as deletion by merging does, but it can cause a problem if it is applied with
insertions too many times. Figure 8.3 depicts an example of the deletion of a node in a
binary search tree by the method of merging.

16 14

12 21 12 21

10 14 19 27 Delete node 10 13 19 27
16

13 24 24

25 25

Figure 8.3: Deletion of Node 16 in a Binary Search Tree by Copying

Now, to understand the complete working of deletion by copying method, the algorithm
is given below.
Delete-by-Copy-Binary-Search-Tree(T.node)
if (node  NULL)
if(Right(ode) == NULL) // Right subtree is empty
temp = node
node = Left(node)
elseif(Left(node) == NULL) // Left subtree is empty
temp = node
node = Right(node)
else // Node has both children
Self-Instructional temp = Left(node)
154 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Binary Search Trees - Crafting Ordered Hierarchies for Efficient Retrieval

prev = node NOTES


while(Right(temp) NULL)
prev = temp
temp = Right(temp)
node.key = temp.key
if(prev == node)
Left(prev) = Left9temp)
else
Right(prev) = Left(temp)
Free(temp)

8.5 BALANCED SEARCH TREE

In earlier sections, we made the case that searching in a tree, as opposed to a linked
list, which is linear and one-dimensional, is faster due to the hierarchy and sorting it
offers. However, if a tree is asymmetrical, lopsided, or has a significant variance in the
height of the subtrees, it can sacrifice resources even though it offers a more two-
dimensional and approachable structure. If every leaf node on a tree is at the same
level, the tree is said to be perfectly balanced. Finding a node in a balanced tree is
simpler and takes far less time than searching a linked list. Thus, maintaining the balance
of the binary search tree becomes more important.
For a node in the tree, the difference between the heights of the left and right
subtree is called a balance factor. If the balance factor is either 0 or 1, the binary tree
is said to be height-balanced or simply balanced. Otherwise, the tree is said to be
unbalanced or out of balance. For a node in the tree, the difference between the height
of the left and right subtree is called a balance factor. For example, the binary search
tree given in Figure 8.4 (a) is unbalanced, as nodes 12 and 16 have a balance factor of
more than 1. Meanwhile, in Figure 8.4 (b), all nodes have a balance factor of either 0
or 1, hence showing a height-balanced tree.

Self-Instructional
Material 155

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES (2)
14
(0)
16

(1)
(2) (1)
(0)
11 16
12 21

(1)
(0) (0)
(0) (0) (0)
(1) (0) 21
27 9 12 19
10 14 19

(0) (0)
(1) (0) 27
8 10
8 11

(0)

(a) (b)

Figure 8.4: Binary Search Tree (a) Unbalanced and (b) Height-Balanced

Various methods are used to maintain the balance of a binary search tree. Some
of the rotational strategies are LL, RR, LR, and RL. These approaches are used at the
time of insertion and deletion of a node in a binary search tree.

In-Text Questions
Q1. What is the property that distinguishes a binary search tree (BST) from other
binary trees?
A. It has only two children per node
B. Its nodes are sorted in descending order
C. It has a root node and leaf nodes
D. It follows a specific ordering of elements within its nodes
Q2. In a binary search tree, which subtree contains elements greater than the root
node’s value?
A. Left subtree B. Right subtree
C. Both subtrees D. Neither subtree

Self-Instructional
156 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Binary Search Trees - Crafting Ordered Hierarchies for Efficient Retrieval

Q3. What is the time complexity of searching for an element in a balanced binary NOTES
search tree containing ‘n’ nodes?
A. O(log n) B. O(n)
C. O(n^2) D. O(1)
Q4. Which traversal technique visits the nodes of a binary search tree in ascending
order?
A. Preorder traversal B. Inorder traversal
C. Postorder traversal D. Level order traversal
Q5. What operation is used to add a new node to a binary search tree while
maintaining its properties?
A. Insertion B. Deletion
C. Traversal D. Rotation

8.6 SUMMARY

Binary search trees are binary trees where the parent’s key value is higher than its left
child and smaller than its right child.
A binary search tree can be used for a wide variety of tasks, including searching,
insertion, deletion, balancing, and many more. Finding an element in a binary search
tree is simpler than finding it in another data structure. Certain guidelines are followed
when performing operations on a binary search tree in order to avoid distorting the
binary search tree attribute.
The newly inserted node is compared to its parent during insertion, and if there
are any inconsistencies, the node is switched out as necessary. The deletion by merging
method and the deletion by copying method are the two deletion strategies used.
Finding the left node with the highest key value in the event of merging, we make it the
parent of the right child. In a copying situation, we must locate the in-order successor,
copy its content into the node that will be removed, and then delete the in-order
successor.
While researching them, we come across an unbalanced binary search tree as Self-Instructional
well. The binary tree is referred to as being out of balance if the heights of the left and Material 157

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES right subtrees deviate by more than 0 or 1. If a tree is out of balance, we use certain
algorithms to balance it in order to correct the nodes’ balance factor. Four different
rotational strategies are available: LL, RR, LR, and RL.
As all operations require the traversal of the binary search tree along the height
of the tree, the time complexity of almost all these algorithms is .

8.7 GLOSSARY

 Binary Search Tree (BST): A binary tree data structure where each node has
at most two children, and the left child contains a value less than the node’s
value, while the right child contains a value greater than the node’s value. This
arrangement allows for efficient searching, insertion, and deletion operations.
 Node: An individual element within a BST that contains a value and pointers to
its left and right children.
 Root Node: The topmost node in a BST, serving as the starting point for
traversal and the parent of all other nodes.
 Parent Node: A node that has child nodes directly connected to it.
 Child Node: Nodes directly connected to a parent node.
 Leaf Node: Nodes that have no child nodes and exist at the ends of branches.
 Internal Node: Nodes that have at least one child node and are not leaf nodes.
 Inorder Traversal: A traversal method that visits nodes in the sequence: left
subtree, current node, right subtree, resulting in an ordered sequence of values
in a BST.
 Preorder Traversal: A traversal method that visits nodes in the sequence:
current node, left subtree, right subtree, often used to create a copy of a BST.
 Postorder Traversal: A traversal method that visits nodes in the sequence: left
subtree, right subtree, current node, commonly used in deleting nodes from a
BST.
 Search Operation: The process of finding a specific value within a BST by
Self-Instructional
158 Material
comparing it with nodes based on the BST’s ordering property.

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Binary Search Trees - Crafting Ordered Hierarchies for Efficient Retrieval

 Insertion Operation: Adding a new node with a specific value into the BST NOTES
while maintaining its binary search tree properties.
 Deletion Operation: Removing a node from the BST while maintaining its
structural and ordering properties.
 Balanced BST: A BST in which the heights of the left and right subtrees of any
node differ by at most one, ensuring balanced performance for operations.
 Unbalanced BST: A BST in which the heights of the left and right subtrees of
nodes can differ significantly, leading to inefficient operations.

8.8 ANSWERS TO IN-TEXT QUESTIONS

1. D. It follows a specific ordering of elements within its nodes


2. B. Right subtree
3. A. O(log n)
4. B. Inorder traversal
5. A. Insertion

8.9 SELF-ASSESSMENT QUESTIONS

1. Define a Binary Search Tree (BST) and explain its properties. How does a
BST differ from other types of trees?
2. Explain the process of inserting elements into a BST. Discuss the rules for
maintaining the properties of a BST during insertion.
3. Discuss the importance of the BST property (left child < parent < right child)
and its significance in searching elements within the tree.
4. Explain the process of searching for an element in a BST. Discuss the algorithmic
approach and its time complexity.
5. Describe the operations of deletion in a BST. Discuss the scenarios and challenges Self-Instructional
Material 159
involved in deleting nodes from a BST.

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES 6. Discuss the advantages and limitations of Binary Search Trees in comparison to
other data structures.
7. What is the difference between a binary tree and a binary search tree?
8. What is the difference between a height-balanced and unbalanced binary search
tree?
9. Why is balancing important for binary search trees?

8.10 REFERENCES

 Goodrich, M.T, Tamassia, R., & Mount, D., Data Structures and Algorithms
Analysis in C++, 2nd edition. Wiley, 2011.
 Cormen, T.H., Leiserson, C.E., Rivest, R. L., Stein C. Introduction to Algorithms,
4th edition, Prentice Hall of India, 2022.
 Drozdek, A., Data Structures and Algorithms in C++, 4th edition, Cengage
Learning, 2012.

8.11 SUGGESTED READINGS

 Sahni, S., Data Structures, Algorithms and applications in C++, 2nd edition,
Universities Press, 2011.
 Tenenbaum, A. M., Augenstein, M. J., & Langsam Y., (2009), Data Structures
Using C and C++. 2nd edition. PHI.

Self-Instructional
160 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Heap Data Structure

LESSON 9 NOTES

HEAP DATA STRUCTURE

Name of Author: Dr Shweta Tyagi


Designation: Assistant Professor,
Shyama Prasad Mukherjee College
Email: [email protected]

Structure
9.1 Learning Objectives
9.2 Introduction
9.3 Binary Heap
9.3.1 Array Representation of Binary Heap
9.3.2 MAX-HEAPIFY Property
9.3.3 BUILD-MAX-HEAP
9.4 Priority Queue
9.4.1 Max Priority Queue and its Operation
9.4.2 Implementing Priority Queue Using Heap
9.5 Summary
9.6 Glossary
9.7 Self-Assessment Questions
9.8 Answers to In-Text Questions
9.9 References
9.10 Suggested Readings

9.1 LEARNING OBJECTIVES

 To learn the concept of the heap as a data structure


 To view the heap as an array object and perform operations on it
 To learn about Priority Queue and implement it using Binary Heap

Self-Instructional
Material 161

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES
9.2 INTRODUCTION

Heap is a special type of tree data structure that is based on the theory of binary trees.
Heaps are utilised in numerous well-known algorithms, including priority queue
implementation, the heap sort sorting algorithm, and Dijkstra’s method for determining
the shortest path. In general, heaps are the type of data structure to utilise when you
need to retrieve the maximum or smallest member quickly.

9.3 BINARY HEAP

A heap is a tree-based data structure which can be viewed as a nearly complete


binary tree that satisfies a heap property. The tree is also designed to be binary, meaning
that each node can have a maximum of two children. All levels of the tree are filled
except for the bottom one, which is filled from the left and up to a certain point until
nodes are present. If a binary tree satisfies the following two fundamental heap qualities,
it can be referred to as a heap.
1. Max-Heap: If the key value of a node in the tree is less than or equal to the key
value of its parent node and all the subtrees recursively adhere to the same
property in the data structure, a heap can be said to be a max-heap. As a result,
the tree’s root contains the largest possible element. Fig. 9.1(a) illustrates the
design of a max heap.
2. Min-Heap: If the key value of a node in the tree is greater than the key value of
its parent node and all the subtrees recursively adhere to the same property in
the data structure, a heap can be said to be a Min-heap. As a result, the tree’s
root contains the smallest possible element. Fig. 9.1(b) depicts the design of a
min heap.

Self-Instructional
162 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Heap Data Structure

NOTES

(a) (b)
Figure 9.1: Example of (a) Max-heap and (b) Min-heap

Notably, unlike in a parent-child connection, a binary heap does not impose


any ordering on its sibling nodes. This is why, although it can be considered partially
ordered, a heap is not a sorted structure.
As we consider heaps to be tree-like structures, the height of a heap is determined
by counting the number of edges along the longest downhill path or the number of
edges from the tree’s root to its leaf node. As we will see, the basic operations on
heaps execute in time at most proportionate to the height of the tree and so take
O(log n) time because they are based on a complete binary tree, which has n items.

9.3.1 Array Representation of Binary Heap

The heap can alternatively be thought of as an array object. Formally, to represent a


heap, an array A[1 : N] of length is considered with heap-size (n) that indicates how
many elements are stored in the heap within the array A. If the array does not contain
any elements, the heap will be regarded as empty.
A[1] is considered the root of the tree, and further children are determined by
the index of the parent node. For, given a node at index i, we can find the index of the
parent node, its left child and right child as well.
Parent(i) = floor(i)
Left(i) = 2i
Self-Instructional
Right(i) = 2i + 1
Material 163

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES We can try this idea on the nodes of the example shown in Figure 9.2. Here, a node at
index 2 has a parent node at index 1 and a left child at index 3. Similarly, we can
explore all other nodes.

Figure 9.1 Example of (a) Max-heap and (b) Min-heap

Although heaps can be implemented as arrays, or we can say that each heap is
an array, we must remember that not all arrays are heaps. If we examine the usage of
max-heap and min-heap, there are several of them that we use in our daily algorithms.
The most frequent application of max-heap, however, is when sorting a data structure
using the heapsort algorithm. The most typical application of min-heap is when we run
priority queue-based algorithms, which we will explore in the next sections.

9.3.2 MAX-HEAPIFY Property

An approach called MAX-HEAPIFY is used to modify data structures, typically arrays,


such that they adhere to the max-heap characteristic. It arranges the nodes in such a
way that the array can accommodate each of the properties that a max-heap maintains.
For an array A, an ith element could be smaller than its left and right children. Hence,
the subtree rooted at index i to follow to the max-heap property, MAX-HEAPIFY
allows the value at A[i] to float down in the tree and makes it a max-heap. Given the
heap size as n, the working of MAX-HEAPIFY is illustrated as given below.
MAX-HEAPIFY(A,i)
Self-Instructional l  left(i)
164 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Heap Data Structure

r  Right(i) NOTES
if l  heap-size
Largestlargest(A[i], A[l])
if r  heap-size
Largestlargest(A[Largest], A[r])
if(i  Largest)
Swap(A[i], A[Largest])

The largest variable in the MAX-HEAPIFY method, it contains the index of the
elements in a parent-child relationship with the highest key value. If the largest equals
the parent node’s index, the function terminates and returns. Suppose the largest is not
equal to the parent node’s index. In that case, the children node is swapped with the
parent node, and MAX-HEAPIFY is called recursively on other subtrees to satisfy
additional max-heap properties. The running time of MAX-HEAPIFY is .
MAX-HEAPIFY is a very helpful algorithm that can be combined and used with
other heap techniques. When a node is added to a heap, it becomes the leaf node of
the heap, which may alter the heap’s properties. In order to arrange the heap desirably,
MAX-HEAPIFY is used in this situation. Like deletion, if removing a node causes the
heap to become distorted and no longer satisfy the heap property, MAX-HEAPIFY
aids in repairing the heap. One such algorithm of the heap is BUILD-MAX-HEAP.

9.3.3 BUILD-MAX-HEAP

The technique uses the bottom-up method of invoking MAX-HEAPIFY from an array
of size n, which is called BUILD-MAX-HEAP. Observe Figure 9.2 and notice the
array representation for storing an n-element heap. The heap has the leaf nodes at
indexes . Since leaf nodes have no children, so we do not need to run MAX-HEAPIFY
on them. Thus, to save waste of time and space, the BUILD-MAX-HEAP algorithm is
written in the way given below.
BUILD-MAX-HEAP(A,n)
n
for i = - 1 downto 1
2
Max-Heapify(A,i)

Self-Instructional
Material 165

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES We know that the running time of MAX-HEAPIFY is , and BUILD-MAX-


HEAP makes calls of MAX-HEAPIFY. Thus, the running time of BUILD-MAX-
HEAP is.
Use the function BUILD-MIN-HEAP (which is the same as BUILD-MAX-
HEAP) but with a call to MIN-HEAPIFY in place of MAX-HEAPIFY in the algorithm,
to build a min-heap. From an unordered linear array, BUILD-MIN-HEAP creates a
min-heap in linear time.

9.4 PRIORITY QUEUE

In the real world, heap data structures have several uses, but the most common and
effective use is as a priority queue. A priority queue is a unique kind of queue where
each element is arranged in a hierarchy and has associated priorities. Operations are
performed according to the priority of elements. That is, an element with higher priority
is attended first. Generally, the key value of the element itself is considered for assigning
the priority. We can also set priorities according to our needs.
Binary heaps can also be used to implement priority queues. In general, there
are two types of priority queues: max-priority queues and min-priority queues. We
will study both specifications clearly in this section.

9.4.1 Max Priority Queue and its Operation

A max-priority queue is a queue where the highest element is given the maximum
priority, i.e., larger elements are in the front of the queue. Multiple operations can
occur on a max priority queue, , as given below:
 Max(A): Returns element of queue with the largest key value.
 Extract-Max: Removes and returns the element of queue with the largest key
value.
 Insert: Insert element into the queue such that
 Increase-key: Changes the key value of to , assuming .
Self-Instructional Task scheduling, shortest path algorithms, event-driven simulations, Huffman
166 Material
coding, and heap sort are just a few of the many uses for priority queues. Additionally,

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Heap Data Structure

they are employed in several computer science and engineering disciplines that call for NOTES
the sorting and searching of data according to priority, as well as in network routing
methods. The priority queue is also used in Google Maps to find the shortest path to
save travelling time. Now, let us understand the workings of the Max Priority Queue.

9.4.2 Implementing Priority Queue Using Heap

The implementation of the different operations of a max-priority queue is based on the


use of max-heaps. Let us discuss their functioning in detail.

MAX-HEAP

This operation can be used to find the maximum element in the array object of the
max-heap. If we notice the structure of a max-heap, the maximum element is found in
its root. Hence, in array , the element at index 1 will be the maximum. Using this idea,
the MAX-HEAP(A) operation works in the following way.
MAX-HEAP(A)
if heap-size  1
return A[1]
else
Display “Error Message: Heap Underflow”

The running time of MAX-HEAP on a heap of size n is.

EXTRACT-MAX-HEAP

This function also uses MAX-HEAPIFY and MAX-HEAP functions to operate.


EXTRACT-MAX-HEAP(A) first removes the element from the root. For the case of
a max-heap, it will be maximum. Then, it takes the final element from the last level of
the heap, sets it in place of the root and then executes MAX-HEAPIFY.
EXTRACT-MAX-HEAP(A)
M = Max-Heap(A)
A[1] = A[heap-size]
Heap-size = heap-size–1
MAX-HEAPIFY(A,1)
return(M) Self-Instructional
Material 167
The running time of EXTRACT-MAX-HEAP on a heap of size n is O(log n) .

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES HEAP-INCREASE-KEY

This function first checks to make sure the key in the object x will not decrease due to
the new key k, and if there are no issues, it provides x the new key value. The process
then locates the array index i that corresponds to object x so that x is A[i]. The
operation also compares the value of the new A[i] with its parent to preserve the
max-heap property like MAX-HEAPIFY since raising the key of A[i] can violate the
max-heap.
HEAP-INCREASE-KEY(A,x,k)
if k < A[x]
Display “Error Message: New key is smaller than current
key.”
A[x] = k
while(x > 1 and A[Parent(x)] < A[x])
Swap(A[x], A[Parent(x)])
x = Parent(x)

The running time of HEAP-INCREASE-KEY on a heap of size n is O(log n).

MAX-HEAP-INSERT
It requires the array A that implements the max-heap, the new object x that is to be
added to the max-heap, and the array A’s size n as inputs. The procedure checks to
see if there is space in the array for the new element first. The max-heap is then
increased by including a new leaf in the tree whose key is – . The key of this new
element is then set to the appropriate value, maintaining the max-heap attribute, and
HEAP-INCREASE-KEY is called to insert an element into the queue.
MAX-HEAP-INSERT(A,x,n)
if heap-size == n
Display “Error Message: Heap Overflow”
heap-size = heap-size + 1
A[heap-size] = -
HEAP-INCREASE-KEY(A,heap-size,x)

The running time of MAX-HEAP-INSERT on a heap of size n is O(log n) .


On the other hand, a min-priority queue supports the operations MIN-HEAP,
Self-Instructional EXTRACT-MIN-HEAP, HEAP-DECREASE-KEY, and MIN-HEAP-INSERT. An
168 Material event-driven simulator can make use of a min-priority queue.

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Heap Data Structure

NOTES
In-Text Questions
Q1. What is the time complexity of heapify operation in a binary heap with 'n'
elements?
a) O(1) b) O(log n)
c) O(n) d) O(n log n)
Q2. Which type of heap is suitable for implementing a priority queue where the
element with the highest priority is processed first?
a) Min Heap b) Max Heap
c) Binary Heap d) D-ary Heap
Q3. Which of the following is a common application of a heap data structure?
a) Graph traversal
b) Searching in a sorted array
c) Sorting linked lists
d) Implementing Dijkstra's algorithm for shortest paths

9.5 SUMMARY

A heap data structure is represented by a binary heap that takes the form of an almost
complete binary tree. For the implementation of Priority queues, binary heaps are
frequently employed. In 1964, J. W. J. Williams invented the binary heap as a data
structure for heapsort.
When it is required to repeatedly delete the item with the highest (or lowest)
priority or when insertions must be spaced out with root node deletions, a heap is a
useful data structure.
The first-in, first-out rule is used in queues; however, in priority queues, values
are deleted according to priority. The highest-priority component is eliminated first.
Priority queues can be implemented using different data structures like an array,
a linked list, a heap, or a binary search tree. One of these data structures that effectively
implements priority queues is the heap data structure. Self-Instructional
Material 169

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES A binary heap can be built with running time , and a heap can support any
priority-queue operation on a set of size in time.

9.6 GLOSSARY

 Heap: The heap is a specialized tree-based data structure that satisfies the
heap property. It is commonly used to implement priority queues.
 Heap Property: The heap property defines the order of elements in a heap.
For a max heap, each parent node must be greater than or equal to its children,
and for a min heap, each parent must be less than or equal to its children.
 Max Heap: A type of heap where the value of each parent node is greater than
or equal to the values of its children.
 Min Heap: A type of heap where the value of each parent node is less than or
equal to the values of its children.
 Heapify: The process of maintaining the heap property (either max heap or
min heap) by rearranging elements in a heap after insertion or deletion.
 Heap Sort: A sorting algorithm that uses the heap data structure. It involves
building a max heap and repeatedly extracting the maximum element.
 Priority Queue: An abstract data type that operates similar to a queue but
assigns a priority level to each element. Priority queues are often implemented
using heaps.
 Binary Heap: A specific type of heap where each node has at most two
children. Binary heaps are commonly used due to their efficient representation
as arrays.
 D-ary Heap: A generalization of the binary heap where each node can have up
to D children. A binary heap is a 2-ary heap.
 Parent Node: In a heap, a parent node is a node that has one or more child
nodes.
 Child Node: In a heap, a child node is a node that is directly connected to a
Self-Instructional parent node.
170 Material

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Heap Data Structure

 Sibling Nodes: Nodes that share the same parent in a heap are called sibling NOTES
nodes.
 Leaf Node: A node in a heap that has no children. All leaf nodes are typically
found at the last level of the heap.
 Heap Size: The number of elements currently present in the heap.
 Complete Binary Tree: A binary tree in which all levels are completely filled
except, possibly, for the last level, which is filled from left to right.
 Heap Operations: Operations like insertion, deletion (extract-max or extract-
min), and heapify that are performed on a heap.
 Decrease Key: An operation in a heap where the value of a key is decreased,
and then the heap is adjusted to maintain the heap property.
 Increase Key: An operation in a heap where the value of a key is increased,
and then the heap is adjusted to maintain the heap property.

9.7 SELF-ASSESSMENT QUESTIONS

1. What is the purpose of designing a heap data structure? Give some applications.
2. What is the difference between Max-heap and Min-heap?
3. What is the difference between heap size and array length ?
4. Represent an array as a heap structure.
5. Find the key values of the parent node, left child, and right child of the node at
index 4 and 6 of the heap represented as an array .
6. Find the leaf nodes and root node of the heap represented as an array . Also,
find the height of the heap using the number of nodes present in the heap.
7. Write the algorithm for MIN-HEAPIFY and BUILD-MIN-HEAP.
8. Given an array, use the MAX-HEAPIFY algorithm to build a Max-heap and
show all steps of computations performed.
9. Given an array, use the MIN-HEAPIFY algorithm to produce a Max-heap and
show all steps of computations performed. Self-Instructional
Material 171

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
Data Structure

NOTES 10. Given a heap, explain the function of HEAP-EXTRACT-MAX algorithm.


11. Given a heap, illustrate the operation of MAX-HEAP-INSERT algorithm.
12. Write pseudocodes for the procedures MIN-HEAP, EXTRACT-MIN-HEAP,
HEAP-DECREASE-KEY, and MIN-HEAP-INSERT that implement a min-
priority queue with a min-heap.
13. What is the need to set the key of the inserted node to in the MAX-HEAP-
INSERT procedure when the next thing we do is increase its key to the desired
value?
14. Why do we check for heap overflow and underflow situations?

9.8 ANSWERS TO IN-TEXT QUESTIONS

1. B) O(log n)
2. B) Max Heap
3. D) Implementing Dijkstra's algorithm for shortest paths

9.9 REFERENCES

 Goodrich, M.T, Tamassia, R., & Mount, D., Data Structures and Algorithms
Analysis in C++, 2nd edition. Wiley, 2011.
 Cormen, T.H., Leiserson, C.E., Rivest, R. L., Stein C. Introduction to Algorithms,
4th edition, Prentice Hall of India, 2022.

9.10 SUGGESTED READINGS

 Drozdek, A., Data Structures and Algorithms in C++, 4th edition, Cengage
Learning, 2012.
Self-Instructional  Sahni, S., Data Structures, Algorithms and applications in C++, 2nd edition,
172 Material
Universities Press, 2011.

© Department of Distance & Continuing Education, Campus of Open Learning,


School of Open Learning, University of Delhi
NOTES
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
NOTES
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
1606-Data Structures [BAP-CA-S2I-Minor] Cover Jan25.pdf - January 19, 2025

You might also like