Data structures & algorithms3 min

Advanced Sorting (Merge/Quick)

To sort millions of records, $O(N^2)$ algorithms are completely unusable. We need advanced sorting algorithms that utilize the Divide & Conquer paradigm to achieve $O(N \log N)$ time complexity.


1. Merge Sort

Merge Sort repeatedly divides the array in half until each subarray contains only 1 element (which is technically sorted). Then, it continuously merges the sorted subarrays back together.

  • Time Complexity: $O(N \log N)$ (Always)
  • Space Complexity: $O(N)$ (Requires temporary arrays during the merge phase)

Implementation

typescript
function mergeSort(arr: number[]): number[] {
  // Base case
  if (arr.length <= 1) return arr;
  
  const mid = Math.floor(arr.length / 2);
  const left = mergeSort(arr.slice(0, mid)); // Divide left
  const right = mergeSort(arr.slice(mid));   // Divide right
  
  return merge(left, right); // Conquer
}

function merge(left: number[], right: number[]): number[] {
  const result: number[] = [];
  let i = 0; let j = 0;
  
  // Compare elements and insert the smallest into result
  while (i < left.length && j < right.length) {
    if (left[i] <= right[j]) {
      result.push(left[i]); i++;
    } else {
      result.push(right[j]); j++;
    }
  }
  
  // Push remaining elements
  return result.concat(left.slice(i)).concat(right.slice(j));
}

2. Quick Sort

Quick Sort selects a "Pivot" element and partitions the array such that all elements smaller than the pivot are moved to the left, and all elements larger are moved to the right. It then recursively applies this to the left and right subarrays.

  • Time Complexity: $O(N \log N)$ (Average), $O(N^2)$ (Worst case if bad pivot is chosen)
  • Space Complexity: $O(\log N)$ (In-place, but recursive call stack takes space)

[!TIP] Despite the $O(N^2)$ worst-case, Quick Sort is often heavily preferred in real-world systems because its $O(\log N)$ space complexity makes it much more cache-friendly than Merge Sort. Many language engines (like V8) use variations of Quick Sort.

Implementation

typescript
function quickSort(arr: number[], left = 0, right = arr.length - 1): number[] {
  if (left < right) {
    const pivotIndex = partition(arr, left, right);
    quickSort(arr, left, pivotIndex - 1);  // Sort left of pivot
    quickSort(arr, pivotIndex + 1, right); // Sort right of pivot
  }
  return arr;
}

function partition(arr: number[], left: number, right: number): number {
  const pivot = arr[right]; // Choose last element as pivot
  let i = left - 1; // Tracks the boundary of elements smaller than pivot
  
  for (let j = left; j < right; j++) {
    if (arr[j] < pivot) {
      i++;
      swap(arr, i, j);
    }
  }
  
  // Swap pivot to its final correct position
  swap(arr, i + 1, right);
  return i + 1;
}

function swap(arr: number[], i: number, j: number) {
  const temp = arr[i];
  arr[i] = arr[j];
  arr[j] = temp;
}

3. Heap Sort

Heap Sort utilizes the Min/Max Heap data structure. It builds a Max Heap from the array in $O(N)$ time. Then, it continually extracts the maximum element (the root) and places it at the end of the array, restoring the heap property each time in $O(\log N)$.

  • Time Complexity: $O(N \log N)$ (Always)
  • Space Complexity: $O(1)$ (In-place)

Unlike Quick Sort, Heap Sort guarantees $O(N \log N)$ in the worst case. Unlike Merge Sort, it requires no extra $O(N)$ array memory. However, in physical benchmarks, Quick Sort is usually faster due to better CPU caching.