Sources & coverage

  • Lecture 4 (Püschel): searching (linear and binary search), the first lower-bound proofs, the sorting problem, the sortedness check, Bubble Sort, invariants, Selection Sort.
  • Lecture 5 (Püschel): Insertion Sort, Mergesort (including Merge), analysis.
  • Script Suchen und Sortieren (version of 7 Oct 2026): Kap. 1 and §§2.1–2.4 cover this week. §§2.5–2.7 (Quicksort, Heapsort, lower bound for sorting, Bucketsort) were not covered in lecture yet. They are announced for next week and are left out here.
  • These notes follow the script’s numbering (Def. 1.1, Def. 2.1, §1.2.1, …). Where the lecture and script differ in pseudocode, examples or details, this is marked ⚠ Discrepancy.

Correction to the Week 2 notes (Maximum Subarray Sum)

In the recap at the start of Lecture 4, Püschel says that both divide and conquer and an inductive approach worked for Maximum Subarray Sum, and that the final algorithm was . He later adds (end of Lecture 4) that this inductive algorithm assumed “the problem is already solved up to position ” and extended the solution by one step, so it was implicitly based on an invariant. The Week 2 recording was missing this part, so the Week 2 notes stopped at the divide-and-conquer reconstruction. A reconstruction of the linear algorithm is in §5.1 below. ⚠ Reconstruction: check it against the handwritten notes.


1. Executive Overview & Core Objectives

Searching (Suchen) in an unsorted array costs comparisons, and this is optimal. In a sorted array, binary search (binäre Suche) needs only comparisons, and this too is optimal. Both optimality claims are lower bounds (untere Schranken): statements about every possible comparison-based algorithm. The second one is proved with a decision tree (Entscheidungsbaum), an information-theoretic argument. Since sorting makes searching dramatically faster, we turn to sorting (Sortieren). Bubble Sort, Selection Sort and Insertion Sort all still cost operations, but they illustrate the central new concept: the (loop) invariant (Invariante). An invariant can be used to prove correctness by induction, and, the other way round, to design an algorithm. Mergesort (divide and conquer) is the first sorting algorithm, at the price of extra memory: it is not in place.

Learning objectives. After this week you should be able to:

  • State the search problem (Def. 1.1) and the sorting problem (Def. 2.1) formally, together with their elementary operations.
  • Write linear search and binary search (recursive and iterative), and analyse them ( and ).
  • Explain why lower bounds are hard (they cover all algorithms), and reproduce:
    • the bound for unsorted search (“every element must take part in a comparison”),
    • the decision-tree bound for sorted search.
  • Use binary search to find the insertion position (Einsortierung) of an element that is not in the array.
  • Define and use a loop invariant: initialisation, maintenance (induction step), termination.
  • Derive Selection Sort and Insertion Sort from an invariant.
  • Count comparisons and swaps/moves for Bubble, Selection and Insertion Sort in the worst case.
  • Write Mergesort and Merge, trace the recursion order, and solve to get .
  • Explain “in place” and why Mergesort is not.

2. Core Concepts & Formal Definitions

2.1 Searching (Script Kap. 1)

Definition 1.1 (Search / Suche)

Given: an array of numbers and an element .
Find: an index with , or the answer “nicht gefunden” (not found) if does not occur in .
Elementary operation: comparisons (Vergleiche) such as . We study only comparison-based search. Other kinds (e.g. hash tables) come later.

Remarks:

  • Restricting to numbers is no loss of generality: every object in a computer is a bit string, which can be read as a number.
  • Motivation: find the exam of one student, given the student number (Matrikelnummer), in a stack of 600–1000 exams. Whether the stack is sorted makes a huge difference. We distinguish
    • Case 1: unsorted (§1.1),
    • Case 2: sorted ascending, (§1.2).

Definition (Pseudocode), clarified in Lecture 4

