Merge sort
Merge sort is a sorting algorithm that follows the divide-and-conquer approach.
It works by recursively dividing the input array into smaller subarrays and sorting
those subarrays then merging them back together to obtain the sorted array.
In simple terms, we can say that the process of merge sort is to divide the array
into two halves, sort each half, and then merge the sorted halves back together.
This process is repeated until the entire array is sorted.
How does Merge Sort work?
Merge sort is a popular sorting algorithm known for its efficiency and stability. It
follows the divide-and-conquer approach to sort a given array of elements.
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.
Let’s sort the array or list [38, 27, 43, 10] using Merge Sort
Divide:
[38, 27, 43, 10] is divided into [38, 27 ] and [43, 10] .
[38, 27] is divided into [38] and [27] .
[43, 10] is divided into [43] and [10] .
Conquer:
[38] is already sorted.
[27] is already sorted.
[43] is already sorted.
[10] is already sorted.
Merge:
Merge [38] and [27] to get [27, 38] .
Merge [43] and [10] to get [10,43] .
Merge [27, 38] and [10,43] to get the final sorted list [10, 27, 38, 43]
Therefore, the sorted list is [10, 27, 38, 43] .
// Java program for Merge Sort
import [Link].*;
class CUTM {
// Merges two subarrays of arr[].
// First subarray is arr[l..m]
// Second subarray is arr[m+1..r]
static void merge(int arr[], int l, int m, int r)
{
// Find sizes of two subarrays to be merged
int n1 = m - l + 1;
int n2 = r - m;
// Create temp arrays
int L[] = new int[n1];
int R[] = new int[n2];
// Copy data to temp arrays
for (int i = 0; i < n1; ++i)
L[i] = arr[l + i];
for (int j = 0; j < n2; ++j)
R[j] = arr[m + 1 + j];
// Merge the temp arrays
// Initial indices of first and second subarrays
int i = 0, j = 0;
// Initial index of merged subarray array
int k = l;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
}
else {
arr[k] = R[j];
j++;
}
k++;
}
// Copy remaining elements of L[] if any
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
// Copy remaining elements of R[] if any
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
}
// Main function that sorts arr[l..r] using
// merge()
static void sort(int arr[], int l, int r)
{
if (l < r) {
// Find the middle point
int m = l + (r - l) / 2;
// Sort first and second halves
sort(arr, l, m);
sort(arr, m + 1, r);
// Merge the sorted halves
merge(arr, l, m, r);
}
}
// A utility function to print array of size n
static void printArray(int arr[])
{
int n = [Link];
for (int i = 0; i < n; ++i)
[Link](arr[i] + " ");
[Link]();
}
// Driver code
public static void main(String args[])
{
int arr[] = { 12, 11, 13, 5, 6, 7 };
[Link]("Given array is");
printArray(arr);
sort(arr, 0, [Link] - 1);
[Link]("\nSorted array is");
printArray(arr);
}
}
Complexity Analysis of Merge Sort
Time Complexity:
o Best Case: O(n log n), When the array is already sorted
or nearly sorted.
o Average Case: O(n log n), When the array is randomly
ordered.
o Worst Case: O(n log n), When the array is sorted in
reverse order.
Auxiliary Space: O(n), Additional space is required for the temporary
array used during merging.
Applications of Merge Sort:
Sorting large datasets
External sorting (when the dataset is too large to fit in memory)
Inversion counting
Merge Sort and its variations are used in library methods of
programming languages.
o Its variation TimSort is used in Python, Java Android and
Swift. The main reason why it is preferred to sort non-
primitive types is stability which is not there in
QuickSort.
o [Link] in Java uses QuickSort
while [Link] uses MergeSort.
It is a preferred algorithm for sorting Linked lists.
It can be easily parallelized as we can independently sort subarrays and
then merge.
The merge function of merge sort to efficiently solve the problems
like union and intersection of two sorted arrays.
Advantages and Disadvantages of Merge Sort
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.
QuickSort
QuickSort is a sorting algorithm based on the Divide and Conquer that picks an
element as a pivot and partitions the given array around the picked pivot by
placing the pivot in its correct position in the sorted array.
How does QuickSort Algorithm work?
QuickSort 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.
Choice of Pivot
There are many different choices for picking pivots.
Always pick the first (or last) element as a pivot. The below implementation
picks the last element as pivot. The problem with this approach is it ends up in
the worst case when array is already sorted.
Pick a random element as a pivot. This is a preferred approach because it does
not have a pattern for which the worst case happens.
Pick the median element is pivot. This is an ideal approach in terms of time
complexity as we can find median in linear time and the partition function will
always divide the input array into two halves. But it takes more time on
average as median finding has high constants.
import [Link];
class CUTM {
// Partition function
static int partition(int[] arr, int low, int high) {
// Choose the pivot
int pivot = arr[high];
// Index of smaller element and indicates
// the right position of pivot found so far
int i = low - 1;
// Traverse arr[low..high] and move all smaller
// elements to the left side. Elements from low to
// i are smaller after every iteration
for (int j = low; j <= high - 1; j++) {
if (arr[j] < pivot) {
i++;
swap(arr, i, j);
}
// Move pivot after smaller elements and
// return its position
swap(arr, i + 1, high);
return i + 1;
// Swap function
static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
// The QuickSort function implementation
static void quickSort(int[] arr, int low, int high) {
if (low < high) {
// pi is the partition return index of pivot
int pi = partition(arr, low, high);
// Recursion calls for smaller elements
// and greater or equals elements
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
public static void main(String[] args) {
int[] arr = {10, 7, 8, 9, 1, 5};
int n = [Link];
quickSort(arr, 0, n - 1);
for (int val : arr) {
[Link](val + " ");
Complexity Analysis of Quick Sort
Time Complexity:
Best Case: (Ω(n log n)), Occurs when the pivot element divides
the array into two equal halves.
Average Case (θ(n log n)), On average, the pivot divides the
array into two parts, but not necessarily equal.
Worst Case: (O(n²)), Occurs when the smallest or largest element
is always chosen as the pivot (e.g., sorted arrays).
Auxiliary Space: O(n), due to recursive call stack
Please refer Time and Space Complexity Analysis of Quick Sort for
more details.
Advantages of Quick Sort
It is a divide-and-conquer algorithm that makes it easier to solve
problems.
It is efficient on large data sets.
It has a low overhead, as it only requires a small amount of
memory to function.
It is Cache Friendly as we work on the same array to sort and do
not copy data to any auxiliary array.
Fastest general purpose algorithm for large data when stability is
not required.
It is tail recursive and hence all the tail call optimization can be
done.
Disadvantages of Quick Sort
It has a worst-case time complexity of O(n2), which occurs when
the pivot is chosen poorly.
It is not a good choice for small data sets.
It is not a stable sort, meaning that if two elements have the same
key, their relative order will not be preserved in the sorted output
in case of quick sort, because here we are swapping elements
according to the pivot’s position (without considering their
original positions).
Binary Search Algorithm
Binary Search Algorithm is a searching algorithm used in a sorted array
by repeatedly dividing the search interval in half. The idea of binary search is to
use the information that the array is sorted and reduce the time complexity to
O(log N).
What is Binary Search Algorithm?
Binary search is a search algorithm used to find the position of a target value
within a sorted array. It works by repeatedly dividing the search interval in half
until the target value is found or the interval is empty. The search interval is
halved by comparing the target element with the middle value of the search
space.
Conditions to apply Binary Search Algorithm in a Data
Structure
To apply Binary Search algorithm:
The data structure must be sorted.
Access to any element of the data structure should take constant time.
Binary Search Algorithm
Below is the step-by-step algorithm for Binary Search:
Divide the search space into two halves by finding the middle index “mid”.
Compare the middle element of the search space with the key.
If the key is found at middle element, the process is terminated.
If the key is not found at middle element, choose which half will be used as
the next search space.
o If the key is smaller than the middle element, then the left side is
used for next search.
o If the key is larger than the middle element, then the right side is
used for next search.
This process is continued until the key is found or the total search space is
exhausted.
Complexity Analysis of Binary Search Algorithm
Time Complexity:
o Best Case: O(1)
o Average Case: O(log N)
o Worst Case: O(log N)
Auxiliary Space: O(1), If the recursive call stack is considered then the
auxiliary space will be O(logN).
Applications of Binary Search Algorithm
Binary search can be used as a building block for more complex algorithms
used in machine learning, such as algorithms for training neural networks or
finding the optimal hyperparameters for a model.
It can be used for searching in computer graphics such as algorithms for ray
tracing or texture mapping.
It can be used for searching a database.
Advantages of Binary Search
Binary search is faster than linear search, especially for large arrays.
More efficient than other searching algorithms with a similar time complexity,
such as interpolation search or exponential search.
Binary search is well-suited for searching large datasets that are stored in
external memory, such as on a hard drive or in the cloud.
Disadvantages of Binary Search
The array should be sorted.
Binary search requires that the data structure being searched be stored in
contiguous memory locations.
Binary search requires that the elements of the array be comparable, meaning
that they must be able to be ordered.
// Java implementation of recursive Binary Search
class BinarySearch {
// Returns index of x if it is present in arr[low..
// high], else return -1
int binarySearch(int arr[], int low, int high, int x)
if (high >= low) {
int mid = low + (high - low) / 2;
// If the element is present at the
// middle itself
if (arr[mid] == x)
return mid;
// If element is smaller than mid, then
// it can only be present in left subarray
if (arr[mid] > x)
return binarySearch(arr, low, mid - 1, x);
// Else the element can only be present
// in right subarray
return binarySearch(arr, mid + 1, high, x);
}
// We reach here when element is not present
// in array
return -1;
// Driver code
public static void main(String args[])
BinarySearch ob = new BinarySearch();
int arr[] = { 2, 3, 4, 10, 40 };
int n = [Link];
int x = 10;
int result = [Link](arr, 0, n - 1, x);
if (result == -1)
[Link](
"Element is not present in array");
else
[Link](
"Element is present at index " + result);