American Flag Sort

In-place MSD radix distribution using cycle-leader style bucket placement.

Best O(n) Avg O(n) Worst O(nk) Space O(k) Stable No In-place Yes Distribution

How it works

In-place MSD radix distribution using cycle-leader style bucket placement.

Implementation

function americanFlagSort(arr, stats) {
  if (arr.length < 2) return;
  const metrics = stats.educational.metrics;
  const recordMetric = (name, delta = 1) => {
    metrics[name] = (metrics[name] || 0) + delta;
  };
  let min = arr[0];
  let max = arr[0];
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] < min) min = arr[i];
    if (arr[i] > max) max = arr[i];
  }
  const RADIX = 256;
  const offset = min < 0 ? -min : 0;
  const maxKey = max + offset;
  metrics.radixBase = RADIX;
  metrics.signedOffset = offset;
  let divisor = 1;
  while (Math.floor(maxKey / divisor) >= RADIX) divisor *= RADIX;
  function digitFor(value, currentDivisor) {
    recordMetric('digitExtractions');
    return Math.floor((value + offset) / currentDivisor) % RADIX;
  }
  function sortRange(lo, hi, currentDivisor) {
    if (hi - lo <= 1 || currentDivisor === 0) {
      if (hi - lo === 1) markSorted(lo);
      return;
    }
    recordMetric('distributionPasses');
    const count = new Array(RADIX).fill(0);
    for (let i = lo; i < hi; i++) {
      const d = digitFor(arr[i], currentDivisor);
      count[d]++;
      recordMetric('countArrayWrites');
    }
    const start = new Array(RADIX).fill(0);
    const end = new Array(RADIX).fill(0);
    const next = new Array(RADIX).fill(0);
    let sum = lo;
    for (let i = 0; i < RADIX; i++) {
      start[i] = sum;
      next[i] = sum;
      sum += count[i];
      end[i] = sum;
    }
    for (let b = 0; b < RADIX; b++) {
      let i = next[b];
      while (i < end[b]) {
        const d = digitFor(arr[i], currentDivisor);
        recordMetric('bucketScans');
        if (d === b) {
          i++;
        } else {
          const to = next[d]++;
          swap(i, to);
          const t = arr[i];
          arr[i] = arr[to];
          arr[to] = t;
          recordMetric('cycleMoves');
        }
      }
    }
    if (currentDivisor === 1) {
      for (let b = 0; b < RADIX; b++) {
        if (count[b] > 0) {
          for (let i = start[b]; i < end[b]; i++) markSorted(i);
        }
      }
    } else {
      const nextDivisor = Math.floor(currentDivisor / RADIX);
      for (let b = 0; b < RADIX; b++) {
        if (count[b] > 1) {
          recordMetric('recursiveBuckets');
          sortRange(start[b], end[b], nextDivisor);
        } else if (count[b] === 1) {
          markSorted(start[b]);
        }
      }
    }
    checkpoint(Math.min(hi, arr.length), arr.length);
  }
  sortRange(0, arr.length, divisor);
}