Pseudocode is not a defined programming language. Whether you write for or “für”, use or , or mix in plain text is irrelevant. The only goal is that the reader understands the algorithm. The script adds that its level of detail may depend on the audience; e.g. “index of the maximum” need not be spelled out.

2.2 Lower bounds and

Definition (Lower bound / untere Schranke)

A lower bound for a problem says that every algorithm (in a fixed model, here: comparison-based) needs at least a certain number of elementary operations in the worst case.

Definition ( , from the exercise sheet)

For :

expresses “grows at least as fast as”, i.e. an asymptotic lower bound. Moreover .

Exam Trap: lower bounds are about all algorithms

“Linear search must look at every element” is not a lower-bound proof. It talks about one specific algorithm. Püschel’s counter-thought: some unknown algorithm might first impose partial structure on the array cheaply (say in steps) and then need fewer comparisons. A valid lower-bound argument must also cover algorithms nobody has thought of yet. That is why lower bounds are hard, and why for many problems it is unknown whether the best known algorithm is optimal.

2.3 Trees: terminology needed this week

Definition (Binary tree terminology)

  • A tree (Baum) consists of nodes (Knoten) connected by edges (Kanten). In a binary tree (Binärbaum) each node has at most two children (Nachfolger).
  • The top node is the root (Wurzel). Nodes without children are leaves (Blätter).
  • Depth (Tiefe) of a node = number of edges on the path from the root; the root has depth 0.
  • Height (Höhe) of the tree = maximum depth of a leaf. The script uses “height” and “depth” of the tree interchangeably.

Definition (Decision tree / Entscheidungsbaum, "Suchbaum" in the lecture)

Any comparison-based algorithm, for a fixed input size , can be drawn as a binary tree:

  • each inner node is a comparison (any comparison, even something like ”?”);
  • its two children are the continuations for the answers yes / no;
  • each leaf is a return (an output).

Running the algorithm on an input = following one root-to-leaf path. The number of comparisons in the worst case = the height of the tree.

⚠ Terminology: in the lecture Püschel calls this a “Suchbaum” (search tree). The script calls it an Entscheidungsbaum (decision tree). “Binary search tree” will later mean a data structure, so prefer “decision tree” here.

2.4 Sorting (Script Kap. 2)

Definition 2.1 (Sorting / Sortieren)

Given: an array of numbers.
Find: a permutation (Umordnung, Permutation) of that is sorted ascending:

Elementary operations: comparisons, and swaps (Vertauschungen) of two array elements, more generally moves (Bewegungen), i.e. writes.

Definition (In place)

A sorting algorithm works in place if it computes inside the memory of , using only extra memory (a constant number of helper variables). Motivation: sorting 100 GB of data with another 100 GB of scratch space is undesirable. When extra memory spills to slower storage (disk instead of RAM), the algorithm can slow down drastically.

Why sort? Sorting makes search much faster ( instead of ). Sort once, then do many searches. The one-time sorting cost amortises (amortisiert sich). Example: student records, keyed by student number, are sorted once and then looked up repeatedly.

2.5 Invariants

Definition (Loop invariant / Invariante)

An invariant is a statement depending on the loop counter that holds after every iteration of a loop. “Invariant” means “unchanging”: the statement form stays the same while changes.
A correctness proof with an invariant has three parts:

  1. Initialisation: holds before the first iteration (e.g. or ). This is the induction base.
  2. Maintenance: if holds and one iteration runs, then holds. This is the induction step.
  3. Termination: the invariant at the end (e.g. or ) implies that the output is correct.

Intuition: invariant = induction applied to a loop

The invariant describes a partially solved state: “the problem is solved up to stage ”. The loop body is the step . This is the same pattern as week 1’s inductive algorithms (star search) and the inductive Maximum Subarray algorithm. Püschel: the concept is identical to the invariants you meet in the parallel programming course (“Einführung in die Programmierung”). Formal verification is its own field; here invariants are used for correctness arguments and, especially, for design.

