An easy first example.
nat sum(List x) { if (x.empty?) return 0 else return x.first + sum(x.rest) }
What is the appropriate size measure? — length of x.
T(n) = "steps" to compute sum(x), where n=|x|
To solve, unroll into a summation: T(n) = &sumi=0…n 1 = n+1 = O(n)
To solve, unroll into a summation: T(n) = &sumi=0…n i = n(n+1)/2 = O(n2)
To solve, unroll into a summation: T(n) = &sumi=0…n 2i = 2 &sumi=0…n i = 2n(n+1)/2 = O(n2)
List merge(List x, List y) { if (x.empty?) return y if (x.first < y.first) return cons(x.first, merge(x.rest, y)) else return cons(y.first, merge(x, y.rest)) }
First attempt at a recurrence:
A bit tricky to solve. (Although we could eventually find that T(m,n) = O(m+n), not surprisingly.)
For this and all other examples, could guess the closed form solution (or its bound), and prove the guess correct inductively. However, we're currently interested in finding such closed forms without guessing.
Second attempt: A unary T(n) would be easier to solve. Does it make sense here? What is appropriate measure of input size?
To solve, assume worst case, which then looks just like previous example.
When is this unrolling into a sum easy? General form:
Assuming the base case happens when n≤k, for some constant k, which is almost always the case, can simply omit writing the base case.
List mergesort(List x) { if (x.empty? or x.rest.empty?) return x else y,z = split x into halves return merge(mergesort(y), mergesort(z)) }
If |x| isn't a power of two, eventually get an uneven split. Let's temporarily ignore this.
To solve…
Unroll recurrences equationally.
T(n)
= 2n + 2T(n/2)
= 2n + 2(2 n/2 + 2T(n/4))
= 2n + 2n + 4T(n/4)
= 2n + 2n + 4(2 n/4 + 2T(n/8))
= 2n + 2n + 2n + 8T(n/8)
= …
= 2n × #unrolls + 2#unrolls × T(1)
= 2n × #unrolls + 2#unrolls
How many times can we unroll? I.e., how many times can we divide n by 2? — log2 n
T(n)
= 2n log2 n +
2log2 n
= 2n log2 n +
n
= O(n log2 n)
Unroll recurrences as a tree. (Same info, different format.)
T(n) T(n/2) T(n/2) =2(n/2)+2T(n/4) ... T(2) T(2) ... T(2) T(2) =2(2)+2T(1) =8 T(1) T(1) T(1) T(1) ... T(1) T(1) T(1) T(1) =1
Prove inductively, T(n) ≤ C × (2n log n), for some C>0
Could have written the following
Gives, within a constant factor, the same result.
What if n isn't a power of two?
Rather than solving directly, observe that more input never takes less time.
This idea can be generalized. Rule of thumb: floors and ceilings in recurrences can usually be safely ignored.
The logarithm base doesn't matter (if constant): O(log2 n) = O(log4 n) = O(log10 n) = …
Need to prove this.
loga n / logb n = ? — loga b — a constant
Asymptotes ignore constant factors, so can ignore constant bases.
Solve by unrolling.
For f(n) = a⋅f(n/b) + c⋅nd,
We're skipping the proof, but it follows our examples. Unroll the recurrences into a sum, and solve. Each case allows the sum to be bounded in a different way.
Apply to mergesort and binary search.
(The full Master Theorem generalizes the c⋅nd part. See COMP 482 for the general form and proof.)
Closed form: T(n) = 10n
Previously, proved Fn = (an - bn)/√5, where a = (1+√5)/2, b = (1-√5)/2, but how to derive that?
Try different initial conditions, and see how much series varies.
Approach to recurrences of the form f(n) = b⋅f(n-1) + c⋅f(n-2):
Guess a sol'n of the form f(n)=αn.
What is α? Well, we get α2 - b⋅α - c = 0 (the characteristic polynomial).
(Note: If started with f(n-3) term, we'd have a cubic characteristic polynomial, etc.)
Compute solutions α1, α2 to the characteristic polynomial. Here, use the quadratic equation.
Verify these answers satisfy the recurrence, temporarily ignoring the initial conditions.
But what about the initial conditions? Hmm, don't work.
Sol'ns to the recurrence relation (w/o the initial conditions) are closed under (a) sums, (b) constant factors. If α1n and α2n are sol'ns, then so are j⋅α1n + k⋅α2n, for any j,k ∈ ℝ.
(That is, "a linear combo. of two sol'ns is also a sol'n", or equivalently, "the set of solutions form a vector space".)
Solve for j,k.
Are we done? Are there more solutions that aren't of the form αn? At this point, we ask the mathematicians, and trust us when they give us the answer we want: yes, this is all solutions.
This technique generalizes: