Any deterministic comparison sort correct for all orders of distinct sort keys needs at least comparisons on some input. Here is a factorial, a logarithm, and the brackets denote the ceiling function. This is a worst-case requirement, not a requirement on every input.[ref]
For three distinct sort keys, the diagram has one leaf for each possible ordering of the original entries . Some paths need two comparisons, others three; two comparisons cannot distinguish all six orders.
Figure 1.
A comparison decision tree distinguishes the six orders of three distinct sort keys
. Each internal node tests the sort keys of two fixed original entries. A leaf gives the order in which their complete sorting records must be emitted. There are leaves and maximum depth three. The diagram assumes distinct sort keys, so no equality branch is needed. Details.
For the proof outline, represent adaptive comparisons by a binary tree: distinct sort keys give only two outcomes per comparison. A leaf fixes an output permutation; one leaf cannot correctly handle two different strict orders. Hence there must be at least leaves. Maximum depth permits at most leaves, so gives the bound. Even a three-way comparator has only two reachable outcomes on this distinct-key family.
The growth follows without a precise approximation to the factorial. At least of its factors are at least , so for ,
The brackets denote the floor function. This gives a constant times lower bound for large , matching the worst-case comparison growth of merge sort.
Distinct sort keys are essential to the counting argument. Identical sort keys do not create different strict orders. If all sort keys coincide, stable sorting returns the arrival order. This does not remove the worst-case bound for a comparison sort required to handle arbitrary distinct-key inputs.
Counting sort Created 2026-10-13 Updated 2026-10-18
Counting sort uses integer sort keys in directly as indices into an array of counts. Constant-cost counting and indexing give time complexity in Big O notation. To retain complete sorting records, cumulative counts locate their output ranges; filling each range from its end while scanning the input backwards gives stable sorting. Emitting bare sort keys from counts would discard associated data. The term matters when the key range is large.[ref]
For an original worked example, take complete sorting records and possible integer sort keys:
The letters label the associated data, and are never compared. Both input and output array indices start at zero, so their valid positions are ; the count array has array indices . Stable sorting must produce
The equal-sort key groups retain and in arrival order. Counting sort prefix ranges explain where those groups belong; stable record placement in counting sort explains how to fill them without losing that order or the complete sorting records.
Heapsort 2026-10-31
Build a max-heap in an array, then repeatedly exchange its root with the last element of the active heap and restore heap order by heapify down. The inactive suffix grows in ascending order. Bottom-up heap construction costs because the number of nodes at height decreases geometrically, and the extraction phase costs . Iterative sifting uses constant auxiliary storage. Long-distance exchanges can reverse equal-key records, so the usual version does not provide stable sorting.
Insertion sort 2026-10-13
Insertion sort grows a sorted prefix of an array one entry at a time. In the adjacent-swap version, start with position , compare its current sort key with the preceding sort key, and swap the complete sorting records only when the right sort key is strictly smaller. Continue leftward until no swap is needed or the entry reaches position zero; then process original position . A tie stops the pass. This is the strict comparison and exchange rule in the authored implementation.[ref]
Take , where the subscript labels a complete sorting record and comparisons inspect only its sort key. Starting from index zero, the complete key-comparison trace is:
ComparisonRight sort key smaller than left?ActionArray after the action
1, insert position : yesSwap positions .
2, insert position : noStop this pass on a tie.
3, insert position : yesSwap positions .
4, continue leftward: yesSwap positions .
5, continue leftward: noStop this pass on a tie.
Figure 1.
Insertion sort: five sort key comparisons and three complete-entry swaps
. Each row after the input shows the state after one comparison. Orange cells mark an adjacent swap; green cells mark a stopping tie. Identity letters are not compared. Both equal-key groups retain their input order, demonstrating stable sorting. Details.
The loop invariant is that, before pass , positions satisfy sortedness and contain precisely the entries originally in that prefix. Moving the next entry left through strictly larger sort keys extends that prefix, and every swap retains sorting permutation preservation. Equal-key entries never cross, giving stable sorting. Each leftward step decreases the position, and each completed pass increases the prefix length, so the algorithm terminates.
With constant-cost key comparisons and swaps, reverse-ordered distinct sort keys cause comparisons and swaps. Thus worst-case time complexity is in Big O notation. An already sorted array needs only key comparisons for and no swaps. The adjacent-swap version uses constant auxiliary space complexity. Small or nearly sorted arrays can therefore make insertion sort useful despite its quadratic worst case.
A least-significant-digit radix sort processes numerical digits from smallest to largest place value. Fix integers and , and take complete sorting records whose sort keys satisfy . Use exactly positions in positional notation with radix , including leading zeros. At pass , apply a stable digit sort to the current array using
The floor function selects the integer quotient, and the remainder selects the current numerical digit. Move the complete sorting record, including its associated data. There are exactly passes, so this procedure terminates even for an empty array. The radix sort suffix invariant supplies its algorithm correctness argument.
For an original input, let the letters identify the attached data:
Here , , and leading zeros only align place values; represents the integer five. The exact pass ledger is:
After the units pass, the numerical digits are . The ties retain before and before before . This array is not yet ordered by its whole integer sort keys: still precedes . After the tens pass, the two-position suffixes are . In particular, now precedes : their tens numerical digits differ, so stable sorting does not require retaining their previous order. The hundreds pass produces the final sorted array, with all attached letters preserved.
Figure 1.
Three stable digit passes order successively longer suffixes
. Original decimal radix sort ledger. Each card carries an unchanged record label. Orange marks the numerical digit read in the current pass; green marks lower positions already processed. The suffix row beneath each pass shows precisely what is ordered, rather than claiming whole-key sortedness early. Details.
Merge algorithm Created 2026-10-13 Updated 2026-10-18
The merge algorithm combines two finite sorted sequences under the same total order, retaining every entry exactly once. Keep a position at the first unconsumed entry, or "head", of each input. While both heads exist, compare their keys, append the smaller head to the output, and advance only that head's position. On equality take the left head. Once either sequence is exhausted, append the other remainder without further key comparisons. Empty inputs therefore require no head comparisons. The MIT notes illustrate this two-head combination step.[ref]
For example, the merge algorithm applied to and returns :
Figure 1.
Five head comparisons in the merge algorithm
. Orange heads are chosen and arrows lead to the next output entry. The compared head pairs are (1,2), (4,2), (4,3), (4,8) and (7,8). The left sequence then runs out; append (8,9) without further key comparisons. Details.
The head pairs , , , and select respectively. After the fifth comparison the left sequence is empty, so is appended. Seven entries are written, but only five head comparisons occur: tests for exhaustion and appending the remainder are separate operations. This distinction makes the example's count smaller than the bound .
For nonempty inputs of lengths , each head comparison consumes exactly one entry. After such removals at most one entry remains, so both heads cannot still exist. Hence the merge algorithm uses at most head comparisons. The bound can be attained: and require six head comparisons before the left sequence is exhausted. With constant-cost comparisons, access and copying, writing all entries gives time complexity in Big O notation. Writing a separate output array needs entry slots; the two positions require only constant additional storage.
The merge algorithm is useful whenever two already ordered collections must become one: it reads each input only forwards. This works with arrays and linked lists, without needing access to arbitrary middle positions. Choosing left on ties preserves each input's internal order and places equal left entries before equal right entries, which is the combination rule needed for stable sorting in merge sort.
The loop invariant behind algorithm correctness is that the emitted prefix is sorted, contains exactly the consumed entries, and every emitted key is at most every unconsumed key. Each sorted sequence has its smallest remaining key at its head, so the smaller of the two heads is safe to append. The invariant survives each removal; when one sequence ends, the other sorted remainder is safe to append in full. Each comparison removes an entry, so termination follows from the finite input lengths.
// Target: computer-science.bigb
Merge sort Created 2026-10-13 Updated 2026-10-18
Merge sort is a divide-and-conquer algorithm that sorts a finite array by splitting it into contiguous halves, sorting each half using recursion, and combining the two sorted arrays with the merge algorithm. An empty or one-entry array is returned immediately. For length , put the first entries on the left and the remaining on the right; the brackets are the floor function and ceiling function. Always choose the left head on equal keys to obtain stable sorting. This is the split, sort, combine structure taught in the MIT lecture notes.[ref]
Take the concrete input array
The tags identify the entries originally at indices and , counting from zero. Both keys are ; the tags are never compared. Reading downward in the figure follows the splits, then the returned sorted arrays. Arrows show dependencies; the level layout groups independent calls rather than giving their chronological execution order.
Figure 1.
Merge sort splits into base cases and returns sorted arrays
. The tagged keys 2a and 2b retain their original order through stable sorting. The merge algorithm makes 4, 6 and 7 head comparisons on the three returning levels, for 17 in total. Details.
The first two halves are and . Their pairs split into one-entry base cases. Applying the merge algorithm gives the following fully worked return levels; each number in the last column counts only comparisons between two available heads.
Returning levelResulting sorted arraysHead comparisons
Pairs, , ,
Halves,
Whole array
In the left-half merge algorithm, the head pairs are , and ; it takes , then appends . In the final merge algorithm, the head pairs are , , , , , and . It takes , then appends . Thus this precise version makes head comparisons and, ignoring identity tags, returns .
The elegance of merge sort is that it creates exactly the structure its next step needs: two smaller sorted arrays make the larger ordering inexpensive. Once the whole array is sorted, equal keys are adjacent and binary search can serve repeated queries. Stable sorting also retains information that the keys alone do not express. If the left-half merge algorithm instead takes the right head on the tie, it returns ; the keys are sorted but stable sorting has failed.
For time complexity, assume each key comparison, entry access and entry copy has constant cost. The merge algorithm processes entries in time. In Big O notation, the resulting recurrence relation is
For , the balanced levels contain entries each and there are returning levels. Each costs , so the worst-case time complexity is . For arbitrary , there are at most returning levels, each processing at most entries, giving the same bound. The worst-case number of head comparisons is also . Here is the base-two logarithm. The MIT notes give this level-by-level analysis for merge sort.[ref]
The conventional array implementation uses a reusable buffer of entries for the merge algorithm, together with pending calls for recursion. Its auxiliary space complexity is therefore , beyond the original array. This bound describes that implementation. A linked list permits a merge algorithm that reconnects existing nodes; its storage accounting differs from copying array entries into a buffer.
For a short algorithm correctness outline, the empty and one-entry base cases are sorted. Assuming recursion correctly sorts both shorter halves, the merge algorithm returns all their entries in order. Equal keys within a half keep their relative order by the same argument applied to the smaller call; across halves, every left entry originally preceded every right entry, so taking left first preserves stable sorting. Both half-lengths decrease for , establishing termination.
For heapsort, use the usual array representation of a max-heap, indexed from zero, with children . Build the max-heap from the last internal node towards the root, using heapify down. The three build stages are
start                    [2,7,3,2,8,1]
sift at index 2          [2,7,3,2,8,1]
sift at index 1          [2,8,3,2,7,1]
sift at index 0          [8,7,3,2,2,1]
At index zero, the value 2 moves below 8 and then below 7. Next exchange the root with the last active element, shorten the active heap, and restore heap order. A vertical separator below distinguishes the active prefix from its sorted final suffix.
Removed maximumAfter root exchangeAfter restoring heap order
1,7,3,2,2 | 87,2,3,1,2 | 8
2,2,3,1 | 7,83,2,2,1 | 7,8
1,2,2 | 3,7,82,1,2 | 3,7,8
2,1 | 2,3,7,82,1 | 2,3,7,8
1 | 2,2,3,7,81 | 2,2,3,7,8
The trace chooses the left child when equal children tie; other tie conventions can give different intermediate heaps with the same sorted output.
For quicksort, use the head as pivot and partition the remaining elements into those strictly less than it and those greater than or equal to it, retaining their order within each partition. This specifies how the duplicate 2 is handled:
pivot 2:  [1]       | 2 | [7,3,2,8]
pivot 7:  [3,2]     | 7 | [8]
pivot 3:  [2]       | 3 | []
Empty and singleton lists are already sorted. Returning from the recursive calls gives
Q [3,2]       = [2] ++ [3] ++ []       = [2,3]
Q [7,3,2,8]   = [2,3] ++ [7] ++ [8]   = [2,3,7,8]
Q [2,7,3,2,8,1] = [1] ++ [2] ++ [2,3,7,8]
For counting sort, count keys through . After reading each input key, the count vector is
initial: (0,0,0,0,0,0,0,0)
read 2:  (0,1,0,0,0,0,0,0)
read 7:  (0,1,0,0,0,0,1,0)
read 3:  (0,1,1,0,0,0,1,0)
read 2:  (0,2,1,0,0,0,1,0)
read 8:  (0,2,1,0,0,0,1,1)
read 1:  (1,2,1,0,0,0,1,1)
The cumulative counts are . For the usual stable sorting version with zero-based output indices, scan the input from right to left, place a key at position , and decrement . The successive placements are 1 at , 8 at , the later 2 at , 3 at , 7 at , and the earlier 2 at . The final answer for all three sorting algorithms is
Heapsort uses time for bottom-up heap construction and for each extraction, giving worst-case time complexity. An iterative heapify down on an array uses auxiliary space complexity. Ordinary heapsort is not a stable sorting method.
Quicksort has balanced-case and average-case time complexity when the input order supplies suitably distributed pivot ranks. With the prescribed first-element pivot, sorted inputs produce subproblems of sizes and , giving . With the stated two-way partition convention, all-equal keys also give this worst case. There is no input-independent expected bound without a distribution or randomization assumption. For an in-place array implementation, the recursion stack is when balanced and in the worst case; the functional list trace also allocates new partition spines and copied concatenation prefixes. Ordinary in-place quicksort is not stable sorting; the stable-partition list convention used in the trace can preserve equal-key order at additional allocation cost.
Counting sort uses time complexity for possible consecutive integer keys. The stable sorting version uses auxiliary space complexity including the output array; counting and re-emitting bare keys needs only beyond its output. It is attractive when is small, but a sparse huge key range makes both initialization and count storage expensive. It relies on indexing by bounded integer keys, so it is not subject to the comparison sorting lower bound.
Represent the two recursively split arrays by adjacent half-open ranges of the original array. This avoids allocating a fresh pair at every call. The caller supplies a distinct work array with at least as many elements as a; only that work array is used for merging. The Oberon code is
PROCEDURE SortRange(VAR a, work: ARRAY OF INTEGER;
                    l, r: INTEGER);
  VAR m, i, j, k: INTEGER;