Definition (Inductive algorithm / induktiver Algorithmus)

An algorithm that builds the solution step by step, making progress by one unit per step (from the solution for stage to the solution for stage ). Selection Sort and Insertion Sort are inductive. Each is naturally described by an invariant.


3. Step-by-Step Derivations & Mathematical Proofs

3.1 Linear search (Script §1.1)

Linear-Search(A[1..n], b)
    for i := 1 to n:
        if A[i] = b: return i
    return "nicht gefunden"

Runtime. In the worst case ( not present, or at the last position) the loop runs times, with one comparison each. “Loops become sums”:

3.2 Lower bound for unsorted search:

Claim

Every comparison-based algorithm for searching an unsorted array needs comparisons in the worst case.

Lecture argument.

  1. Every one of the array elements must take part in at least one comparison. Suppose some element is never compared. Then the algorithm behaves identically whether or not, so for one of the two cases its answer is wrong. This also covers clever algorithms that first impose some structure: imposing structure needs comparisons too, so the argument still applies.
  2. Each comparison involves two “elements” (array entries or ). In total items ( and ) must each appear in a comparison. So

Püschel adds that in fact exactly comparisons are necessary, so linear search is exactly optimal. But this needs a more careful argument than the one above.

⚠ Lecture vs. script: the script argues differently and more directly. Suppose . After the algorithm has read elements, it cannot yet decide, because could be in the last unread element. So it must read all . This is the intuition behind the sharper bound of . The lecture’s argument is weaker but fully rigorous for .

3.3 Binary search (Script §1.2)

Idea (common sense, used by anyone sorting a stack of exams): look at the middle. If it is not the target, the comparison tells you which half the target must be in; throw the other half away and repeat. This is divide and conquer: only one of the two halves is ever searched.

Middle index.

Examples: (the exact middle). .

Why and not ? (student question)

For , is not the middle. Asymptotically it makes no difference. Equivalently one can use , since for every integer .

Recursive version (lecture), corrected:

Binary-Search(A[1..n], b)              // n = current length of the array
    if n = 0: return "nicht gefunden"   // empty array
    m := floor((n+1)/2)
    if A[m] = b: return m               // lucky case
    if b < A[m]:
        return Binary-Search(A[1..m-1], b)          // left half
    else:
        r := Binary-Search(A[m+1..n], b)            // right half
        if r = "nicht gefunden": return r
        return m + r                    // translate index back (offset m)

⚠ Discrepancies / corrections in the lecture version:

  1. Püschel first wrote the right-half call as A[1..m+1]. A student caught it (“klassisch idiotischer Fehler”). It must be A[m+1..n].
  2. The transcript has “Return N” in the lucky case. It must be return m.
  3. inside the routine means the length of the current (sub)array, not the original (clarified after a student question).
  4. A recursive call on A[m+1..n] returns an index relative to that subarray. In a real implementation you must add the offset (or pass the bounds as parameters). Püschel called this a programming detail that doesn’t change the idea. The extra line return m + r above handles it.

Iterative version (script), with invariant ” is the remaining search range”:

Binary-Search(A[1..n], b)
    l := 1; r := n                      // initial search range
    while l <= r:
        m := floor((l + r) / 2)
        if A[m] = b: return m           // found
        else if A[m] > b: r := m - 1    // continue left
        else: l := m + 1                // continue right
    return "nicht gefunden"

Correctness (script). Since is sorted ascending: if , then positions hold only keys , so it suffices to search . The case is symmetric. Termination: the search range shrinks by at least one element per step, so the procedure ends after finitely many steps.

3.4 Runtime of binary search:

After one step, at most positions remain (in both the “left” and the “right” case). The runtime is monotone in (a longer array never takes less time; script footnote: provable by induction). So for :

where are constants. covers the work outside the recursive call: computing , the two comparisons, and so on. It does not matter whether this is 1, 2 or 5 operations.

