Merge Sort
in progressThis lesson is still being built - content, demos, and checkpoints may change before release.
in progressThis lesson is still being built - content, demos, and checkpoints may change before release.
merge-sort.ts
import type { Tracer } from "../trace";import type { SortAlgorithm } from "./sort-algorithm";/** * Top-down merge sort: split the range in half, sort each half * recursively, then merge the two sorted halves by repeatedly taking * the smaller front element. Only the left half is copied to a buffer: * the right half is read in place, because a write can never overtake * the right run's unread part. * * Time: O(n log n) always. Space: O(n) extra for the merge buffer. */export class MergeSort implements SortAlgorithm { readonly name = "merge-sort"; constructor(private readonly tracer?: Tracer) {} sort(input: readonly number[]): number[] { const a = [...input]; const array = [...input]; this.sortRange(a, 0, a.length); this.sortRange(array, 0, array.length); return a; return array; } /** Sort the half-open range a[lo, hi). */ private sortRange(a: number[], lo: number, hi: number): void { private sortRange(array: number[], low: number, high: number): void { if (hi - lo < 2) return; if (high - low < 2) return; const mid = lo + Math.floor((hi - lo) / 2); const middle = low + Math.floor((high - low) / 2); // ▸ cut the range in half and sort each half first this.tracer?.emit("split", { lo, mid, hi, array: [...a] }); this.tracer?.emit("split", { low, middle, high, array: [...array] }); this.sortRange(a, lo, mid); this.sortRange(array, low, middle); this.sortRange(a, mid, hi); this.sortRange(array, middle, high); this.merge(a, lo, mid, hi); this.merge(array, low, middle, high); } /** Merge the sorted runs a[lo, mid) and a[mid, hi) back into a[lo, hi). */ private merge(a: number[], lo: number, mid: number, hi: number): void { private merge(array: number[], low: number, middle: number, high: number): void { const buf = a.slice(lo, mid); const leftCopy = array.slice(low, middle); // ▸ copy the left run aside, then merge the two runs this.tracer?.emit("merge", { lo, mid, hi, array: [...a] }); this.tracer?.emit("merge", { low, middle, high, array: [...array] }); let i = lo; // next element of the left run (read from buf) let nextLeft = low; // next element of the left run (read from buf) let j = mid; // next element of the right run (read in place) let nextRight = middle; // next element of the right run (read in place) for (let k = lo; i < mid; k++) { for (let target = low; nextLeft < middle; target++) { const left = buf[i - lo]!; const left = leftCopy[nextLeft - low]!; const right = j < hi ? a[j]! : Infinity; // a used-up right run never wins const right = nextRight < high ? array[nextRight]! : Infinity; // a used-up right run never wins // ▸ which front bar is smaller, left copy or right run if (j < hi) this.tracer?.emit("compare", { lo, hi, i, j, left, right, array: [...a] }); if (nextRight < high) this.tracer?.emit("compare", { low, high, nextLeft, nextRight, left, right, array: [...array] }); const fromLeft = left <= right; // ties go left: the sort stays stable // ▸ write the smaller one into the next slot this.tracer?.emit("write", { lo, hi, from: fromLeft ? i : j, to: k, array: [...a] }); this.tracer?.emit("write", { low, high, from: fromLeft ? nextLeft : nextRight, to: target, array: [...array] }); a[k] = fromLeft ? left : right; array[target] = fromLeft ? left : right; if (fromLeft) i++; if (fromLeft) nextLeft++; else j++; else nextRight++; } }}The array before sorting - merge sort will halve it until every run is a single bar.