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 ". 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 , 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 and be functions from positive integers to non-negative reals. Then
The pair 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. is really a set of functions, and means . The abuse is harmless as long as you never read it symmetrically: is true and is nonsense.
Example. Show that by producing a witness.
The strategy is to bound each term by a multiple of . For the middle term, whenever . For the constant, whenever . So for all ,
The witness is , . Check it at the boundary: and , so the inequality holds with equality there, and at it fails (), which is why could not be smaller for this particular . Witnesses are never unique: , works too, and is just as valid a proof.
Now you. Produce a witness showing .
Answer
Bound each term: when , and when . So for , , giving the witness , . Checking at : . The bound in fact holds from 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 , and saying so tells you nothing. Two companions fix that.
if there are and with for all : 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.
if both hold: there are and with for . This is the tight statement, and it is what "grows like" actually means.
So is : the upper witness is above, and for the lower, for every , giving , . It is also , truthfully but uselessly, and it is not , because no has eventually: divide by and the left side goes to zero while does not.
Refutations work the same way, by contradiction on the witness. Is ? Suppose a witness existed, so for all . Divide by : for all . But is unbounded, so take and the inequality fails. No witness exists.
Prefer wherever you can prove it. Most published results are stated with out of caution, since an upper bound is what a guarantee needs, but when a source says an algorithm is 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 steps per second, which is the right order of magnitude for a single core running simple loop code.
| cost | |||
|---|---|---|---|
| 6.6 ns | 10.0 ns | 19.9 ns | |
| 100 ns | 1.0 µs | 1.0 ms | |
| 664 ns | 10.0 µs | 19.9 ms | |
| 10.0 µs | 1.00 ms | 16.7 minutes | |
| 1.00 ms | 1.00 s | 31.7 years | |
| years | beyond writing | beyond writing |
Read the columns rather than the rows. At every one of the polynomial costs is imperceptible, and choosing between them is a waste of a morning. At the same choices are the difference between an instant response, a coffee break and a career.
The entry worth staring at is . At an exponential algorithm running a billion steps a second takes thirteen days; at 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 steps handles in 0.1 seconds on a given machine. A colleague proposes running it on . How long will that take, and would a machine 100 times faster fix it?
Cost scales as , and grew by a factor of 100, so the work grows by . The time becomes 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 . Hardware buys you a square root; a better algorithm buys you a different exponent.
Now you. A cubic algorithm takes 1.0 second on . How long does it take on ?
Answer
The input grew by a factor of 5, and cost grows as , so the work grows by . The time is seconds, about two minutes. Going to instead would be a factor of , 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 and then . Two loops one after the other, one linear and one quadratic, cost in total. This is why the dominant term is the only one written.
Products multiply. A loop running times whose body costs costs . A loop running times whose body costs costs , which is the shape of "sort by inserting each element into a balanced tree".
Constants disappear. , and , so the base of a logarithm is a constant factor and vanishes: needs no base. Note that this is not true in an exponent, where and differ by , which is not a constant.
Nested loops are not automatically . 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 times, summing to , whereas a loop that halves a counter each pass runs times regardless of the outer loop.
Example. A function loops from 1 to , and for each it loops from 1 to , 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 constant-cost iterations, which is . The single pass is . By the sum rule the total is , and the is justified because the double loop is bounded both above and below by multiples of : and .
Now you. A function loops from 1 to , and for each it repeatedly halves a counter starting at until it reaches 1, doing constant work each halving. What is the cost?
Answer
The inner loop runs times, which is and does not depend on . By the product rule the total is . The common mistake is to call it 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 steps and mergesort costs , which are plausible ratios once the recursion, the allocation and the copying in mergesort are counted. Setting them equal gives , solved by . Below 256 elements the "worse" algorithm wins, and at 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.
is an upper bound, and people read it as tight. "Quicksort is " and "quicksort is 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 ", which is meaningless: 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 against the naive , 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 walk over a linked list scattered through memory can lose to a 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.