0% found this document useful (0 votes)
12 views

D.S BCA III 2024-25 Notes Assignments

The document outlines the curriculum for a BCA III Semester course on Data Structures using C and C++, taught by Davesh Bhardwaj for the academic year 2024-25. It covers various topics including arrays, stacks, queues, linked lists, trees, B-trees, and sorting techniques, detailing their characteristics, operations, and implementations. Each unit provides foundational knowledge essential for understanding data structures and their applications in programming.
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)
12 views

D.S BCA III 2024-25 Notes Assignments

The document outlines the curriculum for a BCA III Semester course on Data Structures using C and C++, taught by Davesh Bhardwaj for the academic year 2024-25. It covers various topics including arrays, stacks, queues, linked lists, trees, B-trees, and sorting techniques, detailing their characteristics, operations, and implementations. Each unit provides foundational knowledge essential for understanding data structures and their applications in programming.
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/ 50

BCA III Semester

Course Name: DATA STRUCTURE USING C, C++


Course Code: 0327002
Faculty Name- Davesh Bhardwaj
Year- 2024-25

Course Name: DATA STRUCTURE USING C, C++


Course Code: 0327002
Faculty Name- Davesh Bhardwaj
Course Name: Data Structure Using C & C++
Course Code: 0327002 Internal/External Marks: 25/75 Credit: 4

UNIT-I
Introduction to Data Structure and its Characteristics Array
Representation of single and multidimensional arrays; Sparse arrays – lower and upper
triangular matrices and Tri diagonal matrices with Vector Representation also.
UNIT-II
Stacks and Queues : Introduction and primitive operations on stack; Stack application; Infix,
postfix, prefix expressions; Evaluation of postfix expression; Conversion between prefix,
infix and
postfix, introduction and primitive operation on queues, D- queues and priority queues.
UNIT-III
Lists: Introduction to linked lists; Sequential and linked lists, operations such as traversal,
insertion,
deletion searching, Two way lists and Use of headers
UNIT-IV
Trees: Introduction and terminology; Traversal of binary trees; Recursive algorithms for tree
operations such as traversal, insertion, deletion; Binary Search Tree
UNIT-V
B-Trees: Introduction, The invention of B-Tree; Statement of the problem; Indexing with
binary
search trees; a better approach to tree indexes; B-Trees; working up from the bottom;
Example for
creating a B-Tree
UNIT-VI
Sorting Techniques; Insertion sort, selection sort, merge sort, heap sort, searching
Techniques:
linear search, binary search and hashing

Course Name: DATA STRUCTURE USING C, C++


Course Code: 0327002
Faculty Name- Davesh Bhardwaj
UNIT-I: Introduction to Data Structures and Arrays
1. Introduction to Data Structures
A data structure is a way of organizing and storing data in a computer so that it can
be accessed and modified efficiently.1 It's not just about holding data; it's about the
relationships between data elements and the operations that can be performed on
them.
Key Aspects of Data Structures:
• Organization: How data is arranged (e.g., sequentially, hierarchically, in a
network).
• Storage: How data is stored in the computer's memory.
• Efficiency: How efficiently data can be accessed, inserted, deleted, and
searched.
• Abstraction: Providing a high-level view of the data and operations, hiding
the underlying implementation details.
Characteristics of Data Structures:
• Correctness: The data structure should implement its interface correctly and
satisfy the intended purpose.
• Time Complexity: Measures the amount of time required to perform
operations on the data structure as a function of the input size. We often
analyze the best-case, average-case, and worst-case scenarios.
• Space Complexity: Measures the amount of memory space required by the
data structure as a function of the input size. This includes the space used to
store the data itself and any auxiliary space used during operations.
• Simplicity: The data structure should be simple to understand and
implement.
• Efficiency: Operations on the data structure should be efficient in terms of
both time and space.
Types of Data Structures (Broad Categories):
• Primitive Data Structures: Basic data types provided by a programming
language (e.g., integer, float, character, boolean).
• Non-Primitive Data Structures: More complex structures built using primitive
data structures. These can be further classified into:
o Linear Data Structures: Data elements are arranged in a sequential
order (e.g., Arrays, Linked Lists, Stacks, Queues).
o Non-Linear Data Structures: Data elements are not arranged in a
sequential order (e.g., Trees, Graphs).
2. Array
An array is a linear, contiguous data structure that stores a fixed-size collection of
elements of the same data type. Each element in an array is identified by its index or
position.
Diagram:
+---+---+---+---+---+
| A | B | C | D | E | <-- Elements
+---+---+---+---+---+
0 1 2 3 4 <-- Indices
Characteristics of Arrays:
• Fixed Size: The size of an array is determined at the time of declaration and
cannot be easily changed during runtime (in static arrays).
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
•Homogeneous Elements: All elements in an array must be of the same data
type.
• Contiguous Memory Allocation: Elements of an array are stored in adjacent
memory locations. This allows for efficient access to any element using its
index.
• Direct Access (Random Access): Any element in an array can be accessed
directly using its index in constant time, O(1). This is because the memory
address of any element can be calculated directly using the base address of
the array and the element's index.
• Easy Implementation: Arrays are relatively simple to implement.
3. Representation of Single and Multidimensional Arrays
3.1 Single-Dimensional Arrays (1D Arrays)
A 1D array represents a sequence of elements arranged in a single row or column.

Representation in Memory:
Elements are stored in consecutive memory locations. If the base address of the
array (address of the first element) is base_address, and each element occupies size
bytes, then the address of the element at index i can be calculated as:
Address(array[i]) = base_address + i * size
C Language Program (1D Array):
C
#include <stdio.h>

int main() {
int numbers[5]; // Declare an integer array of size 5

// Initialize array elements


numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;
numbers[3] = 40;
numbers[4] = 50;

// Access and print array elements


printf("Element at index 0: %d\n", numbers[0]);
printf("Element at index 3: %d\n", numbers[3]);

// Iterate through the array


printf("All elements: ");
for (int i = 0; i < 5; i++) {
printf("%d ", numbers[i]);
}
printf("\n");

return 0;
}
3.2 Multidimensional Arrays
Multidimensional arrays are used to represent data that has more than one
dimension. The most common types are two-dimensional (2D) arrays (matrices) and
three-dimensional (3D) arrays.

3.2.1 Two-Dimensional Arrays (2D Arrays)


Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
A 2D array can be visualized as a table with rows and columns.

Diagram:
Column 0 Column 1 Column 2
Row 0 [ 1 ] [ 2 ] [ 3 ]
Row 1 [ 4 ] [ 5 ] [ 6 ]
Row 2 [ 7 ] [ 8 ] [ 9 ]
Representation in Memory:
2D arrays are typically stored in memory using one of two main methods:
• Row-Major Order: Elements of each row are stored contiguously, one row
after another. For a 2D array array[R][C] (R rows, C columns), the address of
the element at array[i][j] is:
• Address(array[i][j]) = base_address + (i * C + j) * size
• Column-Major Order: Elements of each column are stored contiguously, one
column after another. For a 2D array array[R][C], the address of the element at
array[i][j] is:
• Address(array[i][j]) = base_address + (j * R + i) * size
(Note: Row-major order is more common in languages like C and C++, while
Fortran uses column-major order).
C Language Program (2D Array):
C
#include <stdio.h>

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

printf("Matrix elements:\n");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}

printf("Element at matrix[1][2]: %d\n", matrix[1][2]); // Output: 6

return 0;
}
3.2.2 Three-Dimensional Arrays (3D Arrays)
A 3D array can be visualized as a collection of 2D arrays stacked on top of each
other.

Diagram (Conceptual):
Imagine multiple matrices arranged in layers. If a 3D array is declared as array[L][R][C]
(L layers, R rows, C columns), you can think of it as L tables of R rows and C
columns.
Representation in Memory (Row-Major Order):

Course Name: DATA STRUCTURE USING C, C++


Course Code: 0327002
Faculty Name- Davesh Bhardwaj
The elements are typically stored by iterating through the layers, then rows within
each layer, and then columns within each row. The address of the element at
array[k][i][j] would be:
Address(array[k][i][j]) = base_address + (k * R * C + i * C + j) * size
C Language Program (3D Array):
C
#include <stdio.h>

int main() {
int cube[2][2][2] = {
{ {1, 2}, {3, 4} },
{ {5, 6}, {7, 8} }
};

printf("3D Array elements:\n");


for (int i = 0; i < 2; i++) {
printf("Layer %d:\n", i);
for (int j = 0; j < 2; j++) {
for (int k = 0; k < 2; k++) {
printf("%d ", cube[i][j][k]);
}
printf("\n");
}
printf("\n");
}

printf("Element at cube[1][0][1]: %d\n", cube[1][0][1]); // Output: 6

return 0;
}
4. Sparse Arrays
A sparse array is an array where most of the elements have a value of zero (or
some other default value). Storing all these zero elements can be inefficient in terms
of memory usage.
Example:
0 0 0 5 0
0 2 0 0 0
0 0 0 0 8
0 0 1 0 0
0 0 0 0 0
In this 5x5 matrix, most elements are zero.

Need for Special Representation:


To save memory, sparse arrays are often represented using alternative data
structures that only store the non-zero elements along with their indices.

4.1 Lower Triangular Matrices


A lower triangular matrix is a square matrix where all the elements above the main
diagonal are zero. The main diagonal consists of elements where the row index
equals the column index (i == j).
Diagram:
a11 0 0 0
a21 a22 0 0
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
a31 a32 a33 0
a41 a42 a43 a44
Vector Representation:
A lower triangular matrix of size n×n has at most n(n+1)/2 non-zero elements
(including the diagonal). These non-zero elements can be stored in a one-
dimensional array (vector) in a row-major or column-major order.
Row-Major Order: Elements are stored row by row. The index k in the vector for an
element aij (where i≥j) can be calculated as:
k = i * (i - 1) / 2 + j - 1
(Assuming 0-based indexing for both matrix and vector)

Column-Major Order: Elements are stored column by column. The index k in the
vector for an element aij (where i≥j) can be calculated as:
k = j * (2n - j - 1) / 2 + i - j
(Assuming 0-based indexing for both matrix and vector)

Algorithm (Storing Lower Triangular Matrix in a Vector - Row Major):


Algorithm StoreLowerTriangular(matrix, vector, n)
k=0
for i from 0 to n-1:
for j from 0 to i:
vector[k] = matrix[i][j]
k=k+1
end Algorithm
Algorithm (Retrieving Element from Vector to Lower Triangular Matrix - Row
Major):
Algorithm RetrieveLowerTriangular(vector, i, j, n)
if i < j:
return 0 // Element above diagonal is zero
else:
index_in_vector = i * (i + 1) / 2 + j
return vector[index_in_vector]
end Algorithm
(Note: Adjust index calculations if using 1-based indexing)

4.2 Upper Triangular Matrices


An upper triangular matrix is a square matrix where all the elements below the
main diagonal are zero.2
Diagram:
a11 a12 a13 a14
0 a22 a23 a24
0 0 a33 a34
0 0 0 a44
Vector Representation:
An upper triangular matrix of size n×n also has at most n(n+1)/2 non-zero elements.
These can be stored in a vector using row-major or column-major order.
Row-Major Order: The index k in the vector for an element aij (where i≤j) can be
calculated as:
k = i * n - i * (i + 1) / 2 + j - i
(Assuming 0-based indexing)

Column-Major Order: The index k in the vector for an element aij (where i≤j) can be
calculated as:
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
k = j * (j - 1) / 2 + i
(Assuming 0-based indexing)

Algorithm (Storing Upper Triangular Matrix in a Vector - Row Major):


Algorithm StoreUpperTriangular(matrix, vector, n)
k=0
for i from 0 to n-1:
for j from i to n-1:
vector[k] = matrix[i][j]
k=k+1
end Algorithm
Algorithm (Retrieving Element from Vector to Upper Triangular Matrix - Row
Major):
Algorithm RetrieveUpperTriangular(vector, i, j, n)
if i > j:
return 0 // Element below diagonal is zero
else:
index_in_vector = i * n - i * (i + 1) / 2 + j - i
return vector[index_in_vector]
end Algorithm
4.3 Tri-diagonal Matrices
A tri-diagonal matrix (also known as a Jacobi matrix) is a square matrix where all
the non-zero elements are on the main diagonal, the first super-diagonal (above the
main diagonal), and the first sub-diagonal (below the main diagonal).
Diagram:
b1 c1 0 0
a2 b2 c2 0
0 a3 b3 c3
0 0 a4 b4
Vector Representation:
A tri-diagonal matrix of size n×n has at most 3n−2 non-zero elements. These
elements can be stored efficiently in a one-dimensional array of size 3n−2. A
common way to store them is row by row, storing the sub-diagonal, main diagonal,
and super-diagonal elements consecutively.
Mapping:
Let the tri-diagonal matrix be M. We can store the elements in a vector V as follows:
• Elements of the sub-diagonal (ai where i>0)
• Elements of the main diagonal (bi)
• Elements of the super-diagonal (ci where i<n−1)
Algorithm (Storing Tri-diagonal Matrix in a Vector):
Algorithm StoreTriDiagonal(matrix, vector, n)
k=0
// Store sub-diagonal (elements a[i][i-1])
for i from 1 to n-1:
vector[k] = matrix[i][i-1]
k=k+1
// Store main diagonal (elements a[i][i])
for i from 0 to n-1:
vector[k] = matrix[i][i]
k=k+1
// Store super-diagonal (elements a[i][i+1])
for i from 0 to n-2:
vector[k] = matrix[i][i+1]
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
k=k+1
end Algorithm
Algorithm (Retrieving Element from Vector to Tri-diagonal Matrix):
Algorithm RetrieveTriDiagonal(vector, i, j, n)
if j == i - 1 and i > 0: // Sub-diagonal
return vector[i - 1]
else if j == i: // Main diagonal
return vector[n - 1 + i]
else if j == i + 1 and i < n - 1: // Super-diagonal
return vector[2 * n - 1 + i]
else:
return 0 // Element is zero
end Algorithm
C Language Program (Vector Representation of Tri-diagonal Matrix):
C
#include <stdio.h>
#include <stdlib.h>

// Function to store a tri-diagonal matrix in a vector


int* storeTriDiagonal(int matrix[][4], int n) {
int *vector = (int*)malloc(sizeof(int) * (3 * n - 2));
int k = 0;

// Sub-diagonal
for (int i = 1; i < n; i++) {
vector[k++] = matrix[i][i - 1];
}
// Main diagonal
for (int i = 0; i < n; i++) {
vector[k++] = matrix[i][i];
}
// Super-diagonal
for (int i = 0; i < n - 1; i++) {
vector[k++] = matrix[i][i + 1];
}
return vector;
}

// Function to retrieve an element from the vector representation


int retrieveTriDiagonal(int *vector,

Course Name: DATA STRUCTURE USING C, C++


Course Code: 0327002
Faculty Name- Davesh Bhardwaj
UNIT-II: Stacks and Queues
1. Stacks
1.1 Introduction to Stacks
A stack is a linear data structure that follows the Last-In, First-Out (LIFO) principle.
This means that the last element inserted into the stack1 is the first one to be
removed. Think of it like a stack of plates – you can only add or remove plates from
the top.2
Analogy: A stack of books, a pile of coins, the undo/redo functionality in software.
Diagram:
+---+ <-- Top
|E|
+---+
|D|
+---+
|C|
+---+
|B|
+---+
| A | <-- Bottom
+---+
1.2 Primitive Operations on Stack
The fundamental operations that can be performed on a stack are:
• PUSH: Adds a new element to the top of the stack.
o Algorithm:
o Algorithm PUSH(stack, item, top, maxSize)
o if top == maxSize - 1 then
o print "Stack Overflow"
o else
o top = top + 1
o stack[top] = item
o end if
o end Algorithm
• POP: Removes the top element from the stack.
o Algorithm:
o Algorithm POP(stack, top)
o if top == -1 then
o print "Stack Underflow"
o return null // Or some indicator of an empty stack
o else
o item = stack[top]
o top = top - 1
o return item
o end if
o end Algorithm
• PEEK (or TOP): Returns the value of the top element of the stack without
removing it.
o Algorithm:
o Algorithm PEEK(stack, top)
o if top == -1 then
o print "Stack is empty"
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
o return null
o else
o return stack[top]
o end if
o end Algorithm
• ISEMPTY: Checks if the stack is empty.
o Algorithm:
o Algorithm ISEMPTY(top)
o if top == -1 then
o return true
o else
o return false
o end if
o end Algorithm
• ISFULL: Checks if the stack is full (when implemented using a fixed-size
array).
o Algorithm:
o Algorithm ISFULL(top, maxSize)
o if top == maxSize - 1 then
o return true
o else
o return false
o end if
o end Algorithm
C Language Program (Stack using Array):
C
#include <stdio.h>
#include <stdlib.h>

#define MAX_SIZE 100

int stack[MAX_SIZE];
int top = -1;

void push(int item) {


if (top == MAX_SIZE - 1) {
printf("Stack Overflow\n");
return;
}
stack[++top] = item;
printf("%d pushed to stack\n", item);
}

int pop() {
if (top == -1) {
printf("Stack Underflow\n");
return -1; // Or some error value
}
return stack[top--];
}

int peek() {
if (top == -1) {
printf("Stack is empty\n");
return -1; // Or some error value
}
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
return stack[top];
}

int isEmpty() {
return top == -1;
}

int isFull() {
return top == MAX_SIZE - 1;
}

int main() {
push(10);
push(20);
push(30);

printf("Top element is %d\n", peek());

printf("%d popped from stack\n", pop());


printf("%d popped from stack\n", pop());

printf("Is stack empty? %s\n", isEmpty() ? "Yes" : "No");

return 0;
}
1.3 Stack Applications
Stacks are used in various applications in computer science, including:
• Function Call Stack: When a function is called, its information (return
address, local variables, parameters) is pushed onto the stack. When the
function returns, this information is popped. This mechanism manages the
flow of execution in programs.
• Expression Evaluation: Stacks are crucial for evaluating arithmetic
expressions, especially postfix (RPN) expressions.
• Expression Conversion: Stacks are used to convert expressions between
infix, postfix, and prefix notations.
• Syntax Parsing: Compilers use stacks to parse the syntax of programming
languages.
• Backtracking Algorithms: Algorithms that explore all possible solutions
often use stacks to keep track of the path taken (e.g., solving mazes, finding
all permutations).
• Undo/Redo Functionality: Many applications use stacks to store the history
of operations, allowing users to undo and redo actions.
• Reversing a Sequence: Stacks can be used to easily reverse a sequence of
elements.
1.4 Infix, Postfix, Prefix Expressions
Arithmetic expressions can be written in three different notations:
• Infix Notation: The operator is placed between the operands. This is the
most common notation used by humans (e.g., a + b, (c * d) - e).
• Postfix Notation (Reverse Polish Notation - RPN): The operator is placed
after the operands (e.g., a b +, c d * e -).

Course Name: DATA STRUCTURE USING C, C++


Course Code: 0327002
Faculty Name- Davesh Bhardwaj
• Prefix Notation (Polish Notation): The operator is placed before the
operands (e.g., + a b, - * c d e).
Examples:
Infix Postfix Prefix

A+B AB+ +AB

(A + B) * C AB+C* *+ABC

A * (B + C) ABC+* *A+BC

1.5 Evaluation of Postfix Expression


Postfix expressions can be efficiently evaluated using a stack.

Algorithm:
1. Scan the postfix expression from left to right.
2. If an operand is encountered, push it onto the stack.
3. If an operator is encountered:
o Pop the top two operands from the stack (let the second popped
operand be the first operand for the operation, and the first popped
operand be the second).
o Perform the operation on the two operands.
o Push the result back onto the stack.
4. After scanning the entire expression, the final result will be the only element
remaining on the stack.
Example (Evaluate 2 3 + 4 *):
1. Scan 2: Push 2 onto the stack. Stack: [2]
2. Scan 3: Push 3 onto the stack. Stack: [2, 3]
3. Scan +: Pop 3, then pop 2. Calculate 2 + 3 = 5. Push 5 onto the stack. Stack:
[5]
4. Scan 4: Push 4 onto the stack. Stack: [5, 4]
5. Scan *: Pop 4, then pop 5. Calculate 5 * 4 = 20. Push 20 onto the stack. Stack:
[20]
6. End of expression. The result is 20.
C Language Program (Evaluation of Postfix Expression):
C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define MAX_STACK_SIZE 100

int stack[MAX_STACK_SIZE];
int top = -1;

void push(int item) {


if (top == MAX_STACK_SIZE - 1) {
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
printf("Stack Overflow\n");
exit(EXIT_FAILURE);
}
stack[++top] = item;
}

int pop() {
if (top == -1) {
printf("Stack Underflow\n");
exit(EXIT_FAILURE);
}
return stack[top--];
}

int evaluatePostfix(char *expression) {


int operand1, operand2, result;
for (int i = 0; expression[i] != '\0'; i++) {
if (isdigit(expression[i])) {
push(expression[i] - '0'); // Convert char digit to integer
} else if (expression[i] == ' ') {
continue; // Ignore spaces
} else {
operand2 = pop();
operand1 = pop();
switch (expression[i]) {
case '+':
result = operand1 + operand2;
break;
case '-':
result = operand1 - operand2;
break;
case '*':
result = operand1 * operand2;
break;
case '/':
if (operand2 == 0) {
printf("Division by zero error\n");
exit(EXIT_FAILURE);
}
result = operand1 / operand2;
break;
default:
printf("Invalid operator: %c\n", expression[i]);
exit(EXIT_FAILURE);
}
push(result);
}
}
return pop();
}

int main() {
char postfix[] = "2 3 + 4 *";
printf("Result of %s is %d\n", postfix, evaluatePostfix(postfix));
return 0;
}
1.6 Conversion Between Prefix, Infix, and Postfix
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
Stacks are commonly used for these conversions.

Infix to Postfix Conversion (Shunting-Yard Algorithm):


This algorithm uses a stack to hold operators.

Algorithm:
1. Initialize an empty output queue (for postfix expression) and an empty
operator stack.
2. Scan the infix expression from left to right.
3. If an operand is encountered, add it to the output queue.
4. If an opening parenthesis ( is encountered, push it onto the operator stack.
5. If a closing parenthesis ) is encountered, pop operators from the stack and
add them to the output queue until an opening parenthesis is encountered.
Pop and discard the opening parenthesis.3
6. If an operator is encountered:
o While the operator stack is not empty and the top operator has equal or
higher precedence than the current operator, pop the operator from the
stack and add it to the output queue.
o Push the current operator onto the stack.
7. After scanning the entire infix expression, pop any remaining operators from
the stack4 and add them to the output queue.
8. The resulting postfix expression is in the output queue.
Operator Precedence (Example):
Operator Precedence Associativity

^ Highest Right-to-left

*, / Medium Left-to-right

+, - Lowest Left-to-right

Infix to Prefix Conversion:


One way is to reverse the infix expression, treat ( as ) and ) as (, convert it to postfix
using the Shunting-Yard algorithm, and then reverse the resulting postfix expression.
Postfix to Infix Conversion:
Use a stack. Scan the postfix expression. If it's an operand, push it onto the stack. If
it's an operator, pop the top two operands, enclose them in parentheses with the
operator in between, and push the resulting string back onto the stack. The final
result will be on the stack.

Prefix to Infix Conversion:


Use a stack. Scan the prefix expression from right to left. If it's an operand, push it
onto the stack. If it's an operator, pop the top two operands, enclose them in
parentheses with the operator in between, and push the resulting string back onto
the stack. The final result will be on the stack.

(Implementation of these conversion algorithms in C can be more involved and is


often covered in more advanced data structures and algorithms courses.)
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
2. Queues
2.1 Introduction and Primitive Operations on Queues
A queue is a linear data structure that follows the First-In, First-Out (FIFO)
principle. This means that the first element5 inserted into the queue is the first one to
be removed.6 Think of it like a line of people waiting – the person who arrived first is
the first to be served.
Analogy: A waiting line at a ticket counter, a print queue, processing tasks in a
system.
Diagram:
Front --> [ A | B | C | D | E ] <-- Rear
Primitive Operations on Queues:
• ENQUEUE (or INSERT): Adds a new element to the rear (end) of the queue.
o Algorithm:
o Algorithm ENQUEUE(queue, item, rear, maxSize)
o if rear == maxSize - 1 then
o print "Queue Overflow"
o else
o rear = rear + 1
o queue[rear] = item
o end if
o end Algorithm
• DEQUEUE (or DELETE): Removes the element from the front of the queue.
o Algorithm:
o Algorithm DEQUEUE(queue, front, rear)
o if front == rear then // Queue is empty
o print "Queue Underflow"
o return null
o else
o item = queue[front]
o front = front + 1
o return item
o end if
o end Algorithm
• PEEK (or FRONT): Returns the value of the front element of the queue
without removing it.
o Algorithm:
o Algorithm PEEK_FRONT(queue, front, rear)
o if front == rear then
o print "Queue is empty"
o return null
o else
o return queue[front]
o end if
o end Algorithm
• ISEMPTY: Checks if the queue is empty.
o Algorithm:
o Algorithm ISEMPTY_QUEUE(front, rear)
o if front == rear then
o return true
o else
o return false
o end if
o end Algorithm
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
• ISFULL: Checks if the queue is full (when implemented using a fixed-size
array).
o Algorithm:
o Algorithm ISFULL_QUEUE(rear, maxSize)
o if rear == maxSize - 1 then
o return true
o else
o return false
o end if
o end Algorithm
C Language Program (Queue using Array):
C
#include <stdio.h>
#include <stdlib.h>

#define MAX_QUEUE_SIZE 100

int queue[MAX_QUEUE_SIZE];
int front = 0;
int rear = 0;

void enqueue(int item) {


if (rear == MAX_QUEUE_SIZE) {
printf("Queue Overflow\n");
return;
}
queue[rear++] = item;
printf("%d enqueued to queue\n", item);
}

int dequeue() {
if (front == rear) {
printf("Queue Underflow\n");
return -1; // Or some error value
}
return queue[front++];
}

int peekFront() {
if (front == rear) {
printf("Queue is empty\n");
return -1; // Or some error value
}
return queue[front];
}

int isEmptyQueue() {
return front == rear;
}

int isFullQueue() {
return rear == MAX_QUEUE_SIZE;
}

int main() {
enqueue(10);
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
enqueue(20);
enqueue(30);

printf("Front element is %d\n", peekFront());

printf("%d dequeued from queue\n", dequeue());


printf("%d dequeued from queue\n", dequeue());

printf("Is queue empty? %s\n", isEmptyQueue() ? "Yes" : "No");

return 0;
}
Circular Queue: A variation of the queue that overcomes the limitation of using
array-based queues where the front and rear pointers might reach the end of the
array even if there are empty slots at the beginning. In a circular queue, the pointers
wrap around.
3. D-Queues (Double-Ended Queues)
A D-queue (Double-Ended Queue) is a linear data structure that allows insertion and
deletion of elements from both ends (front and rear). It's a generalization of both
stacks and queues.
Types of D-Queues:
• Input-Restricted D-Queue: Allows insertion at only one end (usually rear) but
deletion from both ends.
• Output-Restricted D-Queue: Allows deletion from only one end (usually
front) but insertion at both ends.
Primitive Operations on D-Queues:
• Insert at Front: Adds an element to the beginning of the D-queue.
• Insert at Rear: Adds an element to the end of the D-queue.
• Delete from Front: Removes an element from the beginning of the D-queue.
• Delete from Rear: Removes an element from the end of the D-queue.
• Peek Front: Gets the element at

Course Name: DATA STRUCTURE USING C, C++


Course Code: 0327002
Faculty Name- Davesh Bhardwaj
UNIT-III: Lists
1. Introduction to Lists
A list is a linear data structure that stores a collection of data elements. Unlike
arrays, which have a fixed size and contiguous memory allocation, lists can
dynamically grow or shrink and their elements may not be stored contiguously in
memory.
There are two main ways to implement lists:
• Sequential Lists (using Arrays): Elements are stored in contiguous memory
locations, similar to arrays.
• Linked Lists: Elements are stored in non-contiguous memory locations, and
each element contains a pointer (or link) to the next element in the list.
2. Sequential and Linked Lists
2.1 Sequential Lists (Array-Based Lists)
As discussed in UNIT-I, using an array to implement a list provides:
• Contiguous Memory Allocation: Elements are stored next to each other.
• Direct Access: Accessing any element by its index takes constant time, O(1).
Limitations:
• Fixed Size (usually): Traditional arrays have a fixed size declared at
creation. Dynamic arrays can resize, but this operation can be time-
consuming.
• Insertion and Deletion: Inserting or deleting elements in the middle of an
array can be inefficient as it may require shifting subsequent elements,
resulting in O(n) time complexity.
2.2 Linked Lists
A linked list is a linear data structure where each element (called a node) contains
two parts:
• Data: The actual value being stored.
• Pointer (or Link): A reference to the next node in the list.
The list itself is represented by a pointer to the first node, often called the head. The
last node in the list has its pointer set to NULL (or a similar null value) to indicate the
end of the list.
Diagram:
Head --> [ Data | Next ] --> [ Data | Next ] --> [ Data | NULL ]
Types of Linked Lists:
• Singly Linked List: Each node has a pointer to the next node only.
• Doubly Linked List: Each node has pointers to both the next and1 the
previous nodes.
• Circular Linked List: The last node's Next pointer points back to the first
node, forming a cycle.
3. Operations on Singly Linked Lists
Let's focus on the fundamental operations performed on a singly linked list.

3.1 Traversal
Visiting each node in the list exactly once, typically to process or display the data.

Algorithm:
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
Algorithm Traverse(head)
currentNode = head
while currentNode is not NULL:
process currentNode.data
currentNode = currentNode.next
end while
end Algorithm
C Language Program (Traversal):
C
#include <stdio.h>
#include <stdlib.h>

// Structure for a node


struct Node {
int data;
struct Node *next;
};

// Function to create a new node


struct Node* createNode(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}

// Function to traverse and print the list


void traverseList(struct Node* head) {
struct Node* current = head;
printf("Linked List: ");
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}

int main() {
struct Node* head = createNode(10);
head->next = createNode(20);
head->next->next = createNode(30);

traverseList(head);

return 0;
}
3.2 Insertion
Adding a new node to the linked list at different positions.
• Insertion at the Beginning: The new node becomes the new head.
o Algorithm:
o Algorithm InsertAtBeginning(head, newData)
o newNode = createNode(newData)
o newNode.next = head
o head = newNode
o return head
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
o end Algorithm
o C Language Program:
C
struct Node* insertAtBeginning(struct Node* head, int newData) {
struct Node* newNode = createNode(newData);
newNode->next = head;
return newNode;
}

// In main function:
// head = insertAtBeginning(head, 5);
• Insertion at the End: Traverse to the last node and make its next pointer
point to the new node.
o Algorithm:
o Algorithm InsertAtEnd(head, newData)
o newNode = createNode(newData)
o if head is NULL:
o head = newNode
o return head
o end if
o currentNode = head
o while currentNode.next is not NULL:
o currentNode = currentNode.next
o end while
o currentNode.next = newNode
o return head
o end Algorithm
o C Language Program:
C
struct Node* insertAtEnd(struct Node* head, int newData) {
struct Node* newNode = createNode(newData);
if (head == NULL) {
return newNode;
}
struct Node* current = head;
while (current->next != NULL) {
current = current->next;
}
current->next = newNode;
return head;
}

// In main function:
// head = insertAtEnd(head, 40);
• Insertion at a Specific Position (after a given node): Traverse to the node
after which the new node needs to be inserted.
o Algorithm:
o Algorithm InsertAfter(previousNode, newData)
o if previousNode is NULL:
o print "Previous node cannot be NULL"
o return
o end if
o newNode = createNode(newData)
o newNode.next = previousNode.next
o previousNode.next = newNode
o end Algorithm
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
o C Language Program:
C
void insertAfter(struct Node* prevNode, int newData) {
if (prevNode == NULL) {
printf("Previous node cannot be NULL\n");
return;
}
struct Node* newNode = createNode(newData);
newNode->next = prevNode->next;
prevNode->next = newNode;
}

// In main function (assuming you have a pointer 'node2' to the second node):
// insertAfter(head->next, 25);
3.3 Deletion
Removing a node from the linked list.
• Deletion at the Beginning: The head pointer is moved to the second node.
o Algorithm:
o Algorithm DeleteAtBeginning(head)
o if head is NULL:
o print "List is empty"
o return NULL
o end if
o temp = head
o head = head.next
o free(temp) // Release the memory of the deleted node
o return head
o end Algorithm
o C Language Program:
C
struct Node* deleteAtBeginning(struct Node* head) {
if (head == NULL) {
printf("List is empty\n");
return NULL;
}
struct Node* temp = head;
head = head->next;
free(temp);
return head;
}

// In main function:
// head = deleteAtBeginning(head);
• Deletion at the End: Traverse to the second-to-last node and set its next
pointer to NULL.
o Algorithm:
o Algorithm DeleteAtEnd(head)
o if head is NULL:
o print "List is empty"
o return NULL
o end if
o if head.next is NULL: // Only one node
o free(head)
o return NULL

Course Name: DATA STRUCTURE USING C, C++


Course Code: 0327002
Faculty Name- Davesh Bhardwaj
o end if
o secondLast = head
o while secondLast.next.next is not NULL:
o secondLast = secondLast.next
o end while
o temp = secondLast.next
o secondLast.next = NULL
o free(temp)
o return head
o end Algorithm
o C Language Program:
C
struct Node* deleteAtEnd(struct Node* head) {
if (head == NULL) {
printf("List is empty\n");
return NULL;
}
if (head->next == NULL) {
free(head);
return NULL;
}
struct Node* secondLast = head;
while (secondLast->next->next != NULL) {
secondLast = secondLast->next;
}
struct Node* temp = secondLast->next;
secondLast->next = NULL;
free(temp);
return head;
}

// In main function:
// head = deleteAtEnd(head);
• Deletion at a Specific Position (of a given node): Traverse to the node
before the one to be deleted. Adjust the next pointer to skip the node to be
deleted.
o Algorithm:
o Algorithm DeleteNode(head, key) // Delete the first node with data equal to key
o if head is NULL:
o print "List is empty"
o return NULL
o end if
o if head.data == key:
o temp = head
o head = head.next
o free(temp)
o return head
o end if
o currentNode = head
o while currentNode.next is not NULL and currentNode.next.data != key:
o currentNode = currentNode.next
o end while
o if currentNode.next is NULL:
o print "Key not found"
o return head
o end if
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
o temp = currentNode.next
o currentNode.next = currentNode.next.next
o free(temp)
o return head
o end Algorithm
o C Language Program:
C
struct Node* deleteNode(struct Node* head, int key) {
if (head == NULL) {
printf("List is empty\n");
return NULL;
}
if (head->data == key) {
struct Node* temp = head;
head = head->next;
free(temp);
return head;
}
struct Node* current = head;
while (current->next != NULL && current->next->data != key) {
current = current->next;
}
if (current->next == NULL) {
printf("Key %d not found in the list\n", key);
return head;
}
struct Node* temp = current->next;
current->next = current->next->next;
free(temp);
return head;
}

// In main function:
// head = deleteNode(head, 20);
3.4 Searching
Finding a node with a specific data value in the list.

Algorithm:
Algorithm Search(head, key)
currentNode = head
while currentNode is not NULL:
if currentNode.data == key:
return currentNode // Return the node if found
end if
currentNode = currentNode.next
end while
return NULL // Key not found
end Algorithm
C Language Program (Searching):
C
struct Node* searchList(struct Node* head, int key) {
struct Node* current = head;
while (current != NULL) {
if (current->data == key) {
return current; // Found the node

Course Name: DATA STRUCTURE USING C, C++


Course Code: 0327002
Faculty Name- Davesh Bhardwaj
}
current = current->next;
}
return NULL; // Key not found
}

// In main function:
// struct Node* foundNode = searchList(head, 20);
// if (foundNode != NULL) {
// printf("Node with data %d found\n", foundNode->data);
// } else {
// printf("Node with data not found\n");
// }
4. Two Way Lists (Doubly Linked Lists)
A doubly linked list is a variation where each node has three parts:
• Data: The value stored.
• Next Pointer: Points to the next node in the list.
• Previous Pointer: Points to the previous node in the list.
Diagram:
Head <--> [ Prev | Data | Next ] <--> [ Prev | Data | Next ] <--> [ Prev | Data | NULL ]
The first node's Previous pointer is usually NULL, and the last node's Next pointer is
NULL.
Advantages over Singly Linked Lists:
• Backward Traversal: Allows traversal in both forward and backward
directions.
• Efficient Deletion: Deletion of a node is more efficient if the pointer to the
node to be deleted is known (no need to traverse to the previous node).
• Efficient Insertion Before a Node: Similar to deletion, insertion before a
given node is more efficient.
Operations on Doubly Linked Lists:
The insertion, deletion, and traversal operations are similar to singly linked lists but
need to handle the previous pointers as well. For example, when inserting a node,
you need to update the previous pointer of the new node and the previous pointer of
the node that comes after it. Similarly, during deletion, you need to update the
previous pointer of the node after the deleted node.
C Language Program (Doubly Linked List Node Structure):
C
struct DoublyNode {
int data;
struct DoublyNode *next;
struct DoublyNode *prev;
};
Implementing the operations (insertion, deletion, traversal, searching) for a doubly
linked list involves careful manipulation of both next and prev pointers.
5. Use of Headers (Header Nodes)
A header node (or dummy node) is an extra node added at the beginning of a linked
list. This node typically does not store any meaningful data.
Diagram (Singly Linked List with Header):
Head --> [ Dummy | Next ] --> [ Data | Next ] --> [ Data | NULL ]
Advantages of Using Header Nodes:

Course Name: DATA STRUCTURE USING C, C++


Course Code: 0327002
Faculty Name- Davesh Bhardwaj
• Simplifies Empty List Handling: An empty list is represented by a header
node whose next pointer is NULL. This avoids special cases for empty lists in
many operations.
• Simplifies Insertion and Deletion at the Beginning: Insertion at the
beginning becomes the same as insertion after any other node. Deletion at
the beginning also becomes a regular deletion operation.
• Makes Code More Uniform: Many list operations can be implemented with
fewer special case checks.
Example (Insertion at the Beginning with Header Node):
C
struct Node* insertAtBeginningWithHeader(struct Node* head, int newData) {
struct Node* newNode = createNode(newData);
newNode->next = head->next;
head->next = newNode;
return head; // The header node itself doesn't change
}
Example (Deletion at the Beginning with Header Node):
C
struct Node* deleteAtBeginningWithHeader(struct Node* head) {
if (head->next == NULL) {
printf("List is empty (after header)\n");
return head;
}
struct Node* temp = head->next;
head->next = head->next->next;
free(temp);
return head;
}
Header nodes can also be used with doubly linked lists. They often have their prev
pointer pointing to NULL.

Course Name: DATA STRUCTURE USING C, C++


Course Code: 0327002
Faculty Name- Davesh Bhardwaj
UNIT-IV: Trees
1. Introduction and Terminology
A tree is a non-linear, hierarchical data structure that consists of nodes connected by
edges. It represents a hierarchical relationship among data elements.
Key Terminology:
• Node: A fundamental unit of a tree that can contain data and links
(references) to other nodes.
• Root: The topmost node in a tree. A tree has only one root.
• Edge: A connection between two nodes.
• Parent: A node that has one or more children.
• Child: A node directly below a parent node and connected to it by an edge.
• Siblings: Nodes that share the same parent.
• Leaf (or Terminal Node): A node with no children.
• Internal Node (or Non-Terminal Node): A node that has at least one child.
• Path: A sequence of consecutive edges from one node to another.
• Depth of a Node: The number of edges from the root to that node. The depth
of the root is 0.
• Height of a Node: The number of edges on the longest path from that node
to a leaf in its subtree. The height of a leaf is 0.
• Height of a Tree: The height of its root node.
• Degree of a Node: The number of children it has.
• Degree of a Tree: The maximum degree of any node in the tree.
• Subtree: A tree formed by a node and all its descendants.
• Forest: A collection of one or more disjoint trees.
Diagram:
A (Root)
/\
B C
/\ \
D E F (Leaf)
/
G (Leaf)

Terminology Example in the Diagram:


- Root: A
- Children of A: B, C
- Parent of B: A
- Siblings of B: C
- Leaf Nodes: G, E, F
- Internal Nodes: A, B, C, D
- Path from A to G: A -> B -> D -> G
- Depth of G: 3
- Height of G: 0
- Height of D: 1 (longest path to G)
- Height of B: 2 (longest path to D or E)
- Height of Tree: 3 (height of A)
- Degree of A: 2
- Degree of B: 2
- Degree of C: 1
- Degree of D: 1
- Degree of E: 0
- Degree of F: 0
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
- Degree of G: 0
- Subtree rooted at B:
B
/\
D E
/
G
2. Traversal of Binary Trees
A binary tree is a special type of tree where each node has at most two children,
referred to as the left child and the right child.1
There are three main ways to traverse a binary tree:
• Inorder Traversal: Visit the left subtree, then the root, then the right subtree
(Left - Root - Right).
• Preorder Traversal: Visit the root, then the left subtree, then the right subtree
(Root - Left - Right).
• Postorder Traversal: Visit the left subtree, then the right subtree, then the
root (Left - Right - Root).
Diagram (Example Binary Tree):
1
/\
2 3
/\
4 5
Traversal Sequences:
• Inorder: 4 -> 2 -> 5 -> 1 -> 3
• Preorder: 1 -> 2 -> 4 -> 5 -> 3
• Postorder: 4 -> 5 -> 2 -> 3 -> 1
3. Recursive Algorithms for Tree Operations
Recursive algorithms are naturally suited for tree operations due to the inherent
recursive structure of trees (a tree can be seen as a root and two subtrees).

3.1 Recursive Algorithms for Traversal


Algorithm Inorder(node)
if node is not NULL:
Inorder(node.left)
process node.data
Inorder(node.right)
end if
end Algorithm

Algorithm Preorder(node)
if node is not NULL:
process node.data
Preorder(node.left)
Preorder(node.right)
end if
end Algorithm

Algorithm Postorder(node)
if node is not NULL:
Postorder(node.left)
Postorder(node.right)
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
process node.data
end if
end Algorithm
C Language Program (Recursive Traversal):
C
#include <stdio.h>
#include <stdlib.h>

struct TreeNode {
int data;
struct TreeNode *left;
struct TreeNode *right;
};

struct TreeNode* createTreeNode(int data) {


struct TreeNode* newNode = (struct TreeNode*)malloc(sizeof(struct TreeNode));
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}

void inorderTraversal(struct TreeNode* root) {


if (root != NULL) {
inorderTraversal(root->left);
printf("%d ", root->data);
inorderTraversal(root->right);
}
}

void preorderTraversal(struct TreeNode* root) {


if (root != NULL) {
printf("%d ", root->data);
preorderTraversal(root->left);
preorderTraversal(root->right);
}
}

void postorderTraversal(struct TreeNode* root) {


if (root != NULL) {
postorderTraversal(root->left);
postorderTraversal(root->right);
printf("%d ", root->data);
}
}

int main() {
struct TreeNode* root = createTreeNode(1);
root->left = createTreeNode(2);
root->right = createTreeNode(3);
root->left->left = createTreeNode(4);
root->left->right = createTreeNode(5);

printf("Inorder traversal: ");


inorderTraversal(root);
printf("\n");

Course Name: DATA STRUCTURE USING C, C++


Course Code: 0327002
Faculty Name- Davesh Bhardwaj
printf("Preorder traversal: ");
preorderTraversal(root);
printf("\n");

printf("Postorder traversal: ");


postorderTraversal(root);
printf("\n");

return 0;
}
3.2 Recursive Algorithms for Other Tree Operations
• Insertion in a Binary Tree (Level Order): For a general binary tree (not
necessarily a Binary Search Tree), insertion is often done in a level-order
fashion to maintain completeness (if needed). This typically uses a queue and
is iterative.
• Insertion in a Binary Search Tree (BST): (Covered in Section 4)
• Deletion in a Binary Tree: Deletion in a general binary tree can be complex
to maintain structure. A common approach is to find the rightmost leaf node
and replace the node to be deleted with it, then delete the leaf. This is usually
implemented iteratively.
• Deletion in a Binary Search Tree (BST): (Covered in Section 4)
• Searching in a Binary Tree:
• Algorithm Search(node, key)
• if node is NULL:
• return false
• if node.data == key:
• return true
• return Search(node.left, key) or Search(node.right, key)
• end Algorithm
• Finding Height of a Binary Tree:
• Algorithm FindHeight(node)
• if node is NULL:
• return -1 // Height of an empty tree is -1
• leftHeight = FindHeight(node.left)
• rightHeight = FindHeight(node.right)
• return 1 + max(leftHeight, rightHeight)
• end Algorithm
C Language Program (Recursive Search and Height):
C
// (TreeNode structure from previous example)

int searchTreeNode(struct TreeNode* root, int key) {


if (root == NULL) {
return 0; // False
}
if (root->data == key) {
return 1; // True
}
return searchTreeNode(root->left, key) || searchTreeNode(root->right, key);
}

int findHeight(struct TreeNode* root) {


if (root == NULL) {
return -1;
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
}
int leftHeight = findHeight(root->left);
int rightHeight = findHeight(root->right);
return 1 + (leftHeight > rightHeight ? leftHeight : rightHeight);
}

int main() {
struct TreeNode* root = createTreeNode(1);
root->left = createTreeNode(2);
root->right = createTreeNode(3);
root->left->left = createTreeNode(4);
root->left->right = createTreeNode(5);

printf("Searching for 4: %s\n", searchTreeNode(root, 4) ? "Found" : "Not Found");


printf("Searching for 6: %s\n", searchTreeNode(root, 6) ? "Found" : "Not Found");

printf("Height of the tree: %d\n", findHeight(root));

return 0;
}
4. Binary Search Tree (BST)
A Binary Search Tree (BST) is a special type of binary tree that follows these
properties:
1. The left subtree of a node contains only nodes with keys less than the node's
key.
2. The right subtree of a node contains only nodes with keys greater than the
node's key. 3.2 Both the left and right subtrees must also be binary search
trees.
3. There3 are no duplicate keys (typically enforced).
Diagram (Example BST):
8
/\
3 10
/\ \
1 6 14
/\ /
4 7 13
4.1 Operations on Binary Search Trees
• Insertion: To insert a new key into a BST:
1. Start at the root.
2. If the key is less than the current node's key, move to the left child.
3. If the key is greater than the current node's key, move to the right child.
4. If the left or right child is NULL, insert the new node at that position.
5. If the key is equal to the current node's key (duplicates not allowed),
you can choose to do nothing or handle it based on your requirements.
Recursive Algorithm for Insertion in BST:
Algorithm InsertBST(node, key)
if node is NULL:
return createNewNode(key)
if key < node.data:
node.left = InsertBST(node.left, key)
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
else if key > node.data:
node.right = InsertBST(node.right, key)
return node
end Algorithm
• Searching: To search for a key in a BST:
1. Start at the root.
2. If the key is equal to the current node's key, the key is found.
3. If the key is less than the current node's key, search in the left subtree.
4. If the key is greater than the current node's key, search in the right
subtree.
5. If you reach a NULL node, the key is not in the BST.
Recursive Algorithm for Search in BST:
Algorithm SearchBST(node, key)
if node is NULL:
return false
if key == node.data:
return true
else if key < node.data:
return SearchBST(node.left, key)
else: // key > node.data
return SearchBST(node.right, key)
end Algorithm
• Deletion: Deleting a node from a BST involves several cases:
1. Node to be deleted is a leaf node: Simply remove it.
2. Node to be deleted has only one child: Replace the node with its
child.
3. Node to be deleted has two children:
§ Find either the inorder successor (smallest node in the right
subtree) or the inorder predecessor (largest node in the left
subtree).
§ Replace the data of the node to be deleted with the data of the
successor/predecessor.
§ Delete the successor/predecessor (which will now have at most
one child).
Recursive Algorithm for Deletion in BST:
Algorithm DeleteBST(node, key)
if node is NULL:
return NULL // Key not found

if key < node.data:


node.left = DeleteBST(node.left, key)
else if key > node.data:
node.right = DeleteBST(node.right, key)
else: // Key found (node to be deleted)
// Case 1: Leaf node
if node.left is NULL and node.right is NULL:
free(node)
return NULL
// Case 2: Only one child
else if node.left is NULL:
temp = node.right
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
free(node)
return temp
else if node.right is NULL:
temp = node.left
free(node)
return temp
// Case 3: Two children
else:
// Find inorder successor (smallest in right subtree)
successor = findMin(node.right)
node.data = successor.data
node.right = DeleteBST(node.right, successor.data)
return node
end Algorithm

Algorithm findMin(node)
while node.left is not NULL:
node = node.left
return node
end Algorithm
C Language Program (BST Operations):
C
#include <stdio.h>
#include <stdlib.h>

// (TreeNode structure from previous example)

struct TreeNode* insertBST(struct TreeNode* root, int key) {


if (root == NULL) {
return createTreeNode(key);
}
if (key < root->data) {
root->left = insertBST(root->left, key);
} else if (key > root->data) {
root->right = insertBST(root->right, key);
}
return root;
}

struct TreeNode* findMin(struct TreeNode* node) {


while (node->left != NULL) {
node = node->left;
}
return node;
}

struct TreeNode* deleteBST(struct TreeNode* root, int key) {


if (root == NULL) {
return NULL;
}

if (key < root->data) {


root->left = deleteBST(root->left, key);
} else if (key > root->data) {
root->right = deleteBST(root->right, key);
} else {
// Node with the key to be deleted
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
// Case 1: Leaf node
if (root->left == NULL && root->right == NULL) {
free(root);
return NULL;
}
// Case 2: Only one child
else if (root->left == NULL) {
struct TreeNode* temp = root->right;
free(root);
return temp;
} else if (root->right == NULL) {
struct TreeNode* temp = root->left;
free(root);
return temp;
}
// Case 3: Two children
else {
struct TreeNode* successor = findMin(root->right);
root->data = successor->data;
root->right = deleteBST(root->right, successor->data);
}
}
return root;
}

struct TreeNode* searchBST(struct TreeNode* root, int key) {


if (root == NULL || root->data == key) {
return root;
}
if (key < root->data) {
return searchBST(root->left, key);
} else {
return searchBST(root->right, key);
}
}

int main() {
struct TreeNode* root = NULL;
root = insertBST(root, 8);
root = insertBST(root, 3);
root = insertBST(root, 10);
root = insertBST(root, 1);
root = insertBST(root, 6);
root = insertBST(root, 14);
root = insertBST(root, 4);
root = insertBST(root, 7);
root = insertBST(root, 13);

printf("Inorder traversal of BST: ");


inorderTraversal(root);
printf("\n");

struct TreeNode* searchResult = searchBST(root, 6);


if (searchResult != NULL) {
printf("Found %d in BST\n", searchResult->data);
} else {
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
printf("6 not found in BST\n");
}

root = deleteBST(root, 6);


printf("Inorder traversal after deleting 6: ");
inorderTraversal(root);
printf("\n");

return 0;
}
Binary Search Trees

Course Name: DATA STRUCTURE USING C, C++


Course Code: 0327002
Faculty Name- Davesh Bhardwaj
UNIT-V: B-Trees
1. Introduction to B-Trees
A B-tree is a self-balancing tree data structure that is specifically designed to be
efficient for disk-based data storage systems. Unlike binary search trees, B-trees can
have more than two children per node. This property allows them to reduce the
height of the tree for a given number of keys, which in turn minimizes the number of
disk accesses required to find a particular key. Disk accesses are significantly slower
than in-memory operations, making B-trees highly suitable for databases and file
systems.
Key Characteristics of B-Trees:
• Self-Balancing: The tree automatically adjusts its structure to maintain
balance, ensuring efficient search, insertion, and deletion operations.
• Multi-Way Tree: Each node can have a large number of children (within a
defined range).
• Ordered Keys: Keys within each node are stored in sorted order.
• Range Queries: B-trees are efficient for range queries (finding all keys within
a certain interval).
• Disk-Optimized: Designed to minimize disk I/O operations by keeping related
keys in the same node.
2. The Invention of B-Tree
The B-tree was invented by Rudolf Bayer and Edward M. McCreight in 1972 while
working at Boeing Scientific Research Labs. The "B" in B-tree is often thought to
stand for "balanced," "Bayer," or "Boeing," but Bayer himself has stated that the
origin of the "B" is somewhat of a mystery.
3. Statement of the Problem: The Need for Efficient Disk-Based Indexing
Consider a large database stored on a hard disk. Searching for a specific record
based on a key using a simple linear scan would be extremely slow, requiring
potentially reading the entire database. To speed up this process, we use indexes.
An index is a data structure that allows us to quickly locate the records containing a
specific search key.
Challenges with Traditional Binary Search Trees for Disk-Based Indexing:
• High Tree Height: For a large number of keys, a binary search tree can
become very tall (in the worst case, a skewed tree can have a height of n,
where n is the number of keys). Searching in such a tree would require
traversing many levels, and if the tree resides on disk, each level access
could correspond to a slow disk I/O operation.
• Single Key per Node: Each internal node in a binary search tree typically
stores only one key. This means that a single disk block read might only
provide information to move down one level in the tree.
Goal: To create a tree structure that:
• Has a smaller height compared to a binary search tree for the same number
of keys.
• Maximizes the amount of information gained from each disk access.
4. Indexing with Binary Search Trees
While binary search trees provide logarithmic time complexity for search, insertion,
and deletion in memory, their performance degrades significantly when used for
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
disk-based indexing due to the high number of disk accesses caused by their
potentially large height.

Example: A balanced binary search tree with N keys has a height of approximately
log2(N). If N is very large (e.g., billions of records), the number of disk accesses for a
single search could be in the order of 30 or more, which is unacceptable for efficient
database operations.
5. A Better Approach to Tree Indexes: Multi-Way Trees
To reduce the height of the tree, we need a tree where each node can have multiple
children. This is the fundamental idea behind multi-way trees. If each node can hold
multiple keys and have multiple children, then the branching factor of the tree
increases significantly, leading to a much smaller height for the same number of
keys.

Example: Consider a balanced m-way tree (each internal node has between m/2
and m children). The height of such a tree with N keys is approximately logm/2(N). If
m is large (e.g., the number of keys that can fit into a disk block), the height becomes
much smaller than that of a binary search tree.
6. B-Trees
A B-tree is a self-balancing multi-way search tree. It is specifically designed for disk-
oriented data structures. A B-tree of order m (where m>1) has the following
properties:
1. Root Property: The root has at least 2 children unless it is a leaf node.
2. Node Size Property: Every node (except the root) has between ⌈m/2⌉ and m
children.
3. Number of Keys: Every internal node with c children contains c−1 keys.
4. Key Ordering Property: Within each node, the keys are stored in sorted
order.
5. Non-Decreasing Keys: The keys in the subtree rooted at the i-th child of a
node are all less than the (i+1)-th key of that node and greater than the i-th
key.
6. Leaf Property: All leaf nodes are at the same level.
7. Leaf Structure: Leaf nodes do not have children and contain between
⌈m/2⌉−1 and m−1 keys. They might also contain pointers to the actual data
records or pointers to child nodes if the B-tree is used as an index.
Order of the B-Tree (m): The order m determines the maximum number of children
a node can have. The choice of m is typically based on the block size of the disk. We
want each node to fit within one disk block to minimize the number of disk reads per
tree level.
Structure of a B-Tree Node:
A typical internal node in a B-tree of order m looks like this:
P1 K1 P2 K2 P3 ... Km-1 Pm
Where:
• Ki are the keys, sorted in non-decreasing order (K1<K2<...<Km−1).
• Pi are pointers to the children of the node.
• The subtree pointed to by P1 contains keys less than K1.
• The subtree pointed to by Pi (for 1<i<m) contains keys greater than or equal
to Ki−1 and less than Ki.
• The subtree pointed to by Pm contains keys greater than or equal to Km−1.
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
A leaf node looks like this:

K1 K2 K3 ... Kl
Where l is the number of keys in the leaf node (⌈m/2⌉−1≤l≤m−1). These keys might
be associated with pointers to the actual data records.
7. Working Up from the Bottom: Insertion in B-Trees
B-trees are often visualized as growing upwards from the leaves. When a new key is
inserted, it is initially placed in a leaf node. If the leaf node becomes full (contains
m−1 keys), it is split into two nodes. The middle key is promoted to the parent node,
along with pointers to the two new leaf nodes. This process can propagate up the
tree. If the root node also becomes full and splits, the height of the tree increases by
one.
Insertion Algorithm (Conceptual):
1. Find the appropriate leaf node: Traverse the B-tree starting from the root to
find the leaf node where the new key should be inserted (based on the sorted
order of keys).
2. Insert the key: Insert the new key into the leaf node, maintaining the sorted
order.
3. Check for overflow: If the leaf node now contains m keys (one more than the
maximum), it has overflowed and needs to be split.
4. Split the overflowing node:
o The median key of the m keys is promoted to the parent node.
o The keys smaller than the median are kept in the left new leaf node.
o The keys larger than the median are kept in the right new leaf node.
o Pointers to the two new leaf nodes are inserted into the parent node at
the position of the promoted key.
5. Handle parent overflow: If the parent node also overflows due to the
insertion of the new key and pointers, the split process is repeated up the
tree.
6. Splitting the root: If the root node splits, a new root node is created with the
median key and pointers to the two new child nodes, increasing the height of
the tree by one.
8. Example for Creating a B-Tree
Let's create a B-tree of order m=3. This means each node (except the root) can have
between ⌈3/2⌉=2 and 3 children, and thus between 1 and 2 keys. Leaf nodes can
have between ⌈3/2⌉−1=1 and 3−1=2 keys.
Insert the following keys in order: 10, 20, 30, 5, 15, 25, 35, 40, 45.
1. Insert 10:
2. [10] (Leaf/Root)
3. Insert 20:
4. [10, 20] (Leaf/Root)
5. Insert 30: The leaf is now full (2 keys). Split it. The middle key is 20.
6. [20] (Root)
7. / \
8. [10] [30] (Leaves)
9. Insert 5: Insert into the left leaf.
10. [20]
11. / \
12. [5, 10] [30]
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
13. Insert 15: Insert into the left leaf.
14. [20]
15. / \
16. [5, 10, 15] [30]
The left leaf is full. Split it. The middle key is 10. Promote 10 to the parent.
[10, 20] (Root)
/ | \
[5] [15] [30] (Leaves)
17. Insert 25: Insert into the right leaf.
18. [10, 20]
19. / | \
20. [5] [15] [25, 30]
21. Insert 35: Insert into the right leaf.
22. [10, 20]
23. / | \
24. [5] [15] [25, 30, 35]
The right leaf is full. Split it. The middle key is 30. Promote 30 to the parent.
[10, 20, 30] (Root)
/ | | \
[5] [15] [25] [35] (Leaves)
25. Insert 40: Insert into the rightmost leaf.
26. [10, 20, 30]
27. / | | \
28. [5] [15] [25] [35, 40]
29. Insert 45: Insert into the rightmost leaf.
30. [10, 20, 30]
31. / | | \
32. [5] [15] [25] [35, 40, 45]
The rightmost leaf is full. Split it. The middle key is 40. Promote 40 to the
parent.
[10, 20, 30, 40] (Root)
/ | | | \
[5] [15] [25] [35] [45] (Leaves)
The root is now full (3 keys). Split it. The middle key is 20 (the ⌈4/2⌉=2nd key).
Promote 20.
[20] (New Root)
/ \
[10] [30, 40]
/ / \
[5, 15] [25] [35, 45]
Correction in the last split: When splitting [10, 20, 30, 40], with m=3, a node can
hold at most m−1=2 keys. The split results in two nodes and the middle key is
promoted. The middle key here is 20.
[20] (New Root)
/ \
[10] [30, 40]
/ / \
[5, 15] [25] [35, 45]
Let's re-do the final split one more time for clarity with m=3. When [10, 20, 30,
40] in the root needs to split, the middle element is 20.
[20] (New Root)
/ \
[10] [30, 40]
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
/ / \
[5, 15] [25] [35, 45]
Actually, for m=3, the maximum number of keys in a node is 2. When we have
[10, 20, 30] in a node and need to insert, say, 40, it becomes [10, 20, 30, 40]
(temporarily). The split would promote the middle key (20), leaving [10] on the
left and [30, 40] on the right.
Let's correct the step where [10, 20, 30] root needed to accommodate 40.
[20] (New Root)
/ \
[10] [30]
/ / \
[5, 15] [25] [35, 40, 45]
Now the rightmost leaf [35, 40, 45] needs to split. The middle key is 40.
[20]
/ \
[10] [30, 40]
/ / \
[5, 15] [25] [35] [45]