BEGIN
  IF r-l > 1 THEN
    m := l + (r-l) DIV 2;
    SortRange(a, work, l, m);
    SortRange(a, work, m, r);
    i := l; j := m; k := l;
    WHILE (i < m) & (j < r) DO
      IF a[i] <= a[j] THEN
        work[k] := a[i]; i := i+1
      ELSE
        work[k] := a[j]; j := j+1
      END;
      k := k+1
    END;
    WHILE i < m DO
      work[k] := a[i]; i := i+1; k := k+1
    END;
    WHILE j < r DO
      work[k] := a[j]; j := j+1; k := k+1
    END;
    k := l;
    WHILE k < r DO
      a[k] := work[k]; k := k+1
    END
  END
END SortRange;

PROCEDURE MergeSort(VAR a, work: ARRAY OF INTEGER);
BEGIN
  SortRange(a, work, 0, LEN(a))
END MergeSort;
For a range of length , the left length is and the right length is , so as required. Both are positive and smaller than , giving the recursive loop variant analogue for algorithm termination. Empty ranges are also harmless, although the question supplies a nonempty array.
For the first merge loop, regard the two sorted ranges as fixed input sequences and . They are not overwritten during the merge. A loop invariant is that , , , and work[l:k) is the stable sorted merge of the consumed prefixes a[l:i) and a[m:j). Every emitted element is at most every remaining element. Taking the smaller head preserves this property, and choosing the left head on equality preserves stable sorting. The loop variant is
It is nonnegative and decreases by one per iteration. On exit at least one range is exhausted.
For the second loop, the remaining left range is already sorted and any right remainder is absent whenever that loop runs. Appending the next left element preserves the same merge loop invariant; its loop variant is . For the third loop, the left range is exhausted and the right remainder is appended in order; its loop variant is . On completing both loops, work[l:r) is the complete sorted merge and .
In the final copying loop, the loop invariant is that a[l:k) equals work[l:k), work[l:r) remains the correct sorted answer, and positions outside a[l:r) are unchanged. Its loop variant is . At exit the whole answer has been copied back. By strong induction on range length, the recursive calls sort their halves, and the merge preserves the original multiset. Thus
The work array must not alias a: overwriting the source while merging would invalidate the merge loop invariant.
Assume less is a lawful strict comparison, for example a strict weak order, and the two input linked lists are disjoint and sorted by it. Allocate a result header and move existing data linked list nodes; do not copy their values.
PROCEDURE Merge(less: LESS; q, r: List; VAR p: List);
VAR result: List; last, a, b: Tail;
BEGIN
  NEW(result); result.count := q.count + r.count;
  result.tail := NIL; last := result;
  a := q.tail; b := r.tail;
  WHILE (a # NIL) & (b # NIL) DO
    IF less(b,a) THEN
      last.tail := b; b := b.tail
    ELSE
      last.tail := a; a := a.tail
    END;
    last := last.tail
  END;
  IF a # NIL THEN last.tail := a ELSE last.tail := b END;
  q.tail := NIL; q.count := 0;
  r.tail := NIL; r.count := 0;
  p := result
END Merge;
The loop invariant says that the segment from result.tail through last is a sorted prefix of the desired merge algorithm, containing exactly the nodes already removed from the two unread chains. Every element of this prefix is no greater than either unread head. Choosing the smaller unread head maintains this property; choosing from q on a tie also gives stable sorting.
At exit, at most one unread chain is nonempty, and its sorted suffix can be attached as a whole. Each original data linked list node occurs exactly once in the result, and its saved count is correct. Clearing the consumed headers prevents them from claiming ownership of the result's nodes.
Use a merge algorithm with one unread index in each run. Here u is the module's array; the destination is disjoint from the source and lies within the array bounds:
PROCEDURE Merge(a, b, c, d: INTEGER);
VAR i, j, k: INTEGER;
BEGIN
  i := a; j := b; k := d;
  WHILE (i < b) & (j < c) DO
    IF u[i] <= u[j] THEN
      u[k] := u[i]; i := i+1
    ELSE
      u[k] := u[j]; j := j+1
    END;
    k := k+1
  END;
  WHILE i < b DO
    u[k] := u[i]; i := i+1; k := k+1
  END;
  WHILE j < c DO
    u[k] := u[j]; j := j+1; k := k+1
  END
END Merge;
Let denote the original two runs. The first loop invariant says and is the sorted merge of and ; moreover each emitted value is at most every unread value. Choosing the smaller head preserves that condition. Equal heads are taken from the first run, giving stable sorting within this merge. The loop variant is .
For each copying loop, the output remains the sorted merge of the consumed prefixes, at least one source run is exhausted, and only the indicated suffix remains. The respective loop variants are and . At exit both source runs are exhausted:
The destination's multiset is the sum of the two source multisets.
Yes, if a merge takes from its left half when two sort keys compare equal. Within each half the induction hypothesis preserves equal-key order. Across the two halves every left-half occurrence originally preceded every right-half occurrence, and the tie rule preserves that order. Reversing the tie rule would lose stable sorting.
For bare characters, equal occurrences are indistinguishable. The count-and-reconstruct implementation in (d) nevertheless does not preserve the identities of equal-key records, and so cannot provide stable sorting of associated payloads.
A stable refinement computes cumulative counts , the number of input keys at most , then scans the original records right to left. For a record with key , decrement and place the complete record at output index . Equal-key records are placed from the end of their allocated range in reverse scanning order, preserving their original relative order. This costs time and auxiliary storage.
Prefer the alternatives for large unrestricted arrays. Merge sort and heapsort have worst-case time, while insertion sort has worst-case time and can be much faster on nearly sorted data. The intended stooge sort has worst-case , so even insertion sort has the better worst-case exponent.
The first two give the stronger general asymptotic guarantee. Choices between them can depend on storage and stable sorting requirements; these considerations do not rescue Stooge sort's much larger work count.
No: Stooge sort is not stable. Give equal keys distinct record identities and start with
The initial endpoint swap gives . The later two-element calls either see increasing keys or equal keys, so they make no swap. The two records with key 2 have reversed their original order, contradicting stable sorting. The example uses length three, where the intended current-length split is unambiguous.
A compatible Ord instance supplies a total order, so use comparison sort to put equal values together in time and then group adjacent equals in linear time. To preserve the first-occurrence group ordering of part (f), first tag every element with its original index and stable sorting by value. Group the sorted records by value; stability preserves their internal input order. Each group's first record carries its smallest original index. Sort the groups by that index and finally remove the tags. The second sort has at most records, so the total remains time and storage. Merely returning the value-sorted groups could reorder the classes, so the index step matters when preserving the exact earlier convention. The stronger interface evades the equality-only lower bound by permitting order comparisons.
Priority queue 2026-10-13
A priority queue stores items with comparable priority keys and supports insertion, inspection of a best-priority item, and removal of a best-priority item. Here we use the minimum convention: keys have a total order, and smaller means earlier. The operation peek returns a stored item of minimum priority without removing it; extract-min removes and returns such an item. Both require a nonempty priority queue, or an explicitly specified empty-case result. A maximum-oriented priority queue reverses the choice.
For the requests , peek returns Bo and leaves all three requests stored. extract-min returns Bo and leaves Ada and Cy; another peek returns Cy. Inserting now makes Dee the next request selected. This changing collection need not be a sorted array: only the next choice must be available.
Distinct items may share a priority. Inserting alongside permits either item to be removed first. A priority queue does not inherently promise arrival order on ties. If that policy matters, compare priority first and a unique increasing arrival number second; the resulting total order makes earlier arrivals win equal-priority ties. This is analogous to the motivation for stable sorting, but it is an extra policy for the priority queue.
The interface specifies behavior; the data structure determines costs. A binary heap implements a priority queue with constant-time peek and logarithmic-time insertion and extract-min, under the cost assumptions below. Scheduling by urgency or by the time of the next event needs exactly this repeated next-item selection. MIT's notes use these applications to motivate the priority queue interface.[ref]
Radix sort Created 2026-10-13 Updated 2026-10-18
Radix sort sorts encoded sort keys a digit at a time. Its least-significant-digit version uses stable sorting on each successive digit, often by counting sort. For digits with possible values each, it takes time complexity in Big O notation, assuming constant-cost digit extraction and indexing. Stable sorting retains the ordering established by earlier digits when a later digit ties. Both digit access and the direct indexing in counting sort lie outside the comparison sort model, so these bounds do not contradict the comparison sorting lower bound.[ref]
After passes, , the array is in nondecreasing order of , the integer represented by its lowest numerical digits. Equivalently it is ordered by the tuple under lexicographic order, with the most significant processed numerical digit first. Among equal suffixes it retains the original order of the complete sorting records.
The loop invariant has a simple mathematical induction proof. Before any pass, for every sort key, so the original array satisfies it. Suppose it holds after passes. The next stable digit sort puts smaller groups first and preserves the existing order within each tie. Since
the new array is ordered by . Equal new suffixes have equal current numerical digits and equal old suffixes, so their original order is also preserved. Every pass is a permutation of complete sorting records, establishing sorting permutation preservation. At termination , the assumption gives : the invariant becomes whole-key sortedness, together with stable sorting.