Sign in

Libre University uses your GitHub account. Signing in is only needed to sit a final test, so the score is kept on your profile.

How cost grows

An exact step count is machine-specific and unusable, so the previous lesson ended by throwing away everything except the shape of the growth, and this lesson makes "shape" a definition rather than a gesture.

What the definition has to do

The claim to be formalised is "this cost grows like n2". Three properties are wanted from it.

It must ignore constant factors, because the constants come from the hardware and the compiler and cannot be defended. It must ignore lower-order terms, because at the sizes where cost matters they contribute a vanishing fraction. And it must be provable, so that a claim can be settled rather than argued about.

The standard device does all three at once. Instead of asking whether two functions are equal, ask whether one is eventually bounded by a multiple of the other. "Eventually" throws away small n, which is where lower-order terms live. "A multiple of" throws away the constant. And both are witnessed by numbers you can write down, which makes the claim checkable.

The notation is Paul Bachmann's, from 1894, popularised in analysis by Edmund Landau and brought into computing by Donald Knuth in a 1976 note that fixed the definitions used here.

Big O: an upper bound

Let f(n) and g(n) be functions from positive integers to non-negative reals. Then

f(n)=O(g(n))c>0,n0>0 such that f(n)cg(n) for all nn0

The pair (c,n0) is called a witness. To prove a big-O claim you produce one; to refute a claim you show no pair can exist. Nothing else is involved, and in particular no limits are needed.

The equals sign is an abuse of notation that everyone commits and nobody defends. O(g) is really a set of functions, and f=O(g) means fO(g). The abuse is harmless as long as you never read it symmetrically: n=O(n2) is true and O(n2)=n is nonsense.

Example. Show that 3n2+5n+100=O(n2) by producing a witness.

The strategy is to bound each term by a multiple of n2. For the middle term, 5n0.5n2 whenever n10. For the constant, 100n2 whenever n10. So for all n10,

3n2+5n+1003n2+0.5n2+n2=4.5n2

The witness is c=4.5, n0=10. Check it at the boundary: f(10)=300+50+100=450 and 4.5×100=450, so the inequality holds with equality there, and at n=9 it fails (388>364.5), which is why n0 could not be smaller for this particular c. Witnesses are never unique: c=108, n0=1 works too, and is just as valid a proof.

Now you. Produce a witness showing 2n3+7n2+3=O(n3).

Answer

Bound each term: 7n2n3 when n7, and 3n3 when n2. So for n7, 2n3+7n2+32n3+n3+n3=4n3, giving the witness c=4, n0=7. Checking at n=7: 686+343+3=10324×343=1372. The bound in fact holds from n=5 onward, which is fine: a witness only has to work, not to be the smallest.

Omega and Theta: the other two bounds

Big O alone is a weak statement, because it is an upper bound and nothing stops it being loose. Every algorithm in this course is O(2n), and saying so tells you nothing. Two companions fix that.

f(n)=Ω(g(n)) if there are c>0 and n0 with f(n)cg(n) for all nn0: a lower bound, used to say an algorithm cannot do better than something, and used for problems rather than algorithms when proving that no algorithm can.

f(n)=Θ(g(n)) if both hold: there are c1,c2>0 and n0 with c1g(n)f(n)c2g(n) for nn0. This is the tight statement, and it is what "grows like" actually means.

So 3n2+5n+100 is Θ(n2): the upper witness is above, and for the lower, 3n2+5n+1003n2 for every n1, giving c1=3, n0=1. It is also O(n3), truthfully but uselessly, and it is not Θ(n3), because no c1>0 has 3n2+5n+100c1n3 eventually: divide by n3 and the left side goes to zero while c1 does not.

Refutations work the same way, by contradiction on the witness. Is n2=O(n)? Suppose a witness (c,n0) existed, so n2cn for all nn0. Divide by n: nc for all nn0. But n is unbounded, so take n=max(c,n0)+1 and the inequality fails. No witness exists.

Prefer Θ wherever you can prove it. Most published results are stated with O out of caution, since an upper bound is what a guarantee needs, but when a source says an algorithm is O(nlogn) and you want to know whether it might secretly be linear, the answer is usually that the author could have written Θ and chose the weaker word.

The hierarchy, priced

Definitions settle arguments; a table shows why anyone cares. Assume a machine performing 109 steps per second, which is the right order of magnitude for a single core running simple loop code.

costn=100n=1000n=106
log2n6.6 ns10.0 ns19.9 ns
n100 ns1.0 µs1.0 ms
nlog2n664 ns10.0 µs19.9 ms
n210.0 µs1.00 ms16.7 minutes
n31.00 ms1.00 s31.7 years
2n4×1013 yearsbeyond writingbeyond writing

Read the columns rather than the rows. At n=100 every one of the polynomial costs is imperceptible, and choosing between them is a waste of a morning. At n=106 the same choices are the difference between an instant response, a coffee break and a career.

The entry worth staring at is 2n. At n=50 an exponential algorithm running a billion steps a second takes thirteen days; at n=60 it takes 36.5 years. Ten more items, a factor of a thousand. Buying a machine a thousand times faster buys you ten more items. That is why the last lesson of this course treats exponential cost as a qualitative barrier rather than a large number.