Course Name: DATA STRUCTURE USING C, C++


Course Code: 0327002
Faculty Name- Davesh Bhardwaj
UNIT-VI: Sorting and Searching Techniques
1. Sorting Techniques
Sorting is the process of arranging a collection of items (e.g., numbers, strings,
records) in a specific order (e.g., ascending or descending).

1.1 Insertion Sort


Insertion sort is a simple sorting algorithm that builds the final sorted array one item
at a time. It is much less efficient on large lists than more advanced algorithms1 such
as quicksort, heapsort, or merge sort. However, insertion sort provides several
advantages:
• Simple2 to implement.
• Efficient for (quite) small data sets.
• Adaptive, i.e., efficient for data sets that are already substantially sorted: the
time complexity is O(n+d), where d is the number of inversions.3
• Stable; i.e., does not change the relative order of elements with equal keys.
• In-place; i.e., only requires a constant amount O(1) of additional memory
space.
• Online; i.e., can sort a list as it receives it.4
Algorithm:
1. Iterate through the array from the second element (index 1) to the last
element.
2. For each element current, compare it with the elements before it.
3. Shift elements greater than current to one position ahead of their current
position.
4. Insert current into the correct position in the sorted subarray.
Diagram:
Let's sort the array [5, 1, 4, 2, 8]
Initial: [5, 1, 4, 2, 8]
Iteration 1 (current = 1):

