Hints — Module 01: The Notion of an Algorithm
Graduated hints, three per stage. Reach for hint 1 first; drop to 3 only when stuck.
Stage 1: Euclid's algorithm, step by step
- The whole method rests on one fact: if
r = m mod n, then the common divisors of(m, n)are exactly the common divisors of(n, r), sogcd(m, n) = gcd(n, r). The remainder strictly shrinks, so the process must stop — that is the termination (finiteness) argument. - Follow Knuth's three steps literally as a
loop: E1 forms the remainder, E2 tests it against zero, E3 shiftsnintomandrinton. Don't reach for recursion or awhile r != 0rewrite yet — mirror the control flow. Remember Algorithm E is stated for positive integers only, so assertm > 0 && n > 0. let r = m % n; if r == 0 { return n; } m = n; n = r;insideloop {}. The answer is the last nonzero remainder, which is the currentnwhenrfirst hits zero.
Stage 2: Avoiding trivial replacements
- Step E3's
m <- n; n <- ris pure data shuffling that computes nothing. The observation (exercise 1.1-3) is that you can let the two variables swap roles every half-turn instead of copying values between them. - Unroll one iteration into two: reduce
mmodulonin place, test for zero; then reducenmodulomin place, test for zero. The loop body now has two remainder steps and two zero-tests, and never assigns one variable's value into another. m %= n; if m == 0 { return n; } n %= m; if n == 0 { return m; }insideloop {}. Note the answer is whichever variable is nonzero when the other becomes zero. Cross-check againsteuclid_eon a small grid to convince yourself they agree.
Stage 3: Extended Euclid: certifying the gcd
- Alongside the remainders, carry Bézout coefficients so that at every moment you know integers making
a*m + b*nequal to the current value. Then the final gcd arrives already certified by the identitya*m + b*n = gcd(m, n). - Maintain two rows of the invariant simultaneously:
(a1, b1, c)for the larger value and(a, b, d)for the smaller, witha1*m + b1*n = canda*m + b*n = d. Each division step recyclesc <- d,d <- r, and updates the coefficient rows by exactly the same recurrence with the quotientq. Use a signed wide type (i128) for the coefficients — they go negative. - Start
(a1,b1)=(1,0),(a,b)=(0,1),(c,d)=(m,n). Each step:q = c/d,r = c%d; ifr==0return(d, a, b); elsec=d; d=r;and set the new(a,b)to(a1 - q*a, b1 - q*b)while(a1,b1)takes the old(a,b). Return the gcd asu64but the coefficients asi128.
Stage 4: Counting divisions; Lamé's worst case
- The running time of Algorithm E is governed by how many division steps (executions of E1) it performs. Lamé's theorem says the worst case for numbers below a bound comes from consecutive Fibonacci numbers, because they force every quotient down to 1 — the slowest possible descent.
- Write a counter version of Euclid that returns the number of times the remainder step ran instead of the gcd. Then feed it consecutive Fibonacci pairs
(F_{k+1}, F_k)and observe the step count is exactlyk - 1. - Clone the Stage-1 loop but accumulate
let mut t = 0; ... t += 1;on each remainder, returningtwhenr == 0. For the worst-case check, generate Fibonacci numbers with a rolling pair(fk, fk1)and assertdivision_steps(fk1, fk) == k - 1.