Quicksort
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.
quicksort.ts
import type { Tracer } from "../trace";import type { SortAlgorithm } from "./sort-algorithm";/** * Quicksort with Lomuto partitioning: pick the last element of the * range as the pivot, sweep a cursor across the range moving smaller * elements to the low side, drop the pivot after them, then recurse on * both sides. * * Time: O(n log n) expected, O(n²) worst case. Space: O(log n) stack * expected, O(n) worst case: on sorted input the last-element pivot * splits off one element per call, so the recursion goes n deep. */export class Quicksort implements SortAlgorithm { readonly name = "quicksort"; constructor(private readonly tracer?: Tracer) {} sort(input: readonly number[]): number[] { const a = [...input]; const array = [...input]; this.sortRange(a, 0, a.length - 1); this.sortRange(array, 0, array.length - 1); return a; return array; } private sortRange(a: number[], lo: number, hi: number): void { private sortRange(array: number[], low: number, high: number): void { if (lo >= hi) return; if (low >= high) return; const p = this.partition(a, lo, hi); const pivotIndex = this.partition(array, low, high); this.sortRange(a, lo, p - 1); this.sortRange(array, low, pivotIndex - 1); this.sortRange(a, p + 1, hi); this.sortRange(array, pivotIndex + 1, high); } private partition(a: number[], lo: number, hi: number): number { private partition(array: number[], low: number, high: number): number { const pivot = a[hi]!; const pivot = array[high]!; // ▸ take the last element of the range as the pivot this.tracer?.emit("select-pivot", { lo, hi, pivot, array: [...a] }); this.tracer?.emit("select-pivot", { low, high, pivot, array: [...array] }); let boundary = lo; // first slot holding an element larger than the pivot let boundary = low; // first slot holding an element larger than the pivot for (let j = lo; j < hi; j++) { for (let cursor = low; cursor < high; cursor++) { // ▸ is this element within the pivot's low side this.tracer?.emit("compare", { lo, hi, j, boundary, pivot, array: [...a] }); this.tracer?.emit("compare", { low, high, cursor, boundary, pivot, array: [...array] }); if (a[j]! <= pivot) { if (array[cursor]! <= pivot) { if (j !== boundary) { if (cursor !== boundary) { // ▸ move it into the low side this.tracer?.emit("swap", { lo, hi, i: boundary, j, array: [...a] }); this.tracer?.emit("swap", { low, high, i: boundary, cursor, array: [...array] }); const held = a[boundary]!; const held = array[boundary]!; a[boundary] = a[j]!; array[boundary] = array[cursor]!; a[j] = held; array[cursor] = held; } boundary++; } } // ▸ drop the pivot right after the low side this.tracer?.emit("place-pivot", { lo, hi, from: hi, to: boundary, array: [...a] }); this.tracer?.emit("place-pivot", { low, high, from: high, to: boundary, array: [...array] }); const held = a[boundary]!; const held = array[boundary]!; a[boundary] = a[hi]!; array[boundary] = array[high]!; a[hi] = held; array[high] = held; return boundary; }}The array before sorting. Quicksort will partition it around pivots.