Compare 1 with 5. 1 < 5, so shift 5 to the right.

[ , 5, 4, 2, 8]

Insert 1 at the beginning.

[1, 5, 4, 2, 8]

Iteration 2 (current = 4):

Compare 4 with 5. 4 < 5, shift 5. [1, , 5, 2, 8]

Compare 4 with 1. 4 > 1. Insert 4 after 1.

[1, 4, 5, 2, 8]

Iteration 3 (current = 2):

Course Name: DATA STRUCTURE USING C, C++


Course Code: 0327002
Faculty Name- Davesh Bhardwaj
Compare 2 with 5. 2 < 5, shift 5. [1, 4, , 5, 8]

Compare 2 with 4. 2 < 4, shift 4. [1, , 4, 5, 8]

Compare 2 with 1. 2 > 1. Insert 2 after 1.

[1, 2, 4, 5, 8]

Iteration 4 (current = 8):

Compare 8 with 5. 8 > 5. Array remains the same in this step.

[1, 2, 4, 5, 8]

Sorted Array: [1, 2, 4, 5, 8]


C Language Program:
C
#include <stdio.h>

void insertionSort(int arr[], int n) {


int i, key, j;
for (i = 1; i < n; i++) {
key = arr[i];
j = i - 1;

/* Move elements of arr[0..i-1], that are greater than key,


to one position ahead of their current position */
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j = j - 1;
}
arr[j + 1] = key;
}
}

