AlgoDocs

Merge Sort

A Classic Divide-and-Conquer Algorithm

Introduction

Merge Sort is a classic divide-and-conquer sorting algorithm that efficiently organizes data by splitting, sorting, and merging. Known for its stability and predictable performance, Merge Sort works as follows:

How Merge Sort Works?

Divide

The array is recursively divided into two equal (or nearly equal) halves until each subarray contains only a single element.

Sort

These smaller subarrays are sorted independently through recursion.

Merge

Finally, the sorted subarrays are merged together to form a single, fully sorted array.

Merge Sort Visualization

Implementation of Merge Sort

// mergesort.cpp
#include <iostream>
using namespace std;
void merge(int arr[], int l, int mid, int r)
{
    int s1 = (mid - l) + 1;
    int s2 = r - mid;
    int a1[s1];
    int a2[s2];
    for (int i = 0; i < s1; i++)
    {
        a1[i]=arr[l+i];
    }
    for (int i = 0; i < s2; i++)
    {
        a2[i]=arr[mid + 1 + i];
    }
    int i = 0;
    int j = 0;
    int k = l;
    while (i < s1 && j < s2)
    {
        if (a1[i] < a2[j])
        {
            arr[k++] = a1[i++];
        }
        else
        {
            arr[k++] = a2[j++];
        }
    }
    while (i < s1)
    {
        arr[k++] = a1[i++];
    }
    while (j < s2)
    {
        arr[k++] = a2[j++];
    }
}
void mergeSort(int arr[], int l, int r)
{
    if (l >= r)
        return;
    int mid = l + (r - l) / 2;
    mergeSort(arr, l, mid);
    mergeSort(arr, mid + 1, r);
    merge(arr, l, mid, r);
}
int main()
{
    int arr[]={7, 2, 1, 5, 3, 6, 7};
    int n=sizeof(arr)/sizeof(arr[0]);
    mergeSort(arr, 0, n - 1);
    for(int i=0;i<n;i++){
        cout<<arr[i]<<" ";
    }
}
#mergesort.py
def merge(arr,l,mid,r):
    s1=(mid-l)+1
    s2=r-mid
    a1=[0]*s1
    a2=[0]*s2
    for i in range(0,s1):
        a1[i]=arr[l+i]
    for i in range(0,s2):
        a2[i]=arr[mid+1+i]
    i=0
    j=0
    k=l
    while i<s1 and j<s2:
        if a1[i]<a2[j]:
            arr[k]=a1[i]
            k+=1
            i+=1
        else:
            arr[k]=a2[j]
            k+=1
            j+=1
    
    while i<s1:
        arr[k]=a1[i]
        k+=1
        i+=1
    while j<s2:
        arr[k]=a2[j]
        k+=1
        j+=1
            

def mergeSort(arr,l,r):
    if l>=r:
        return
    mid=l+(r-l)//2
    mergeSort(arr,l,mid)
    mergeSort(arr,mid+1,r)
    merge(arr,l,mid,r)

arr=[7,2,1,5,3,6,7]
mergeSort(arr,0,len(arr)-1)

print(arr)
// mergeSort.js
console.log("We're Working");

Time Complexity

  • Best Case: O(nlogn), When the array is already sorted or nearly sorted.
  • Average Case: O(nlogn), Consistent performance regardless of initial array ordering.
  • Worst Case: O(nlogn), Even with completely unordered data.

Space Complexity

  • O(n) additional space for storing temporary arrays during merging.

Key Features

Advantages and Disadvantages

Advantages

  • Stable Sort: Preserves relative order of equal elements.
  • Predictable Performance: O(n log n) in all cases.
  • External Storage: Effective for sorting data too large to fit in memory.

Disadvantages

  • Space Requirements: Requires O(n) additional space.
  • Overhead: Less efficient for small arrays due to function call overhead.
  • Cache Performance: Not as cache-friendly as in-place sorts like quicksort.

Practical Applications

  1. External Sorting: When data doesn't fit in memory.
  2. Linked List Sorting: Efficient for linked list implementations.
  3. Stable Sorting: When preserving order of equal elements is important.
  4. Inversion Count Problems: Useful in solving algorithms that need to count inversions.

How is this guide?