3-Way Quicksort

Quicksort partitioning into less/equal/greater regions, especially strong on duplicate-heavy input.

Best O(n) Avg O(n log n) Worst O(n²) Space O(log n) Stable No In-place Yes Comparison-based

How it works

Quicksort partitioning into less/equal/greater regions, especially strong on duplicate-heavy input.

Implementation

function quick3WaySort(arr, stats) {
  const n = arr.length;
  if (n < 2) {
    if (n === 1) markSorted(0);
    return;
  }
  const INSERTION_THRESHOLD = 16;
  const metrics = stats.educational.metrics;
  const recordMetric = (name, delta = 1) => {
    metrics[name] = (metrics[name] || 0) + delta;
  };
  metrics.insertionThreshold = INSERTION_THRESHOLD;
  function swap(i, j) {
    if (i === j) return;
    swap(i, j);
    const value = arr[i];
    arr[i] = arr[j];
    arr[j] = value;
    recordMetric('pairSwaps');
  }
  function compareAndSwap(i, j) {
    compare(i, j);
    if (arr[i] > arr[j]) swap(i, j);
  }
  function insertionSort(lo, hi) {
    recordMetric('insertionSortCalls');
    for (let i = lo + 1; i <= hi; i++) {
      const value = arr[i];
      let j = i;
      while (j > lo) {
        compare(j - 1, i);
        if (arr[j - 1] <= value) break;
        arr[j] = arr[j - 1];
        recordMetric('writeOperations');
        write(j, arr[j]);
        j--;
      }
      arr[j] = value;
      recordMetric('writeOperations');
      write(j, value);
    }
  }
  function pivotToFront(lo, hi) {
    const mid = lo + (hi - lo >> 1);
    compareAndSwap(lo, mid);
    compareAndSwap(lo, hi);
    compareAndSwap(mid, hi);
    swap(lo, mid);
    recordMetric('pivotSelections');
  }
  function sort(lo, hi) {
    while (lo < hi) {
      if (hi - lo + 1 <= INSERTION_THRESHOLD) {
        insertionSort(lo, hi);
        return;
      }
      pivotToFront(lo, hi);
      const pivot = arr[lo];
      let i = lo;
      let j = hi + 1;
      let p = lo;
      let q = hi + 1;
      while (true) {
        do {
          i++;
          if (i > hi) break;
          compare(i, lo);
        } while (i <= hi && arr[i] < pivot);
        do {
          j--;
          compare(j, lo);
        } while (j >= lo && arr[j] > pivot);
        if (i === j && i <= hi && arr[i] === pivot) {
          p++;
          swap(p, i);
          recordMetric('equalFrontMoves');
        }
        if (i >= j) break;
        swap(i, j);
        if (arr[i] === pivot) {
          p++;
          swap(p, i);
          recordMetric('equalFrontMoves');
        }
        if (arr[j] === pivot) {
          q--;
          swap(q, j);
          recordMetric('equalBackMoves');
        }
      }
      i = j + 1;
      for (let k = lo; k <= p; k++) {
        swap(k, j);
        j--;
      }
      for (let k = hi; k >= q; k--) {
        swap(k, i);
        i++;
      }
      recordMetric('partitionCalls');
      checkpoint(Math.min(metrics.partitionCalls, n), n);
      if (j - lo < hi - i) {
        if (lo < j) sort(lo, j);
        lo = i;
      } else {
        if (i < hi) sort(i, hi);
        hi = j;
      }
    }
  }
  sort(0, n - 1);
  for (let i = 0; i < n; i++) markSorted(i);
  checkpoint(n, n);
}