// Function to print an array


void printArray(int arr[], int size) {
int i;
for (i = 0; i < size; i++)
printf("%d ", arr[i]);
printf("\n");
}

int main() {
int arr[] = {5, 1, 4, 2, 8};
int n = sizeof(arr) / sizeof(arr[0]);

printf("Unsorted array: ");


printArray(arr, n);

insertionSort(arr, n);

printf("Sorted array: ");


printArray(arr, n);
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
return 0;
}
Time Complexity:
• Best Case: O(n) (when the array is already sorted)
• Average Case: O(n2)
• Worst Case: O(n2) (when the array is sorted in reverse order)5 Space
Complexity: O(1) (in-place)
1.2 Selection Sort
Selection sort is a simple comparison sorting algorithm. It works by repeatedly
finding the minimum element from the unsorted part and putting it at the beginning.6
The algorithm maintains two subarrays in a given array:
1. The subarray which is already sorted.
2. Remaining subarray which is unsorted.
7
In each iteration of the selection sort, the minimum element from the unsorted
subarray is picked and moved to the sorted subarray.8
Algorithm:
1. Iterate through the array from the first element (index 0) to the second-to-last
element.
2. For each iteration i, find the minimum element in the unsorted part of the array
(from index i to the end).
3. Swap the minimum element found with the element at index i.
Diagram:
Let's sort the array [6, 4, 1, 5, 3]
Initial: [6, 4, 1, 5, 3]
Iteration 1 (i = 0):

