proving correctness of algorithms
Inspection of algorithms that produce the correct behavior over a broad range of inputs is very important. We may run our algo for some sample input values to verify correctness, but this only ensures this is true for the values that are tested (not all possible inputs).
Verifying an algorithm
Empirical analysis cannot assure the correctness for all range of inputs, this would be sampling for different types of input.
For example: To prove that a loop executes correctly, we want to show it works for
the first iteration, then for the second, etc. We also want to show the loop terminates
when it is supposed to (isn't infinite). This is similar to mathematical induction
.
- We will prove using induction that a "loop invariant", a property of the loop in the algo below, is always true
- We will show that the algorithm terminates
Loop invariant
The term invariant
is "never changing or something that is always true".
For loops this would be something that's the same for every iteration of the loop.
The loop invariant here is max
, which always holds the maximum number among the first i
elements.
def findMax(list):
max = list[0]
for i from 0 to len(list)-1:
if list[i] > max:
max = list[i]
return max
Proof of correctness
Steps to determine the proof of correctness with loop invariants are described below. These are only inclusive of loop invariants.
Steps
- Identify the loop invariant
- Use induction to prove that the loop invariant holds true. For this, we need to prove
the following conditions:
1. Initialization (Base Case) - The loop invariant is satisfied at the beginning of the for loop
1. Maintenance (Inductive Case) - Assume that the loop invariant is true for the ith
iteration,
then show that the loop invariant will be true before the (i + 1)th
iteration
1. Termination - When the loop terminates, the invariant gives us a useful property that
helps show that the algorithm is correct
Example proving a recursive algorithm
Proving recursive invariants is similar to loop invariants. We'll use strong induction because it's easier for recursive structures at all levels.
This example computes 3^n
for a non-negative integer n
using recursion:
ThreePower(n):
if n = 0:
return 1
else if n mod 2 = 0: (i.e. n is even)
x = ThreePower(n / 2)
return x * x
else: (if n is odd)
x = ThreePower((n - 1) / 2)
return 3 * x * x
Cases
- Base case - It is similar to what we have seen before:
- We consider that the recursive invariant holds true for the base case of the recursive algorithm
- Inductive case - We assume that:
- The recursive invariant is true for the
ith
execution - We also assume that is truef or all values less than
ith
(previous iterations),
- Then we want to show that the invariant is going to be true for the
(i + 1)th
execution
- The recursive invariant is true for the
- Termination - When execution terminates, the invariant gives us a useful property
Proof
- Recursive invariant - At each recursive call,
ThreePower(k)
returns3^k
- Initialization (Base case) - When
k = 0
,ThreePower(k)
returns1 = 3^0
which is correct - Maintenance (Inductive case):
- We can divide this into 2 cases:
k
is even,k
is odd- When
k
is even - Algorithm setsx
toThreePower(k/2)
, which by recursion invariant returns3^(k/2)
- The algorithm returns
x * x
, which is3^(k/2) = 3^k
- The algorithm returns
- When
k
is odd - Algorithm setsx
toThreePower((k - 1)/2))
, which by recursion invariant returns3^(k-1)/2
- The algorithm returns
3 * x * x
which is3 * 3^(k-1)/2*3^(k-1)/2 = 3^k
- The algorithm returns
- When
- We can divide this into 2 cases:
- Termination - At the top level of the recursive call,
ThreePower(n)
gives3^n
, the solution to the problem