Sorting and Searching Algorithms Guide
Sorting and Searching Algorithms Guide
1
Experiment no.: - 1[a]
INPUT CODE
#include <stdio.h>
int main() {
int arr[] = {10, 20, 80, 30, 60, 50, 110, 100, 130, 170};
int n = sizeof(arr) / sizeof(arr[0]);
int target = 110;printf("Original array: \n");
printf("\n");
if (result != -1) {
} else {
2
target = 99; // Test for an element not in the array
if (result != -1) {
} else {
return 0;
OUTPUT
COMPLEXITY
Time Complexity:
In both these situations, the algorithm has to traverse the entire list, performing 'n'
comparisons (where 'n' is the number of elements in the array). Hence, the time
complexity is linear, O(n).
2. Average Case: O(n): On average, if the target element is present in the list, it is
expected to be found somewhere in the middle. This means, on average, the
3
algorithm will perform approximately n/2 comparisons. Since Big O notation discards
constant factors, the average-case time complexity is also O(n).
3. Best Case: O(1): The best-case scenario occurs when the target element is the
very first element in the list. In this case, the algorithm finds the element with just one
comparison. Hence, the time complexity is constant, O(1).
4
Experiment no.: - 1[b]
BINARY SEARCH is an efficient algorithm for finding an item from a sorted list of
items. It works by repeatedly dividing in half the portion of the list that could contain
the item, until you've narrowed down the possible locations to just one. For Binary
Search to work, the data structure must be sorted.
INPUT CODE
#include <stdio.h>
if (arr[mid] == target) {
return mid;
low = mid + 1;
else {
high = mid - 1;
5
}
int main() {
int arr[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
printf("\n");
if (result != -1) {
} else {
if (result != -1) {
} else {
6
return 0;
OUTPUT
COMPLEXITY
Time Complexity:
Space Complexity:
7
8
Experiment no.: - 2
BUBBLE SORT is a simple sorting algorithm that repeatedly steps through the
list, compares adjacent elements, and swaps them if they are in the wrong order.
The pass through the list is repeated until no swaps are needed, which indicates that
the list is sorted. It gets its name because smaller elements "bubble" to the top (or
beginning) of the list with each pass.
INPUT CODE
#include <stdio.h>
bubble_sort(arr, n);
return 0;
}
9
OUTPUT
Original array: 64 34 25 12 22 11 90
Sorted array: 11 12 22 25 34 64 90
Space Complexity:
Bubble Sort is an in-place sorting algorithm because it only requires a constant
amount of extra space for temporary variables (like temp during a swap). Therefore,
its space complexity is O(1).
10
Experiment no.: - 3
INPUT CODE
#include <stdio.h>
int i, j, min_idx;
min_idx = i;
min_idx = j;
}
arr[min_idx] = arr[i];
arr[i] = temp;
int main() {
11
printf("%d ", arr[i]);
printf("\n");
selection_sort(arr, n);
printf("\n");
return 0;
OUTPUT
Original array: 12 11 13 5 6
Sorted array: 5 6 11 12 13
COMPLEXITY
The complexity of Selection Sort is primarily determined by its nested loop structure.
1. Outer Loop: This loop runs n-1 times, where n is the number of elements in
the array. Its purpose is to iterate through the unsorted portion of the array,
placing one element into its correctly sorted position in each pass.
2. Inner Loop: This loop is responsible for finding the minimum element in the
remaining unsorted subarray.
○ In the first pass of the outer loop (i = 0), the inner loop runs n-1
times (comparing arr[1] to arr[n-1]).
○ In the second pass (i = 1), the inner loop runs n-2 times.
○ ...and so on, until the last pass (i = n-2), where it runs 1 time.
12
Since Big O notation discards constant factors and lower-order terms, the time
complexity for comparisons is O(n^2).
Unlike other sorting algorithms, Selection Sort performs a minimal number of swaps.
In each pass of the outer loop, it performs at most one swap (to place the minimum
element found into its correct position).
Therefore, for an array of size n, Selection Sort performs exactly n-1 swaps.\
Since n-1 is proportional to n, the swap complexity is O(n).
Space Complexity:
13
Experiment no.: - 4
INSERTION SORT builds the final sorted array (or list) one item at a time. It
iterates through the input elements and removes one element at each iteration, finds
the place where it belongs within the already sorted part of the array, and inserts it
there. It's efficient for small data sets and works well with nearly sorted data.
INPUT CODE
void insertion_sort(int arr[], int n) {
int i, key, j;
key = arr[i];
j = i - 1;
arr[j + 1] = arr[j];
j = j - 1;
}
arr[j + 1] = key;
}
}
int main() {
printf("\n");
14
insertion_sort(arr, n);
printf("\n");
return 0;
}
OUTPUT
Original array: 12 11 13 5 6
Sorted array: 5 6 11 12 13
COMPLEXITY
The complexity of Insertion Sort is determined by the nested loops, which dictate the
number of comparisons and shifts required.
1. Outer Loop: This loop iterates from the second element (index 1) to the end
of the array (n-1 times). In each iteration, it considers an element arr[i] to
be inserted into its correct position within the already sorted subarray
arr[0...i-1].
2. Inner Loop (while loop): This loop compares the key (the element to be
inserted) with elements in the sorted subarray (arr[j]) and shifts elements
that are greater than the key one position ahead.
○ Worst Case: The inner loop runs a maximum of i times for each
iteration of the outer loop. This occurs when the array is sorted in
reverse order, as every element needs to be compared with all
preceding elements and shifted.
○ Best Case: The inner loop runs a minimum of 1 time for each iteration
of the outer loop. This occurs when the array is already sorted, as the
key is compared only with the last element of the sorted subarray and
no shifts are needed.
In the worst case (reverse-sorted array), the outer loop runs n-1 times. For each i
from 1 to n-1, the inner loop runs i times. The total number of comparisons and
shifts is approximately:
1 + 2 + 3 + ... + (n-1)
15
This is an arithmetic series, and its sum is given by the formula: n * (n-1) / 2.
For large n, this simplifies to approximately n^2 / 2.
Therefore, both the worst-case and average-case time complexities are O(n^2), as
we discard constant factors and lower-order terms.
Space Complexity:
Insertion Sort is an in-place sorting algorithm because it only requires a constant
amount of extra space for temporary variables (like key and j during the process).
Therefore, its space complexity is O(1).
16
Experiment no.: - 5
INPUT CODE
#include <stdio.h>
int main() {
int arr[] = {12, 34, 54, 2, 3};
int n = sizeof(arr) / sizeof(arr[0]);
printf("Original array: \n");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
shell_sort(arr, n);
printf("Sorted array: \n");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
OUTPUT
17
Original array: 12 34 54 2 3
Sorted array: 2 3 12 34 54
COMPLEXITY
The complexity of Shell Sort is challenging to analyze precisely and depends heavily
on the chosen gap sequence. Unlike other sorting algorithms, there isn't a single
formula for its time complexity that holds true for all gap sequences. However, we
can discuss its performance in terms of general cases.
Time Complexity:
1. Worst Case: The worst-case time complexity of Shell Sort depends entirely
on the gap sequence used.
○ Using Hibbard's increments (1, 3, 7, ..., 2^k - 1), the worst-case
complexity is O(n^(3/2)).
○ Using Knuth's increments (1, 4, 13, ..., (3^k - 1)/2), the worst-case
complexity is O(n^(3/2)).
○ Some more advanced gap sequences can achieve O(n log^2 n) or
even O(n^(4/3)).
○ The worst-case for a poorly chosen gap sequence can still be O(n^2),
similar to Insertion Sort.
2. Average Case: The average-case complexity is also dependent on the gap
sequence. Generally, it's considered to be much better than O(n^2) but worse
than O(n log n). For common gap sequences like Hibbard's or Knuth's, it's
often close to O(n^(5/4)) or O(n^(7/6)).
3. Best Case: The best-case scenario also depends on the gap sequence and
the initial state of the array.
○ If the array is already sorted and the gap sequence includes 1 (which it
usually does as the final pass), the last pass (insertion sort with gap 1)
will take O(n) time. However, the preceding passes would still
contribute to the overall time.
○ Generally, for a nearly sorted array with a good gap sequence, the best
case can approach O(n log n), but it's often still considered higher,
such as O(n^(4/3)) for some sequences.
18
Gap Sequence Worst Case Average Case
19
Experiment no.: - 6
INPUT CODE
#include <stdio.h>
swap(&arr[i], &arr[j]);
}
}
return (i + 1);
20
void quick_sort(int arr[], int low, int high) {
quick_sort(arr, pi + 1, high);
}
}
int main() {
printf("\n");
quick_sort(arr, 0, n - 1);
printf("\n");
return 0;
OUTPUT
Original array: 12 11 13 5 6
Sorted array: 5 6 11 12 13
COMPLEXITY
21
The complexity of Quick Sort is derived from its recursive, divide-and-conquer
approach, similar to Merge Sort. However, its performance varies significantly based
on the pivot selection strategy.
Where:
● If the pivot is always the smallest element, k would be 0, and the recurrence
relation becomes:
T(n) = T(0) + T(n-1) + O(n) which simplifies to T(n) = T(n-1) + O(n)
● If the pivot is always the largest element, k would be n-1, and the recurrence
relation becomes:
T(n) = T(n-1) + T(0) + O(n) which also simplifies to T(n) = T(n-1) + O(n)
● If the pivot consistently divides the array into two halves of roughly equal size
(e.g., k = n/2), the recurrence relation becomes:
T(n) = 2T(n/2) + O(n)
This recurrence relation is identical to that of Merge Sort. Using the Master Theorem
(Case 2, as derived for Merge Sort), where a=2, b=2, and f(n)=O(n), we compare
f(n) with n^(log_b a) = n^(log_2 2) = n^1. Since f(n) is proportional to
n^1, the solution is T(n) = O(n log n).
Space Complexity:
● Worst Case: O(n) - In the worst-case scenario (unbalanced partitions), the
recursive calls can form a linear chain, leading to a call stack depth
proportional to n.
● Average Case: O(log n) - In the average case (balanced partitions), the
recursive call stack depth is proportional to log n.
Conclusion
● Time Complexity:
○ Worst Case: O(n^2) - Occurs with poor pivot selection (e.g., already
sorted array with first/last element pivot).
○ Average Case: O(n log n) - Occurs with good pivot selection that
leads to balanced partitions.
○ Best Case: O(n log n) - Occurs when the pivot always divides the
array into two equal halves.
● Space Complexity:
○ Worst Case: O(n) (due to recursion stack)
○ Average Case: O(log n) (due to recursion stack)
23
Experiment no.: - 7
INPUT CODE
#include <stdio.h>
int i, j, k;
int n1 = m - l + 1;
int L[n1], R[n2]; // Copy data to temp arrays L[] and R[]
arr[k] = L[i];
i++;
} else {
24
arr[k] = R[j];
j++;
}
k++;
}
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
}
if (l < r) {
int m = l + (r - l) / 2;
merge_sort(arr, l, m);
merge_sort(arr, m + 1, r);
merge(arr, l, m, r);
}
}
int main() {
printf("\n");
merge_sort(arr, 0, n - 1);
printf("Sorted array: \n");
25
return 0;
OUTPUT
Original array: 12 11 13 5 6
Sorted array: 5 6 11 12 13
COMPLEXITY
The complexity of Merge Sort is derived from its recursive, divide-and-conquer
nature. Let's analyze it by breaking down its two main phases: Divide and Conquer
(Merge).
Where:
Step-by-Step Derivation
Let's break down the O(n) merge operation and then solve the recurrence relation.
● It iterates through both subarrays, comparing elements and placing them into
the temporary array or back into the original array.
● In the worst case, each element from both subarrays will be looked at and
moved exactly once.
● If we have two subarrays of size n/2 each, the total number of comparisons
and movements will be proportional to n/2 + n/2 = n.
● Therefore, the time complexity of the merge operation is O(n).
Conclusion
● Time Complexity:
○ Worst Case: O(n log n) - Merge Sort always performs n log n
comparisons in the worst case because it always divides the array into
two halves and then merges them, regardless of the initial order.
○ Average Case: O(n log n) - Similar to the worst case, the division and
merging steps maintain this complexity.
○ Best Case: O(n log n) - Even if the array is already sorted, Merge Sort
will still perform the divide and merge operations, leading to O(n log
n) comparisons.
● Space Complexity: O(n) - Merge Sort requires auxiliary space for the
merging process. In the typical implementation, temporary arrays are created
to hold the sub-arrays during the merge operation. In the worst case, these
temporary arrays can take up O(n) space.
27
Experiment no.: - 8
Objective:
To write programs to find the Minimum Spanning Tree (MST) of a connected,
weighted, undirected graph using
1. Kruskal’s Algorithm
2. Prim’s Algorithm.
Definition:
A Minimum Spanning Tree (MST) of a connected, weighted, undirected graph
is a subset of its edges that:
1. Connects all the vertices together (so the graph remains connected),
2. Contains no cycles (it is a tree), and
3. Has the minimum possible total edge weight among all the spanning trees of
the graph.
2. Prim’s Algorithm – Starts with one vertex and grows the tree by adding the
smallest edge connecting the tree to a new vertex.
28
Kruskal’s Algorithm for Minimum Spanning Tree
Examples:
Construct a minimum spanning tree using kruskal’s algorithm for the graph given
below –
[Link] the first step, sort all the edges in the given graph in an ascending
order and store the values in an array.
29
[Link], construct a forest of the given graph on a single plane.
[Link] the list of sorted edge costs, select the least cost edge and add it onto the
forest in output graph.
o B→D=5
o Minimum cost = 5
o Visited array, v = {B, D}
[Link], the next least cost edge is B → A = 6; so we add it onto the output
graph.
o Minimum cost = 5 + 6 = 11
o Visited array, v = {B, D, A}
30
[Link] next least cost edge is C → F = 9; add it onto the output graph.
o Minimum Cost = 5 + 6 + 9 = 20
o Visited array, v = {B, D, A, C, F}
[Link] next edge from the least cost array is B → C = 11, hence we add it in the
output graph.
o Minimum cost = 5 + 6 + 9 + 10 + 11 = 41
o Visited array, v = {B, D, A, C, F, E}
31
8. The last edge from the least cost array to be added in the output graph is F → G =
12.
o Minimum cost = 5 + 6 + 9 + 10 + 11 + 12 = 53
o Visited array, v = {B, D, A, C, F, E, G}
The obtained result is the minimum spanning tree of the given graph with cost
= 53.
Time Complexity
Sorting edges: 𝑂(𝐸log 𝐸)
Union-Find operations: 𝑂(𝐸log 𝑉)
Overall: 𝑂(𝐸log 𝑉)
32
Applications
Designing least-cost networks (telecommunication, roads, electrical wiring)
Clustering algorithms in machine learning
Network optimization problems
Example:
Find the minimum spanning tree using prims method (greedy approach) for
the graph given below with S as the arbitrary root.
Step 1:
Create a visited array to store all the visited vertices into it.
o V={}
The arbitrary root is mentioned to be S, so among all the edges that are
connected to S we need to find the least cost edge.
33
o S→B=8
o V = {S, B}
Step 2:
Since B is the last visited, check for the least cost edge that is connected to the
vertex B.
o B→A=9
o B → C = 16
o B → E = 14
Hence, B → A is the edge added to the spanning tree.
o V = {S, B, A}
Step 3:
Since A is the last visited, check for the least cost edge that is connected to the
vertex A.
o A → C = 22
o A→B=9
o A → E = 11
But A → B is already in the spanning tree, check for the next least cost edge.
Hence, A → E is added to the spanning tree.
34
o V = {S, B, A, E}
Step 4:
Since E is the last visited, check for the least cost edge that is connected to the
vertex E.
o E → C = 18
o E→D=3
Therefore, E → D is added to the spanning tree.
o V = {S, B, A, E, D}
Step 5:
Since D is the last visited, check for the least cost edge that is connected to
the vertex D.
o D → C = 15
o E→D=3
35
Therefore, D → C is added to the spanning tree.
o V = {S, B, A, E, D, C}
Conclusion
The MST connects all vertices with minimum total edge weight.
Kruskal’s and Prim’s algorithms are both efficient and widely used in
network design, road mapping, and circuit layout Problems.
36