Strong (or course-of-values) induction is an easier proof technique than ordinary induction because you get to make a stronger assumption in the inductive step. In that step, you are to prove that the proposition holds for k+1 assuming that that it holds for all numbers from 0 up to k. This stronger assumption is especially useful for showing that many recursive algorithms work.
The recipe for strong induction is as follows:
For example, consider a binary search algorithm that searches efficiently for an element contained in a sorted array. We might implement this algorithm recursively as follows:
/** Return an index of x in a.
* Requires: a is sorted in ascending order, and x is found in the array a
* somewhere between indices left and right.
*/
int binsrch(int x, int[] a, int left, int right) {
int m = (left+right)/2;
if (x == a[m]) return m;
if (x < a[m])
return find(x, a, l, m−1)
else
return find(x, a, m+1, r);
}
Because this code is tail-recursive, we can also transform it into
iterative code straightforwardly:
int binsrch(int x, int[] a, int left, int right) { while (true) { int m = (left+right)/2; if (x == a[m]) return m; if (x < a[m]) r = m−1; else l = m+1; } }
Binary search is efficient and easy to understand, but it is also famously easy to implement incorrectly. So it is a good example of code for which we want to think carefully about whether it works. Just testing it may well miss cases in which it does not work correctly.
We can prove either piece of code correct by induction, but it is arguably
simpler to think about the recursive version. The problem with convincing
ourselves that binsrch
works is that it uses itself, so the
argument becomes circular if we're not careful. The key observation is that
binsrch
works in a divide-and-conquer fashion, calling itself only
on arguments that are “smaller” in some way. In what way do the
arguments become smaller? The difference between the parameters right
and left
becomes smaller in the recursive call. This is then the
variable we should choose to construct our inductive argument. Now we can
follow the strong induction recipe.
Let P(n) be the assertion that binsrch
works correctly for
inputs where right−left = n. If we can prove that P(n) is true for all n, then we know that binsrch
works on all possible arguments.
Inductive Step. We assume that binsrch
works as
long as left−right ≤ k. Our goal is to prove that it works on an input
where left−right = k + 1. There are three cases, where
x = a[m], where x < a[m] and where x > a[m].
binsrch
(and its iterative variant) are correct!
Notice that if we had made a mistake coding the x > a[m] case,
and passed m
as left
instead of m+1
(easy to do!), the proof we just constructed would have failed in that
case. And in fact, the algorithm could go into an infinite loop when
right = left + 1. This shows the value of careful inductive reasoning.