Example. A quadratic algorithm costing exactly n2 steps handles n=104 in 0.1 seconds on a given machine. A colleague proposes running it on n=106. How long will that take, and would a machine 100 times faster fix it?

Cost scales as n2, and n grew by a factor of 100, so the work grows by 1002=104. The time becomes 0.1×104=1000 seconds, about 17 minutes. A machine 100 times faster brings that to 10 seconds, which is a genuine improvement, but note what the same money buys on the input side: at fixed time budget, a machine 100 times faster handles only 10 times the input, since 100=10. Hardware buys you a square root; a better algorithm buys you a different exponent.

Now you. A cubic algorithm takes 1.0 second on n=1000. How long does it take on n=5000?

Answer

The input grew by a factor of 5, and cost grows as n3, so the work grows by 53=125. The time is 125 seconds, about two minutes. Going to n=105 instead would be a factor of 1003=106, or about 11.6 days.

Combining bounds

Four rules cover almost every analysis you will do, and each follows from the definition in a line or two.

Sums take the maximum. If f1=O(g1) and f2=O(g2) then f1+f2=O(max(g1,g2)). Two loops one after the other, one linear and one quadratic, cost O(n2) in total. This is why the dominant term is the only one written.

Products multiply. A loop running n times whose body costs O(n) costs O(n2). A loop running n times whose body costs O(logn) costs O(nlogn), which is the shape of "sort by inserting each element into a balanced tree".

Constants disappear. O(3n)=O(n), and logbn=log2n/log2b, so the base of a logarithm is a constant factor and vanishes: O(logn) needs no base. Note that this is not true in an exponent, where 2n and 3n differ by 1.5n, which is not a constant.

Nested loops are not automatically n2. A loop where the inner bound depends on the outer index needs the sum evaluated. The nested duplicate check of the previous lesson runs its inner loop n-1-i times, summing to n(n-1)/2=Θ(n2), whereas a loop that halves a counter each pass runs Θ(logn) times regardless of the outer loop.

Example. A function loops i from 1 to n, and for each i it loops j from 1 to i, doing constant work in the body. Then it makes one pass over the array doing constant work. What is the total cost, tightly?

The double loop performs i=1ni=n(n+1)/2 constant-cost iterations, which is Θ(n2). The single pass is Θ(n). By the sum rule the total is Θ(max(n2,n))=Θ(n2), and the Θ is justified because the double loop is bounded both above and below by multiples of n2: n(n+1)/2n2/2 and n2.

Now you. A function loops i from 1 to n, and for each i it repeatedly halves a counter starting at n until it reaches 1, doing constant work each halving. What is the cost?

Answer

The inner loop runs log2n+1 times, which is Θ(logn) and does not depend on i. By the product rule the total is Θ(nlogn). The common mistake is to call it Θ(n2) because two loops are nested; what matters is how many times each runs, not how deeply they sit.

Four ways this misleads

The notation is a tool for discarding information, and it is worth being explicit about what gets discarded, because every one of these has cost somebody a week.

Constants matter at real sizes. Suppose insertion sort costs n2/4 steps and mergesort costs 8nlog2n, which are plausible ratios once the recursion, the allocation and the copying in mergesort are counted. Setting them equal gives n=32log2n, solved by n=256. Below 256 elements the "worse" algorithm wins, and at n=50 it wins by a factor of 3.6. This is not a curiosity: real sort implementations switch to insertion sort on small blocks for exactly this reason, and Timsort, the sort in Python and in Java's Arrays.sort for objects, chooses a minimum run length between 32 and 64 and insertion-sorts anything shorter.

O is an upper bound, and people read it as tight. "Quicksort is O(n2)" and "quicksort is O(nlogn) on average" are both true statements about the same algorithm, and a reader who does not notice which bound is being quoted will draw the wrong conclusion. Worse is the common phrase "at least O(n)", which is meaningless: O is already an upper bound, so "at least an upper bound" says nothing. The word wanted there is Ω.

Asymptotic means eventually, and eventually can be absurd. Strassen's 1969 matrix multiplication algorithm costs Θ(nlog27)=Θ(n2.807) against the naive Θ(n3), and it does win on real hardware, with measured crossovers in tuned implementations somewhere from a few dozen to a few hundred. The descendants of Coppersmith and Winograd's method go much further, down to an exponent of about 2.372 in the current record, and none of them beats anything at any size a computer will ever be given, because their constants are astronomical. Such results are called galactic algorithms, and they are genuine mathematics with no engineering content.

Steps are not the only resource. The count says nothing about memory, and mergesort's extra array is the reason quicksort survives. It says nothing about cache behaviour, and a Θ(n) walk over a linked list scattered through memory can lose to a Θ(nlogn) pass over a contiguous array. It says nothing about the cost of the operations themselves, and every claim so far has quietly assumed that comparing two elements and reading a[i] cost the same fixed amount.

That last assumption is the one to attack next. Whether reading an element costs a constant depends entirely on how the elements are arranged in memory, and the two arrangements available, one block or a chain of separately allocated cells, give completely different cost tables for exactly the same operations. The next lesson starts with the block.