Ford-Johnson

Merge-insertion for minimum comparisons.

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

How it works

Merge-insertion for minimum comparisons.

Implementation

function fordJohnsonSort(arr, stats) {
  const n = arr.length;
  function binaryInsert(chain, elIdx, maxIdx) {
    let left = 0, right = maxIdx;
    while (left < right) {
      let mid = left + Math.floor((right - left) / 2);
      compare(chain[mid], elIdx);
      if (arr[chain[mid]] < arr[elIdx]) left = mid + 1; else right = mid;
    }
    chain.splice(left, 0, elIdx);
  }
  const J = [1, 3, 5, 11, 21, 43, 85, 171, 341, 683, 1365, 2731, 5461, 10923, 21845, 43691, 87381, 174763, 349525, 699051, 1398101];
  function fj(indices) {
    let len = indices.length;
    if (len < 2) return indices;
    let pairs = [];
    let leftOver = -1;
    for (let i = 0; i < len; i += 2) {
      if (i + 1 < len) {
        compare(indices[i], indices[i + 1]);
        if (arr[indices[i]] > arr[indices[i + 1]]) pairs.push({
          win: indices[i],
          lose: indices[i + 1]
        }); else pairs.push({
          win: indices[i + 1],
          lose: indices[i]
        });
      } else {
        leftOver = indices[i];
      }
    }
    let winners = pairs.map(p => p.win);
    let sortedWinners = fj(winners);
    let mainChain = [...sortedWinners];
    let orderedPends = [];
    for (let i = 0; i < mainChain.length; i++) {
      const w = mainChain[i];
      let p = pairs.find(x => x.win === w);
      if (p) orderedPends.push(p.lose);
    }
    if (orderedPends.length > 0) mainChain.unshift(orderedPends[0]);
    let jIdx = 1;
    while (true) {
      let jk = jIdx < J.length ? J[jIdx] : J[J.length - 1];
      let jprev = jIdx - 1 >= 0 ? J[jIdx - 1] : J[J.length - 2];
      if (jprev - 1 >= orderedPends.length) break;
      let end = Math.min(jk - 1, orderedPends.length - 1);
      let start = jprev;
      for (let i = end; i >= start; i--) {
        let p = orderedPends[i];
        let pair = pairs.find(x => x.lose === p);
        let wIdx = pair ? mainChain.indexOf(pair.win) : mainChain.length;
        if (wIdx === -1) wIdx = mainChain.length;
        binaryInsert(mainChain, p, wIdx);
      }
      jIdx++;
      if (end === orderedPends.length - 1) break;
    }
    if (leftOver !== -1) binaryInsert(mainChain, leftOver, mainChain.length);
    return mainChain;
  }
  let initialIndices = [];
  for (let i = 0; i < n; i++) initialIndices.push(i);
  let finalOrder = fj(initialIndices);
  let output = new Array(n);
  for (let i = 0; i < n; i++) output[i] = arr[finalOrder[i]];
  for (let i = 0; i < n; i++) {
    arr[i] = output[i];
    write(i, output[i]);
    markSorted(i);
  }
}