Minimum element in [6, 4, 1, 5, 3] is 1 at index 2.

Swap arr[0] (6) with arr[2] (1).

[1, 4, 6, 5, 3]

Iteration 2 (i = 1):

Minimum element in [4, 6, 5, 3] is 3 at index 4.

Swap arr[1] (4) with arr[4] (3).

[1, 3, 6, 5, 4]

Iteration 3 (i = 2):

Minimum element in [6, 5, 4] is 4 at index 4.

Swap arr[2] (6) with arr[4] (4).

[1, 3, 4, 5, 6]

Course Name: DATA STRUCTURE USING C, C++


Course Code: 0327002
Faculty Name- Davesh Bhardwaj
Iteration 4 (i = 3):

Minimum element in [5, 6] is 5 at index 3.

Swap arr[3] (5) with arr[3] (5). (No change)

[1, 3, 4, 5, 6]

Sorted Array: [1, 3, 4, 5, 6]


C Language Program:
C
#include <stdio.h>

void swap(int *xp, int *yp) {


int temp = *xp;
*xp = *yp;
*yp = temp;
}

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


int i, j, min_idx;

// One by one move boundary of unsorted subarray


for (i = 0; i < n - 1; i++) {
// Find the minimum element in unsorted array
min_idx = i;
for (j = i + 1; j < n; j++)
if (arr[j] < arr[min_idx])
min_idx = j;

// Swap the found minimum element with the first element


swap(&arr[i], &arr[min_idx]);
}
}

