Unit II(Searching and Sorting)
Unit II(Searching and Sorting)
In computer programming, searching and sorting are essential techniques for organizing and
manipulating data. Searching involves finding a specific element within a data set, while
sorting involves arranging the elements of a data set in a specific order. Both techniques play a
vital role in the performance and efficiency of computer programs.
There are various searching and sorting algorithms available in programming, each with its
own strengths and weaknesses depending on the size, type, and complexity of the data. In this
article, we will provide a comprehensive guide to the most commonly used searching and
sorting techniques in programming. We will discuss the basics of each technique, their time
and space complexities, and when to use them to achieve optimal performance. By the end of
this article, you will have a better understanding of how to choose the appropriate searching
Sorting algorithms can be broadly classified into two categories: comparison-based and non-
comparison-based algorithms.
Comparison-based algorithms compare elements in the input to determine their order, and thus
their time complexity is lower bounded by the number of comparisons needed to correctly
order the input. All the sorting algorithms mentioned in this blog post, including Bubble Sort,
Selection Sort, Insertion Sort, Merge Sort, Quick Sort, and Heap Sort, are comparison-based
algorithms.
determine their order. These algorithms are often more specialized and are used in specific
situations where the input has certain properties. For example, counting sort is a non-
comparison-based algorithm that can be used to sort inputs consisting of integers with a small
range.
Another way to classify sorting algorithms is based on their stability. A sorting algorithm is
said to be stable if it preserves the relative order of equal elements in the input. Merge Sort and
Insertion Sort are stable algorithms, while Quick Sort and Heap Sort are unstable algorithms.
In addition to these broad classifications, there are many variations and extensions of sorting
algorithms, such as shell sort, radix sort, and bucket sort, each designed to optimize for
1. Bubble sort
Bubble sort is a simple sorting algorithm that works by repeatedly
swapping adjacent elements if they are in the wrong order. The
algorithm starts at the beginning of the list and compares each pair
of adjacent elements. If they are not in the correct order, they are
swapped, and the algorithm continues until no more swaps are
needed. Bubble sort has a time complexity of O(n²) and is not
suitable for large datasets.
We sort the array using multiple passes. After the first pass, the maximum element goes
to end (its correct position). Same way, after second pass, the second largest element
goes to second last position and so on.
In every pass, we process only those elements that have already not moved to correct
position. After k passes, the largest k elements must have been moved to the last k
positions.
In a pass, we consider remaining elements and compare all adjacent and swap if larger
element is before a smaller element. If we keep doing this, we get the largest (among
the remaining elements) at its correct position.
Output
Sorted array:
11 12 22 25 34 64 90
2. Selection Sort
Selection sort is another simple sorting algorithm that works by repeatedly finding the smallest
element from the unsorted part of the list and swapping it with the first element. The algorithm
then moves on to the second element and finds the smallest element from the remaining
unsorted elements and swaps it with the second element, and so on until the entire list is
sorted. Selection sort also has a time complexity of O(n²) and is not suitable for large datasets.
1. First we find the smallest element and swap it with the first element. This way we get
the smallest element at its correct position.
2. Then we find the smallest among remaining elements (or second smallest) and swap it
with the second element.
3. We keep doing this until we get all elements moved to correct position.
int main() {
int arr[] = {64, 25, 12, 22, 11};
int n = sizeof(arr) / sizeof(arr[0]);
selectionSort(arr, n);
return 0;
}
Output
Original vector: 64 25 12 22 11
Sorted vector: 11 12 22 25 64
Complexity Analysis of Selection Sort
Time Complexity: O(n2) ,as there are two nested loops:
One loop to select an element of Array one by one = O(n)
Another loop to compare that element with every other Array element = O(n)
Therefore overall complexity = O(n) * O(n) = O(n*n) = O(n2)
Auxiliary Space: O(1) as the only extra memory used is for temporary variables.
3. Insertion Sort
Insertion sort is a simple sorting algorithm that works by building the final sorted list one
element at a time. The algorithm starts with the second element of the list and compares it with
the first element. If the second element is smaller than the first element, it is swapped with the
first element. The algorithm then moves on to the third element and compares it with the
second and first elements, swapping it with the appropriate element if needed. The algorithm
continues in this manner until the entire list is sorted. Insertion sort has a time complexity of
O(n²) but is more efficient than bubble sort and selection sort for small datasets.
We start with the second element of the array as the first element is assumed to be sorted.
Compare the second element with the first element if the second element is smaller then
swap them.
Move to the third element, compare it with the first two elements, and put it in its correct
position
Repeat until the entire array is sorted.
// Driver method
int main()
{
int arr[] = { 12, 11, 13, 5, 6 };
int n = sizeof(arr) / sizeof(arr[0]);
insertionSort(arr, n);
printArray(arr, n);
return 0;
}
Output
5 6 11 12 13
Complexity Analysis of Insertion Sort
Time Complexity
Best case: O(n), If the list is already sorted, where n is the number of elements in
the list.
Average case: O(n2), If the list is randomly ordered
Worst case: O(n2), If the list is in reverse order
Space Complexity
Auxiliary Space: O(1), Insertion sort requires O(1) additional space, making it a
space-efficient sorting algorithm.
Disadvantages
Inefficient for large lists.
Not as efficient as other sorting algorithms (e.g., merge sort, quick sort) for most cases.
4. Merge Sort
Merge sort is a divide-and-conquer algorithm that works by dividing the list into smaller sub-
lists, sorting them, and then merging them back together. The algorithm recursively divides
the list in half until each sub-list contains only one element. It then merges the sub-lists back
together, comparing the elements in each sub-list and merging them in the correct order.
Merge sort has a time complexity of O(n log n) and is more efficient than the previous
algorithms for large datasets.
How does Merge Sort work?
Here’s a step-by-step explanation of how merge sort works:
1. Divide: Divide the list or array recursively into two halves until it can no more be
divided.
2. Conquer: Each subarray is sorted individually using the merge sort algorithm.
3. Merge: The sorted subarrays are merged back together in sorted order. The process
continues until all elements from both subarrays have been merged.
merge(arr, l, m, r);
}
}
// Function to print an array
void printArray(int A[], int size)
{
int i;
for (i = 0; i < size; i++)
printf("%d ", A[i]);
printf("\n");
}
// Driver code
int main()
{
int arr[] = { 12, 11, 13, 5, 6, 7 };
int arr_size = sizeof(arr) / sizeof(arr[0]);
Output
Given array is
12 11 13 5 6 7
Sorted array is
5 6 7 11 12 13
It works on the principle of divide and conquer, breaking down the problem into smaller
sub-problems.
There are mainly three steps in the algorithm:
1. Choose a Pivot: Select an element from the array as the pivot. The choice of pivot can
vary (e.g., first element, last element, random element, or median).
2. Partition the Array: Rearrange the array around the pivot. After partitioning, all
elements smaller than the pivot will be on its left, and all elements greater than the pivot
will be on its right. The pivot is then in its correct position, and we obtain the index of
the pivot.
3. Recursively Call: Recursively apply the same process to the two partitioned sub-arrays
(left and right of the pivot).
4. Base Case: The recursion stops when there is only one element left in the sub-array, as
a single element is already sorted.
Advantages
Stability : Merge sort is a stable sorting algorithm, which means it maintains the
relative order of equal elements in the input array.
Guaranteed worst-case performance: Merge sort has a worst-case time complexity
of O(N logN) , which means it performs well even on large datasets.
Simple to implement: The divide-and-conquer approach is straightforward.
Naturally Parallel : We independently merge subarrays that makes it suitable for
parallel processing.
Disadvantages
Space complexity: Merge sort requires additional memory to store the merged sub-
arrays during the sorting process.
Not in-place: Merge sort is not an in-place sorting algorithm, which means it requires
additional memory to store the sorted data. This can be a disadvantage in applications
where memory usage is a concern.
Merge Sort is Slower than QuickSort in general as QuickSort is more cache friendly
because it works in-place.
5. Quick Sort
Quick sort is another divide-and-conquer algorithm that works by selecting a pivot element
from the list and partitioning the other elements into two sub-lists, according to whether they
are less than or greater than the pivot element. The algorithm then recursively sorts the sub-
lists, using the same pivot element selection and partitioning process until the entire list is
sorted. Quick sort has a time complexity of O(n log n) and is more efficient than merge sort
for small to medium-sized datasets.
// C Program to illustrate Quick Sort
#include <stdio.h>
// Partition function
int partition(int arr[], int low, int high) {
quickSort(arr, 0, n - 1);
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
Output
Sorted Array
1 5 7 8 9 10
Heap sort is a comparison-based sorting algorithm that works by first building a binary heap
from the list of elements and then repeatedly extracting the maximum element from the heap
and adding it to the sorted list. The heap is restructured after each extraction to maintain the
heap property. Heap sort has a time complexity of O(n log n) and is more efficient than the
#include <stdio.h>
// Driver's code
int main() {
int arr[] = {9, 4, 3, 8, 10, 2, 5};
int n = sizeof(arr) / sizeof(arr[0]);
heapSort(arr, n);
Output
Sorted array is
2 3 4 5 8 9 10
comparison
From this table, we can see that Merge Sort and Heap Sort are the most efficient algorithms
for large datasets, with a time complexity of O(nlogn) in the worst case. Quick Sort is also
efficient for large datasets, but has a worst-case time complexity of O(n²). Bubble Sort,
Selection Sort, and Insertion Sort are less efficient for larger datasets, with a time complexity
Additionally, we can see that Merge Sort is stable, meaning that it preserves the relative order
of equal elements in the input, while Quick Sort and Heap Sort are unstable, meaning that they
Finally, we can see that Merge Sort requires additional memory to store the sub-arrays during
the recursion, while Heap Sort is the most space-efficient algorithm, using only O(1) space.
Searching Techniques
There are several types of searching algorithms in computer programming, each with its own
strengths and weaknesses depending on the size, type, and complexity of the data. The most
1. Linear search is a simple and basic searching algorithm used to find a target value in a
data set. It is also known as sequential search and involves traversing the data set from
start to end, comparing each element with the target value until a match is found or the
end of the data set is reached. Linear search is suitable for small and unordered data sets
The time complexity of linear search is O(n), where n is the size of the data set. In the worst-
case scenario, the target value is not found in the data set, and the algorithm has to traverse the
entire data set, making n comparisons. In the best-case scenario, the target value is found in
languages. Its simplicity and ease of implementation make it a useful algorithm for small and
simple data sets. However, for larger and ordered data sets, other searching algorithms such as
2. Binary search is a searching algorithm used to find a target value in an ordered data set. It
works by repeatedly dividing the search space in half until the target value is found or it is
determined that the target value does not exist in the data set.
The algorithm starts by comparing the target value with the middle element of the data set. If
the middle element is the target value, the search is successful. If the middle element is greater
than the target value, the search continues in the lower half of the data set. If the middle
element is smaller than the target value, the search continues in the upper half of the data set.
The process repeats until the target value is found or it is determined that the target value does
Binary search has a time complexity of O(log n), where n is the size of the data set. In the
worst-case scenario, the target value is not found in the data set, and the algorithm has to
divide the search space log n times, making log n comparisons. In the best-case scenario, the
target value is found in the middle element, and only one comparison is needed.
Binary search is an efficient algorithm for large and ordered data sets. However, it requires the
data set to be sorted in ascending or descending order beforehand. It can be implemented using