Why is "constant " allowed? (lecture)

You only see at the end, after solving the recurrence, that the exact constant is irrelevant. It ends up as a factor in , which O-notation absorbs.

Telescoping (to guess):

Proof by induction (script) that for all , :

  • Base (, ): . ✓
  • Step (, ):

So . If is not a power of two, the result is the same; nothing interesting is added.

How fast is ?

: searching a million sorted elements takes about 20 steps. : a million times a million takes about 40 steps. Püschel: on a planet where search were more expensive, our digital world would not work as well.

3.5 Lower bound for sorted search: (Script §1.2.1)

Theorem

Every comparison-based search algorithm on a sorted array of length needs, in the worst case, at least comparisons. Binary search is therefore asymptotically optimal.

Proof idea: draw an arbitrary comparison-based algorithm as a decision tree, then count.

Example: decision tree of binary search for (lecture). Only element comparisons are counted; index arithmetic such as computing is left out.

                    [ A[2] = b ? ]
                   yes /      \ no
              return 2      [ b < A[2] ? ]
                           yes /       \ no
                   [ b = A[1] ? ]     [ b = A[3] ? ]
                   yes /   \ no       yes /   \ no
              return 1    NG       return 3    NG

(NG = nicht gefunden.) Worst case: 3 comparisons = height of this tree.

General argument. Take any comparison-based algorithm and its decision tree, with height .

  1. Worst-case comparisons . Each input produces one root-to-leaf path. The longest path has comparisons.
  2. Number of possible outputs : the indices , plus “nicht gefunden”. Each output needs at least one leaf, so
  3. A binary tree of height has at most leaves. Depth 1 gives at most 2 leaves, depth 2 at most 4, depth 3 at most 8, and in general each level at most doubles.
  4. Combining:

⚠ Transcript garble: the final line was transcribed as ” kleiner gleich größer gleich”. The correct conclusion is .

⚠ Lecture vs. script: the script counts nodes instead of leaves. A binary tree of height has at most nodes, and there are at least nodes, so

This is slightly weaker. Its footnote notes that counting leaves directly (as in the lecture) gives the sharper . Counting nodes is useful when, as in the script’s Fig. 1.1, a return can follow directly after a yes-answer at an inner level.

Information-theoretic view (script)

A comparison yields one bit of information. With comparisons, an algorithm can distinguish at most situations. Distinguishing outcomes needs . This generalises to the concept of entropy.

Exam Trap: the bound only holds for comparison-based algorithms

A student asked whether this rules out faster search. It does not rule out non-comparison methods such as hash tables. The argument needs every branching to be binary (yes/no). Püschel: “If you want to consider something else, keep studying.”

3.6 Insertion position via binary search (Script §1.2.2, Einsortierung)

Often we want to know where would have to be inserted to keep the array sorted. Binary search gives this as a side effect: it ends exactly where “would have been”.

Iterative version: if is not found, then at termination

so the insertion position is .

Why (script footnote): we left the loop, so . One checks that is never set beyond and never below . If occurs multiple times, there are several valid answers.

3.7 Checking sortedness (Script, start of Kap. 2)

Sorted(A[1..n])
    for i := 1 to n-1:
        if A[i] > A[i+1]: return false     // sortedness violated
    return true

iterations with one comparison each give . This is optimal: the check must look at every element.

3.8 Bubble Sort (Script §2.1)

Idea: modify the sortedness check. Whenever (a violation), repair it by swapping.

One pass alone does not sort. Lecture example , one pass:

Not sorted. But the largest element (7) is now at its correct place: once the largest element is reached, it wins every comparison and is swapped all the way to the right. It “bubbles up”, hence the name.

Fix: repeat the pass.

Bubble-Sort(A[1..n])
    for j := 1 to n-1:              // passes  (script: j = 1..n)
        for i := 1 to n-1:          // optimisation: i = 1..n-j suffices
            if A[i] > A[i+1]:
                swap A[i] and A[i+1]

