Smoothsort

A variation of heapsort.

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

How it works

A variation of heapsort.

Implementation

function smoothSort(arr, stats) {
  const n = arr.length;
  if (n < 2) {
    if (n === 1) {
      markSorted(0);
      checkpoint(100, 100);
    }
    return;
  }
  const leo = [1, 1];
  while (leo[leo.length - 1] < n) {
    leo.push(leo[leo.length - 1] + leo[leo.length - 2] + 1);
  }
  const metrics = stats.educational.metrics;
  const recordMetric = (name, delta = 1) => {
    metrics[name] = (metrics[name] || 0) + delta;
  };
  metrics.leonardoOrders = leo.length;
  function swapIndices(left, right) {
    if (left === right) return;
    const value = arr[left];
    arr[left] = arr[right];
    arr[right] = value;
    recordMetric('swapOperations');
    swap(left, right);
  }
  function markSorted(index, completed) {
    markSorted(index);
    checkpoint(40 + Math.floor(completed * 60 / n), 100);
  }
  function sift(root, order) {
    recordMetric('siftCalls');
    while (order >= 2) {
      const rightChild = root - 1;
      const leftChild = root - 1 - leo[order - 2];
      let largest = root;
      recordMetric('rootChildComparisons');
      compare(leftChild, largest);
      if (arr[leftChild] > arr[largest]) largest = leftChild;
      recordMetric('rootChildComparisons');
      compare(rightChild, largest);
      if (arr[rightChild] > arr[largest]) largest = rightChild;
      if (largest === root) break;
      swapIndices(root, largest);
      recordMetric('siftDescents');
      if (largest === leftChild) {
        root = leftChild;
        order -= 1;
      } else {
        root = rightChild;
        order -= 2;
      }
    }
  }
  const orders = [];
  function trinkle(pos, idx) {
    recordMetric('trinkleCalls');
    while (idx > 0) {
      const prevPos = pos - leo[orders[idx]];
      recordMetric('ancestorComparisons');
      compare(prevPos, pos);
      if (arr[prevPos] <= arr[pos]) break;
      if (orders[idx] >= 2) {
        const rightChild = pos - 1;
        const leftChild = pos - 1 - leo[orders[idx] - 2];
        recordMetric('childGuardComparisons');
        compare(prevPos, leftChild);
        if (arr[prevPos] <= arr[leftChild]) break;
        recordMetric('childGuardComparisons');
        compare(prevPos, rightChild);
        if (arr[prevPos] <= arr[rightChild]) break;
      }
      swapIndices(pos, prevPos);
      recordMetric('trinkleAscents');
      pos = prevPos;
      idx--;
    }
    sift(pos, orders[idx]);
  }
  for (let i = 0; i < n; i++) {
    const heapCount = orders.length;
    if (heapCount >= 2 && orders[heapCount - 2] === orders[heapCount - 1] + 1) {
      orders.pop();
      orders[orders.length - 1]++;
      recordMetric('heapMerges');
    } else if (heapCount >= 1 && orders[heapCount - 1] === 1) {
      orders.push(0);
      recordMetric('heapPushes');
    } else {
      orders.push(1);
      recordMetric('heapPushes');
    }
    trinkle(i, orders.length - 1);
    checkpoint(Math.floor((i + 1) * 40 / n), 100);
  }
  let completed = 0;
  for (let i = n - 1; i > 0; i--) {
    completed++;
    recordMetric('extractionPasses');
    markSorted(i, completed);
    if (orders[orders.length - 1] <= 1) {
      orders.pop();
    } else {
      const order = orders.pop();
      const rightPos = i - 1;
      const leftPos = i - 1 - leo[order - 2];
      orders.push(order - 1);
      recordMetric('heapSplits');
      trinkle(leftPos, orders.length - 1);
      orders.push(order - 2);
      recordMetric('heapSplits');
      trinkle(rightPos, orders.length - 1);
    }
  }
  markSorted(0);
  checkpoint(100, 100);
}