Stable Quicksort

Quicksort that preserves the order of equal elements by partitioning each range into less / equal / greater buffers and writing them straight back, so every level visibly regroups the bars in place. The equal block lands in its final position immediately.

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

How it works

Quicksort that preserves the order of equal elements by partitioning each range into less / equal / greater buffers and writing them straight back, so every level visibly regroups the bars in place. The equal block lands in its final position immediately.

Implementation

function stableQuickSort(arr, stats) {
  const n = arr.length;
  if (n < 2) {
    if (n === 1) markSorted(0);
    return;
  }
  const INSERTION_THRESHOLD = 24;
  const metrics = stats.educational.metrics;
  const recordMetric = (name, delta = 1) => {
    metrics[name] = (metrics[name] || 0) + delta;
  };
  metrics.insertionThreshold = INSERTION_THRESHOLD;
  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];
        write(j, arr[j]);
        j--;
      }
      arr[j] = value;
      write(j, value);
    }
    for (let i = lo; i < hi; i++) markSorted(i);
  }
  function medianOfThree(lo, mid, hi) {
    compare(lo, mid);
    compare(mid, hi);
    compare(lo, hi);
    recordMetric('pivotSelections');
    let a = arr[lo];
    let b = arr[mid];
    let c = arr[hi];
    if (a > b) {
      const t = a;
      a = b;
      b = t;
    }
    if (b > c) {
      const t = b;
      b = c;
      c = t;
    }
    if (a > b) {
      const t = a;
      a = b;
      b = t;
    }
    return b;
  }
  function sort(lo, hi) {
    const len = hi - lo;
    if (len <= 1) {
      if (len === 1) markSorted(lo);
      return;
    }
    if (len <= INSERTION_THRESHOLD) {
      insertionSort(lo, hi);
      return;
    }
    const mid = lo + (len >> 1);
    const pivot = medianOfThree(lo, mid, hi - 1);
    recordMetric('stablePartitions');
    const less = [];
    const equal = [];
    const greater = [];
    for (let i = lo; i < hi; i++) {
      compare(i, mid);
      const value = arr[i];
      if (value < pivot) less.push(value); else if (value > pivot) greater.push(value); else equal.push(value);
    }
    recordMetric('bufferedItems', len);
    let w = lo;
    for (let i = 0; i < less.length; i++) {
      arr[w] = less[i];
      write(w, less[i]);
      w++;
    }
    const eqStart = w;
    for (let i = 0; i < equal.length; i++) {
      arr[w] = equal[i];
      write(w, equal[i]);
      markSorted(w);
      w++;
    }
    const gtStart = w;
    for (let i = 0; i < greater.length; i++) {
      arr[w] = greater[i];
      write(w, greater[i]);
      w++;
    }
    checkpoint(eqStart, n);
    sort(lo, eqStart);
    sort(gtStart, hi);
  }
  sort(0, n);
  checkpoint(n, n);
}