Full lecture trace on :

PassStepsResult
swap 7,5; swap 7,1; swap 7,4
3,5 ok; swap 5,1; swap 5,4; 5,7 ok
swap 3,1; rest ok
nothing to do (by chance)

Optimisation (student remark). After pass the largest elements are in place, so the comparisons at the end are useless. The inner loop may run only up to . This does not change the asymptotics.

⚠ Lecture vs. script: the lecture’s outer loop runs ; the script’s runs , and a footnote notes that passes suffice. The script uses the example , sorted after 3 passes.

Runtime.

  • Comparisons: two nested loops become two nested sums:

    With the optimisation: , still .
  • Swaps: at most one per comparison, so . This is tight in the worst case: the reversed array needs swaps (script).

Beyond the lecture: exact swap count

Every adjacent swap of an out-of-order pair removes exactly one inversion (a pair with ). So Bubble Sort makes exactly as many swaps as the input has inversions, at most , attained by the reversed array.

Correctness via invariant (lecture and script):

: After passes of the outer loop, the largest elements are at their correct (final) positions.

Formally (script): , and every element left of position is .

  • Initialisation: is trivial (“the 0 largest elements are in place”).
  • Maintenance : the largest elements sit at the end, and nothing to their left is larger. So the test A[i] > A[i+1] never moves them, and no swaps happen for . Meanwhile, the largest element of the remaining part , i.e. the -largest overall, bubbles to position during pass .
  • Termination: says the largest elements are in place. Then the single remaining element must also be in place, so the array is sorted.

⚠ Script detail: the script writes the second condition with strict "". With duplicate values this is false in general. It must be "".

3.9 Selection Sort (Script §2.2): from invariant to algorithm

The key insight of this lecture: instead of starting from an algorithm and finding its invariant, start with an invariant and design the algorithm that maintains it. This works especially well for inductive algorithms.

Take the same invariant as Bubble Sort:

: the last positions contain the largest elements, sorted, at their final positions.

   A:  [   first n-j: unsorted rest    |  last j: sorted & final ]
       1                          n-j    n-j+1                  n
  • At the start, holds (the final part is empty).
  • If we can get from to , we eventually reach , which means sorted.

Progress by one (): the final part is perfect; nothing there may change. Which element must go to position ? The maximum of the unsorted part . Find it and swap it into position . This is Selection Sort.

Selection-Sort(A[1..n])                 // lecture version
    for j := 0 to n-2:
        k := index of the maximum of A[1..n-j]
        swap A[k] and A[n-j]

⚠ Lecture vs. script: the script writes for j := n downto 1: k := index of max in A[1..j]; swap A[k], A[j]. This is the same algorithm with the script’s equal to the lecture’s . The script’s last iteration () is trivial.

Lecture trace on ; ”|” marks where the invariant region begins:

Invariant beforeArrayMax of unsorted partAfter swap
7
5
4
3
sorted–

Starting from the invariant gives the correctness proof for free. Initialisation is trivial, each iteration establishes the next invariant by construction, and implies sortedness.

Finding the maximum (student question): the maximum of elements needs exactly comparisons. Assume the first element is the maximum, scan the rest, and update whenever something larger appears.

Runtime.

  • Swaps: iterations, one swap each, so . This is a big improvement over Bubble Sort’s .
  • Comparisons: iteration finds the maximum of elements with comparisons:

    (Tip from the lecture: when a sum like looks unfriendly, write it out: gives , gives , …, gives .)

Summary: comparisons, swaps, in place.

3.10 Insertion Sort (Script §2.3): a different invariant

Try a different invariant, this time at the front:

