Sample Sort

Generalizes quicksort to many pivots: it draws a set of splitter samples, distributes each range into buckets between consecutive splitters, writes the buckets straight back, and recurses. Each level visibly regroups the bars into ordered buckets.

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

How it works

Generalizes quicksort to many pivots: it draws a set of splitter samples, distributes each range into buckets between consecutive splitters, writes the buckets straight back, and recurses. Each level visibly regroups the bars into ordered buckets.

Implementation

function sampleSort(arr, stats) {
  const n = arr.length;
  if (n < 2) {
    if (n === 1) markSorted(0);
    return;
  }
  const INSERTION_THRESHOLD = 32;
  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 sort(lo, hi) {
    const len = hi - lo;
    if (len <= INSERTION_THRESHOLD) {
      insertionSort(lo, hi);
      return;
    }
    const mid = lo + (len >> 1);
    const bucketCount = Math.max(2, Math.floor(Math.sqrt(len)));
    const step = Math.floor(len / bucketCount);
    const splitters = [];
    for (let s = 1; s < bucketCount; s++) splitters.push(arr[lo + s * step]);
    splitters.sort((a, b) => a - b);
    const splitterCount = splitters.length;
    recordMetric('splitterSelections', splitterCount);
    const buckets = [];
    for (let b = 0; b <= splitterCount; b++) buckets.push([]);
    for (let i = lo; i < hi; i++) {
      compare(i, mid);
      const value = arr[i];
      let blo = 0;
      let bhi = splitterCount;
      while (blo < bhi) {
        const bm = blo + bhi >> 1;
        if (splitters[bm] <= value) blo = bm + 1; else bhi = bm;
      }
      buckets[blo].push(value);
    }
    let maxBucket = 0;
    for (let b = 0; b < buckets.length; b++) {
      if (buckets[b].length > maxBucket) maxBucket = buckets[b].length;
    }
    if (maxBucket === len) {
      insertionSort(lo, hi);
      return;
    }
    recordMetric('distributionPasses');
    let w = lo;
    for (let b = 0; b < buckets.length; b++) {
      const bucket = buckets[b];
      const start = w;
      for (let i = 0; i < bucket.length; i++) {
        arr[w] = bucket[i];
        write(w, bucket[i]);
        w++;
      }
      checkpoint(start, n);
      sort(start, w);
    }
  }
  sort(0, n);
  checkpoint(n, n);
}