External Merge Sort

Chunk-and-merge strategy modeling large-data sorting workflows beyond RAM.

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

How it works

Chunk-and-merge strategy modeling large-data sorting workflows beyond RAM.

Implementation

function externalMergeSort(arr, stats) {
  const n = arr.length;
  if (n < 2) return;
  const source = [...arr];
  const chunkSize = Math.max(8, Math.floor(Math.sqrt(n || 1)));
  let runs = [];
  const sortChunk = chunk => {
    for (let p = 1; p < chunk.length; p++) {
      const key = chunk[p];
      let j = p - 1;
      while (j >= 0) {
        if (chunk[j] <= key) break;
        chunk[j + 1] = chunk[j];
        j--;
      }
      chunk[j + 1] = key;
    }
    return chunk;
  };
  function renderRuns(activeRuns) {
    let writeIndex = 0;
    for (let runIndex = 0; runIndex < activeRuns.length; runIndex++) {
      const run = activeRuns[runIndex];
      for (let valueIndex = 0; valueIndex < run.length; valueIndex++) {
        const value = run[valueIndex];
        arr[writeIndex] = value;
        write(writeIndex, value);
        writeIndex++;
      }
    }
  }
  const mergeRuns = (leftRun, rightRun) => {
    const merged = [];
    let leftIndex = 0;
    let rightIndex = 0;
    while (leftIndex < leftRun.length && rightIndex < rightRun.length) {
      if (leftRun[leftIndex] <= rightRun[rightIndex]) {
        merged.push(leftRun[leftIndex++]);
      } else {
        merged.push(rightRun[rightIndex++]);
      }
    }
    while (leftIndex < leftRun.length) merged.push(leftRun[leftIndex++]);
    while (rightIndex < rightRun.length) merged.push(rightRun[rightIndex++]);
    return merged;
  };
  for (let i = 0; i < n; i += chunkSize) {
    const chunk = source.slice(i, Math.min(i + chunkSize, n));
    runs.push(sortChunk(chunk));
  }
  renderRuns(runs);
  while (runs.length > 1) {
    const nextRuns = [];
    for (let i = 0; i < runs.length; i += 2) {
      if (i + 1 >= runs.length) {
        nextRuns.push(runs[i].slice());
        continue;
      }
      nextRuns.push(mergeRuns(runs[i], runs[i + 1]));
    }
    runs = nextRuns;
    renderRuns(runs);
  }
  for (let i = 0; i < n; i++) markSorted(i);
}