: the first elements are sorted, but not necessarily at their final positions (they need not be the smallest elements).

   A:  [ first j: sorted (not final) |   rest: untouched   ]
       1                          j    j+1                n
  • Initialisation: holds, since a single element is sorted by itself.
  • Termination: means all elements are sorted, hence correct.
  • Maintenance (student proposal): take the next element and insert it at the right place in the sorted prefix , shifting larger elements one step right.

Lecture example: prefix , next element :

Now the first 6 elements are sorted.

Finding the position cheaply. The prefix is sorted, so use binary search () instead of a linear scan (). Binary search returns the insertion position as a side effect (§3.6).

Insertion-Sort(A[1..n])                 // lecture version, corrected
    for j := 1 to n-1:
        k := insertion position of A[j+1] in A[1..j]   // binary search
        x := A[j+1]                     // remember it: shifting overwrites A[j+1]
        shift A[k..j] to A[k+1..j+1]
        A[k] := x

⚠ Discrepancies:

  1. Püschel said “this time from 1 to ” for the loop, but the body inserts , and his own analysis sums . The loop must run .
  2. The script indexes differently: for j := 2 to n, inserting into , with := smallest index in with . Edge case: if is larger than everything in the prefix, no such index exists, and simply stays at position (no shift).

Lecture trace on :

InvariantInsertResult
: 7 goes after 3
5 between 3 and 7
4 between 3 and 5; shift 5, 7
1 at the front; shift 4 elements (worst case)
–sorted

Runtime.

  • Comparisons: binary search on elements costs . Sum over iterations, then use that a sum of logs is the log of the product (school mathematics, middle-of-the-night knowledge):

    ( was on exercise sheet 2. The upper bound is immediate from .)
  • Moves: in the worst case (new element goes to the front, e.g. a reversed array) we shift elements and write , so moves:

Summary: comparisons (the first time we see this!) but moves. Both bounds are tight in the worst case (script). In place. Overall still quadratic.

Different invariant, different trade-off

  • Selection Sort (invariant at the back, final positions): few swaps, many comparisons.
  • Insertion Sort (invariant at the front, only sorted): few comparisons, many moves.

Both are still overall. To beat that, we need a different design principle.

3.11 Mergesort (Script §2.4): divide and conquer

Idea (“probably the most natural-looking”):

  1. Divide: split the array in the middle.
  2. Conquer: sort the left half and the right half recursively.
  3. Combine: merge (verschmelzen) the two sorted halves into one sorted array.
Mergesort(A, l, r)                      // sorts A[l..r]; initial call: l = 1, r = n
    if l < r:                           // at least 2 elements
        m := floor((l + r) / 2)
        Mergesort(A, l, m)              // sort left half
        Mergesort(A, m+1, r)            // sort right half
        Merge(A, l, m, r)               // merge both halves

All recursive calls only split. All the actual work happens in Merge.

Merge, key observation: the first (smallest) element of the result is either the first element of the left half or the first element of the right half, because both halves are sorted. Keep two pointers (left) and (right). Compare and , write the smaller one to the output, and advance that pointer.

Example (lecture): merge and :

  • 1 vs 2: take 1. 4 vs 2: take 2. 4 vs 3: take 3. 4 vs 5: take 4. 7 vs 5: take 5. 7 vs 8: take 7. 9 vs 8: take 8. Left rest: 9.
  • Result: .
Merge(A, l, m, r)                       // A[l..m] and A[m+1..r] are sorted
    B := new array (extra memory, same size as A[l..r])
    i := l          // first unused element, left half
    j := m + 1      // first unused element, right half
    k := l          // next position in B (script: k := 1, B indexed from 1)
    while i <= m and j <= r:            // neither half exhausted
        if A[i] < A[j]:
            B[k] := A[i]; i := i + 1; k := k + 1
        else:
            B[k] := A[j]; j := j + 1; k := k + 1
    copy the remaining elements of the left OR right half to B   // only one has a rest
    copy B back to A[l..r]