// Function to print an array


void printArray(int arr[], int size) {
int i;
for (i = 0; i < size; i++)
printf("%d ", arr[i]);
printf("\n");
}

int main() {
int arr[] = {6, 4, 1, 5, 3};
int n = sizeof(arr) / sizeof(arr[0]);

printf("Unsorted array: ");


printArray(arr, n);

selectionSort(arr, n);

printf("Sorted array: ");


printArray(arr, n);

Course Name: DATA STRUCTURE USING C, C++


Course Code: 0327002
Faculty Name- Davesh Bhardwaj
return 0;
}
Time Complexity:
• Best Case: O(n2)
• Average Case: O(n2)
• Worst Case: O(n2) Space Complexity: O(1) (in-place)
1.3 Merge Sort
Merge sort is an efficient, general-purpose, comparison-based sorting algorithm.
Most implementations produce a stable sort, which9 means that the implementation
preserves the input order of equal elements.10 It is a divide and conquer algorithm.
Algorithm:
1. Divide: Divide the unsorted list into n sublists, each containing one element (a
list of one element is considered sorted).
2. Conquer: Repeatedly merge sublists to produce new sorted sublists until
there is only one sublist11 remaining. This will be the sorted12 list.
Diagram:
Let's sort the array [8, 3, 1, 7, 0, 10, 2]
Divide:

[8], [3], [1], [7], [0], [10], [2]

Merge (pairs):

[3, 8], [1, 7], [0, 10], [2]

Merge (pairs again):

[1, 3, 7, 8], [0, 2, 10]

Merge (final):

[0, 1, 2, 3, 7, 8, 10]

C Language Program:
C
#include <stdio.h>
#include <stdlib.h>

// Merges two subarrays of arr[].


// First subarray is arr[l..m]
// Second subarray is arr[m+1..r]
void merge(int arr[], int l, int m, int r) {
int i, j, k;
int n1 = m - l + 1;
int n2 = r - m;

/* create temp arrays */


int *L = (int *)malloc(n1 * sizeof(int));
int *R = (int *)malloc(n2 * sizeof(int));

/* Copy data to temp arrays L[] and R[] */


for (i = 0; i < n1; i++)
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
L[i] = arr[l + i];
for (j = 0; j < n2; j++)
R[j] = arr[m + 1 + j];

/* Merge the temp arrays back into arr[l..r]*/


i = 0; // Initial index of first subarray
j = 0; // Initial index of second subarray
k = l; // Initial index of merged subarray
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = R[j];
j++;
}
k++;
}

/* Copy the remaining elements of L[], if there are any */


while (i < n1) {
arr[k] = L[i];
i++;
k++;
}

/* Copy the remaining elements of R[], if there are any */


while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
free(L);
free(R);
}

/* l is for left index and r is right index of the


sub-array of arr to be sorted */
void mergeSort(int arr[], int l, int r) {
if (l < r) {
// Same as (l+r)/2, but avoids overflow for large l and r
int m = l + (r - l) / 2;

// Sort first and second halves


mergeSort(arr, l, m);
mergeSort(arr, m + 1, r);

merge(arr, l, m, r);
}
}

// Function to print an array


void printArray(int arr[], int size) {
int i;
for (i = 0; i < size; i++)
printf("%d ", arr[i]);
printf("\n");
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj
}

int main() {
int arr[] = {8, 3, 1, 7, 0, 10, 2};
int n = sizeof(arr) / sizeof(arr[0]);

printf("Unsorted array: ");


printArray(arr, n);

mergeSort(arr, 0, n - 1);

printf("Sorted array: ");


printArray(arr, n);

return 0;
}
Time Complexity:
• Best Case: O(nlogn)
• Average Case: O(nlogn)
• Worst Case: O(nlogn) Space Complexity: O(n) (due to the13 auxiliary space
used in the merge operation)
1.4 Heap Sort
Heap sort is a comparison-based sorting algorithm that uses a binary heap data
structure. It is similar to selection sort where we first14 find the maximum element and
place the maximum element at the end. We repeat the same process for the
remaining elements.15
Algorithm:
1. Build a max heap from the input data.
2. Heapify: While the size of the heap is greater than 1:
o Swap the root (maximum element) with the last element of the heap.
o Decrease the heap size by 1.
o Heapify the root of the remaining heap.
Diagram:
Let's sort the array [4, 10, 3, 5, 1]
1. Build Max Heap:

Initial: [4, 10, 3, 5, 1]

Heapify (bottom-up): [10, 5, 3, 4, 1]

2. Sorting:
o Swap root (10) with last (1): [1, 5, 3, 4, 10], heap size = 4.
o Heapify root: [5, 4, 3, 1, 10]
o Swap root (5) with last (1): [1, 4, 3, 5, 10], heap size = 3.
o Heapify root: [4, 1, 3, 5, 10]
o Swap root (4) with last (3): [3, 1, 4, 5, 10], heap size = 2.
o Heapify root: [3, 1, 4, 5, 10] (no change needed)
o Swap root (3) with last (1): [1, 3, 4, 5, 10], heap size = 1.
o Heap size is 1, sorting complete.

Course Name: DATA STRUCTURE USING C, C++


Course Code: 0327002
Faculty Name- Davesh Bhardwaj
Sorted Array: [1, 3, 4, 5, 10]
C Language Program:
C
#include <stdio.h>
#include <stdlib.h>

// Function to swap two numbers


void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}

// To heapify a subtree rooted with node i which is


// an index in arr[], n is size of heap
void heapify(int arr[], int n, int i) {
int largest = i; // Initialize largest as root
int left = 2 * i + 1; // left child
int right = 2 * i + 2; // right child

// If left child is larger than root


if (left < n && arr[left] > arr[largest])
largest = left;

// If right child is larger than largest so far


if (right < n && arr[right] > arr[largest])
largest = right;

// If largest is not root


if (largest != i) {
swap(&arr[i], &arr[largest]);

// Recursively heapify the affected sub-tree


heapify(arr, n, largest);
}
}

// Main function to do heap sort


void heapSort(int arr[], int n) {
// Build max heap
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);

// One by one extract an element from heap


for (int i = n - 1; i > 0; i--) {
// Move current root to end
swap(&arr[0], &arr[i]);

// call max heapify on the reduced heap


heapify(arr, i, 0);
}
}

// Function to print an array


void printArray(int arr[], int size) {
int i;

Course Name: DATA STRUCTURE USING C, C++


Course Code: 0327002
Faculty Name- Davesh Bhardwaj
for (i = 0; i < size; i++)
printf("%d ", arr[i]);
printf("\n");
}

int main() {
int arr[] = {4, 10, 3, 5, 1};
int n = sizeof(arr) / sizeof(arr[0]);

printf("Unsorted array: ");


printArray(arr, n);

heapSort(arr, n);

printf("Sorted array: ");


printArray(arr, n);

return 0;

Course Name: DATA STRUCTURE USING C, C++


Course Code: 0327002
Faculty Name- Davesh Bhardwaj
Course Name: DATA STRUCTURE USING C, C++
Course Code: 0327002
Faculty Name- Davesh Bhardwaj

You might also like