Why the “rest” step: when one pointer runs past its half, the other half may still hold elements. They are all larger than everything written so far and already sorted, so they are appended unchanged. Why copy back: Mergesort works on . Alternatively one could continue working with .

Example: recursion order (lecture),

First call: , , . The recursion always finishes the left part completely before touching the right part. The merges happen in this order:

Calls on single elements () do nothing. Püschel: the processing order of recursive algorithms is not always obvious, so trace it once by hand.

Correctness (script) by strong induction on the length :

  • Base: , a single element, already sorted.
  • Step: let . Then and , so both recursive ranges are strictly shorter than . By the induction hypothesis both halves are sorted correctly. If Merge correctly combines two sorted ranges, the result is correct.
  • Merge correctness: induction on . The invariant is: the output so far contains the smallest elements of in sorted order, and the remaining elements are exactly and .

⚠ Script detail: the script states the Merge invariant as ” contains the smallest elements”. Since is the next free position, it should be (the smallest). This is an off-by-one in the statement, not in the algorithm.

Runtime: recurrence. Let , and let count comparisons (or moves). Merge on elements costs at most : one linear pass, each element read once and written once (plus copying back).

Telescoping:

Induction proof of :

  • Base : . ✓
  • Step: . ✓

So , and in fact , for both comparisons and moves.

Alternative analysis by levels (script). The recursion has levels. On each level, every one of the numbers takes part in exactly one Merge. Each comparison places at least one number for good, so there are comparisons per level. There are exactly copy operations per level (write to , copy back). Total:

Extra memory: needs cells. Mergesort is not in place. In-place merging fails because the two pointers advance irregularly: writing into directly would overwrite elements still needed. (The script notes that reducing Mergesort’s extra memory is possible but much more complicated.) Püschel ironically said “glücklicherweise” (fortunately) about the extra space. It is of course a disadvantage: a billion extra cells for a billion numbers is expensive.


4. Worked Examples & Applications

Example: overview of the sorting algorithms so far (Script Table 2.1, restricted to what was covered)

AlgorithmComparisonsMovesExtra spaceDesign idea
Bubble Sortrepair violations of the sortedness check
Selection Sortinvariant at the back: select the max
Insertion Sortinvariant at the front: insert the next element
Mergesortdivide and conquer, merge

Example: decision tree of binary search, (Script Fig. 1.1)

Root: ? If yes, return 3. If no: ?

  • If yes: ? If yes, return 1; otherwise return 2.
  • If no: ? If yes, return 5; otherwise ? If yes, return 4; otherwise return 6.

Height 4, so at most 4 comparisons for 6 elements. (The figure omits explicit “not found” leaves for brevity.)

Exam Trap: index offsets in recursive binary search

Calling binary search on A[m+1..n] returns a position inside the subarray. Forgetting to add gives wrong indices. In the iterative version with the problem does not arise.

Exam Trap: "Insertion Sort is "

Only its comparisons are (with binary search). The shifts cost in the worst case, so the total runtime is . Always state which operation you are counting.

Exam Trap: Selection Sort's swaps vs. comparisons

swaps but comparisons: still quadratic. Even on an already sorted array, Selection Sort performs all comparisons.

Exam Trap: invariant must imply correctness at the end

Insertion Sort’s invariant (“prefix sorted”) only implies correctness at , when the prefix is the whole array. Bubble/Selection Sort’s invariant (“largest elements are final”) already implies correctness at . Know which index you end at.

Exam Trap: floor in the middle index

Mergesort uses . Both recursive ranges, and , are non-empty and strictly shorter whenever . This is exactly what the correctness induction needs. With and the recursion could fail to shrink (e.g. gives , and the second call would be on again).


5. Intuition & Conceptual Bridges

5.1 Bridge back: inductive Maximum Subarray Sum (⚠ reconstruction)

Püschel’s recap says the inductive approach gave , built by “assuming the problem is solved up to here and making progress by one”, i.e. an invariant. The standard version:

Invariant after processing :

  • = maximum subarray sum within (or 0),
  • = maximum sum of a subarray ending at position (or 0).
MaxSubarrayInductive(a_1, ..., a_n)     // reconstruction
    best := 0; suffix := 0
    for j := 1 to n:
        suffix := max(0, suffix + a_j)   // best subarray ending at j (or empty)
        best   := max(best, suffix)
    return best

Step : the best subarray ending at either extends the best one ending at , or is empty. Cost: per step, so overall. This is the same “invariant → inductive algorithm” pattern as Selection and Insertion Sort.

5.2 Intuition

Intuition: binary search halves the uncertainty

Each comparison can at best halve the set of remaining candidate answers. With possible answers, about halvings are needed. Binary search actually achieves this halving, which is why it matches the lower bound.

Intuition: why Mergesort beats the quadratic algorithms

The algorithms make progress by one element per linear-time step. Mergesort does work per level, but there are only levels, because the problem halves each time. Halving shows up again: halving gives levels, as with binary search and Karatsuba.

Intuition: Mergesort vs. Insertion Sort

Insertion Sort inserts one element into a sorted list, which costs up to moves. Merge combines two sorted lists of length at once in linear time. Merging in bulk is far cheaper than inserting one by one.

5.3 Connections

  • Back: recurrences and telescoping (weeks 1–2); is like Karatsuba’s recurrence but with one subproblem; is the same recurrence as reconstructed for divide-and-conquer Maximum Subarray Sum. Exercise sheet 2’s is used for Insertion Sort. Induction (week 1) underlies invariants and Mergesort’s correctness.
  • Forward (script §§2.5–2.7, next week):
    • Quicksort: divide and conquer “in reverse” (partition first, then recurse), in place, in good cases, worst case.
    • Heapsort: Selection Sort with a heap to find the maximum in : and in place.
    • Lower bound for comparison-based sorting: , with the same decision-tree argument as §3.5 but with leaves (one per permutation). So Mergesort is asymptotically optimal.
    • Bucketsort: non-comparison-based, for restricted inputs.

6. Summary & Self-Assessment Questions

Cheat Table

TopicKey fact
Search (Def. 1.1)find with or “nicht gefunden”; elementary op = comparison
Linear search; optimal (exactly comparisons needed)
Unsorted lower boundevery element in some comparison, 2 items per comparison:
Binary search; , so
Sorted lower bound outputs, leaves, so
Insertion positioniterative binary search ends with , ; insert at
;
Sorting (Def. 2.1)permutation with for ; ops: comparisons, swaps/moves
In place extra memory
Sortedness check, optimal
Invariant proofinitialisation, maintenance (induction step), termination implies correctness
Bubble Sortinvariant: largest at the end; comparisons, swaps
Selection Sortsame invariant, swap the max into place; comparisons, swaps
Insertion Sortinvariant: prefix sorted; binary search gives comparisons; shifts
Mergesort, so ; extra space
Mergetwo pointers, take the smaller, append the rest, copy back;

Self-Assessment Questions

Question 1. Run the iterative binary search (script version) on
for (a) and (b) . List in each step. For (b), give the insertion position and verify .

Question 2. Using the decision-tree argument, show that every comparison-based algorithm searching a sorted array of elements needs at least 10 comparisons in the worst case. Why does the argument not apply to hash tables?

Question 3. Sort with (a) Bubble Sort (lecture version, inner loop , and also with the optimisation), (b) Selection Sort, (c) Insertion Sort. Count comparisons and swaps/shifts for each.

Question 4. Prove by induction that the Mergesort recurrence , (for ) has the closed form . Then explain why .

Question 5. Design from an invariant. Take the invariant “after iterations, contains the smallest elements of , sorted, at their final positions”. (a) Derive an algorithm that maintains it. (b) Give its comparison and swap counts. (c) How does it differ from Insertion Sort’s invariant?