Part I · Foundations Chapter 3

Characterizing Running Times

The five asymptotic notations, stated precisely, plus the standard functions and identities that every later analysis assumes you can manipulate without looking them up.

Chapter 2 said “drop the lower-order terms and the constant” and got away with it. Chapter 3 makes that legitimate. It defines O, Ω, Θ, o, and ω as sets of functions, proves the relationship between them, explains the notational abuses everyone commits, and then collects the standard functions — logarithms, exponentials, factorials, the iterated logarithm — with the identities you need to compare them. It is the most notation-dense chapter in Part I and the one whose contents you will use most often without noticing.

4th edition note. The chapter was renamed from “Growth of Functions” and restructured: §3.1 now gives an informal, intuition-first pass at the three main notations before §3.2 states the formal definitions. The mathematics is unchanged. The 4th edition also adds a careful discussion of what it means to say an algorithm’s running time is O(n²) as opposed to saying a function is.

Contents

  1. What the notation is for
  2. Θ-notation: asymptotically tight
  3. O-notation: upper bound
  4. Ω-notation: lower bound
  5. The three pictures
  6. Theorem 3.1, and how to use it
  7. o and ω: the strict versions
  8. Notational abuse, and why it is allowed
  9. Comparing functions
  10. Bounding a running time, not a function
  11. Standard functions and identities
  12. The growth hierarchy
  13. Recap

What the notation is for

The running time of insertion sort came out as an² + bn + c, where a, b, and c were sums and differences of eight unknown machine constants. Nobody can use that expression, and nobody should want to: the constants depend on the compiler, the processor, and the mood of the branch predictor. What survives all of that is the .

Asymptotic notation describes how a function behaves as its argument grows without bound, discarding constant factors and lower-order terms. It is the coarsest description of a running time that is still useful, which is exactly what makes it portable across machines.

Two cautions the chapter is explicit about. Asymptotic statements say nothing about small inputs — an algorithm that is Θ(n lg n) can lose badly to a Θ(n²) one below some threshold, as Chapter 1’s table showed. And the notations are defined for functions whose domain is the natural numbers, since running times are counts; when we write Θ(n²) for a function of a real variable, we mean the restriction to integers.

Θ-notation: asymptotically tight

Start with the strongest of the three, because the other two are halves of it. The key move is that Θ(g(n)) is a set of functions, not a number.

Θ(g(n)) = { f(n) : there exist positive constants c₁, c₂, and n₀ such that 0 ≤ c₁g(n) ≤ f(n) ≤ c₂g(n) for all n ≥ n₀ }

Read it as: past some point n₀, the function f is trapped between two constant multiples of g. We say g(n) is an asymptotically tight bound for f(n).

Three details in that definition earn their place:

Worked example: ½n² - 3n = Θ(n²)

This is the book’s own example and it is the one to be able to reproduce. We must find c₁, c₂, n₀ with

c₁n² ≤ ½n² - 3n ≤ c₂n² for all n ≥ n₀

Divide through by , which is positive, so the inequalities are preserved:

c₁ ≤ ½ - 3/n ≤ c₂

The middle expression increases toward ½ as n grows. So:

So c₁ = 1/14, c₂ = ½, n₀ = 7 verify the claim. Other choices work too; the definition only demands that some triple exists.

The general rule this illustrates. For a polynomial p(n) = a₄nᵈ + … + a₁n + a₀ with a₄ > 0, we have p(n) = Θ(nᵈ). The lower-order terms are eventually swamped by the leading one, and the leading coefficient is absorbed into c₁ and c₂. You will almost never need to do the c₁, c₂, n₀ dance in practice — but being able to do it once is what makes the shortcut trustworthy.

The same reasoning shows why lower-order terms are discardable in general: they are, by definition, functions that grow more slowly, so past some n₀ their contribution can be covered by adjusting the constants. And a constant function is Θ(1), with n⁰ = 1 the implicit g.

O-notation: upper bound

Θ bounds a function from both sides. Often you can only establish, or only care about, the upper side.

O(g(n)) = { f(n) : there exist positive constants c and n₀ such that 0 ≤ f(n) ≤ c·g(n) for all n ≥ n₀ }

g(n) is an asymptotic upper bound for f(n): f grows no faster than g, to within a constant factor.

Since Θ requires both bounds and O only the upper one, Θ(g(n)) ⊆ O(g(n)). Anything tightly bounded is also upper bounded. The converse fails, and that asymmetry is the source of the most common misuse of the notation:

O is an upper bound, not a description. Every one of these is true: n = O(n), n = O(n²), n = O(n¹⁰⁰), n = O(2ⁿ). The last three are true and useless, the way “this flight takes at most a year” is true and useless. When someone says an algorithm is O(n²) they usually mean Θ(n²); when a paper says it, they may well mean only the upper bound, because a matching lower bound was too hard to prove. The distinction matters when comparing two algorithms.

The convention in the literature is nonetheless to use O most of the time, for a good reason: an upper bound on the worst case is a guarantee, and a guarantee is what you want from an analysis. Note also that bounding the worst case with O(n²) automatically bounds every case with O(n²), which is not true of Θ. Insertion sort’s running time is O(n²) on all inputs, but it is not Θ(n²) on all inputs — on sorted input it is Θ(n).

Ω-notation: lower bound

Ω(g(n)) = { f(n) : there exist positive constants c and n₀ such that 0 ≤ c·g(n) ≤ f(n) for all n ≥ n₀ }

g(n) is an asymptotic lower bound: f grows at least as fast as g, to within a constant factor.

Ω is how you say an algorithm cannot do better than something. The best-case running time of insertion sort is Ω(n), which is really just the observation that it must at least look at every element. More substantially, Chapter 8 proves that any comparison sort takes Ω(n lg n) comparisons in the worst case — a statement about every possible algorithm of that kind, which is why lower bounds are the harder and more interesting half of the subject.

The three pictures

The definitions are easier to hold onto as three graphs. In each, the shaded relationship only has to hold to the right of n₀.

n₀ c₂g(n) c₁g(n) f(n) (a) f(n) = Θ(g(n)) squeezed between both n₀ cg(n) f(n) (b) f(n) = O(g(n)) stays below n₀ cg(n) f(n) (c) f(n) = Ω(g(n)) stays above
Figure 3.1 — The three notations. To the left of n₀ the curves may cross freely; the definitions constrain nothing there.

Theorem 3.1, and how to use it

Theorem 3.1. For any two functions f(n) and g(n), we have f(n) = Θ(g(n)) if and only if f(n) = O(g(n)) and f(n) = Ω(g(n)).

The proof is immediate from the definitions — the two one-sided inequalities of O and Ω are exactly the two halves of the Θ inequality — but the theorem is the standard proof strategy for tight bounds, and it is used constantly:

To show f(n) = Θ(g(n)): 1. Prove the upper bound f(n) = O(g(n)) 2. Prove the lower bound f(n) = Ω(g(n)) 3. Cite Theorem 3.1

An example the chapter uses: an² + bn + c = Θ(n²) for constants a > 0, b, c. Rather than hunting for c₁ and c₂ simultaneously, bound above by noting every term is at most a constant times for large n, bound below by noting the an² term eventually dominates the others, and conclude.

The theorem also explains the standard division of labour in algorithms research. To pin down the complexity of a problem, you give an algorithm (an upper bound) and a proof that no algorithm can do better (a lower bound). When the two meet, the problem is settled. Sorting by comparisons is settled at Θ(n lg n): merge sort supplies the O, and the decision-tree argument of Chapter 8 supplies the Ω.

o and ω: the strict versions

O may or may not be tight: 2n² = O(n²) is tight, while 2n = O(n²) is not. Little-oh is the notation for an upper bound that is definitely not tight.

o(g(n)) = { f(n) : for ANY positive constant c > 0, there exists a constant n₀ > 0 such that 0 ≤ f(n) < c·g(n) for all n ≥ n₀ }

Compare the quantifiers carefully, because that single change is the entire difference:

Quantifier on cMeaningLimit form
f = O(g)There exists some cf grows no faster than glim f/g < ∞
f = o(g)For every c, however smallf becomes insignificant relative to glim f/g = 0
f = Ω(g)There exists some cf grows at least as fast as glim f/g > 0
f = ω(g)For every c, however largef dwarfs glim f/g = ∞

The limit characterisations on the right are usually the fastest way to settle a question in practice, and CLRS gives them explicitly for o and ω. Examples:

2n = o(n²) // yes: 2n/n² = 2/n → 0 2n² ≠ o(n²) // no: 2n²/n² = 2, not 0 n²/2 = ω(n) // yes: (n²/2)/n = n/2 → ∞ n²/2 ≠ ω(n²) // no: ratio is ½, not ∞ lg n = o(n) // logs lose to any positive power n¹⁰⁰ = o(2ⁿ) // polynomials lose to exponentials

The chapter draws the analogy that makes all five easy to remember:

NotationBehaves like
f(n) = O(g(n))a ≤ b
f(n) = Ω(g(n))a ≥ b
f(n) = Θ(g(n))a = b
f(n) = o(g(n))a < b
f(n) = ω(g(n))a > b

Notational abuse, and why it is allowed

Since Θ(g(n)) is a set, the honest statement is f(n) ∈ Θ(g(n)). Everybody writes f(n) = Θ(g(n)) instead. CLRS keeps the equals sign and explains what it means in each position, because the convention is genuinely useful once you know how to read it.

Where the notation appearsWhat it meansExample
Alone on the right Set membership n² + n = Θ(n²) means n² + n is a member of that set
Inside a formula Stands for some anonymous function in that set 2n² + Θ(n) means 2n² + h(n) for some h(n) ∈ Θ(n)
On the left of = “For any choice on the left, there is a choice on the right making it true” 2n² + Θ(n) = Θ(n²)

That third row is the one worth pausing on. The equation 2n² + Θ(n) = Θ(n²) asserts: no matter which function h(n) ∈ Θ(n) you pick, 2n² + h(n) lands in Θ(n²). Read that way, chains of such equations compose, which is what lets you write a derivation like this and have each step mean something:

T(n) = 2T(n/2) + Θ(n) = 2·Θ((n/2) lg(n/2)) + Θ(n) = Θ(n lg n)
The relation is not symmetric. n = O(n²) is true; O(n²) = n is meaningless. The equals sign here reads left to right only. This is the price of the convenient notation, and it is why some authors insist on . Read = as “is”, in the sense of “a bee is an insect” — true, but not reversible.

One more convention: when asymptotic notation appears in an expression with more than one variable, such as O(m + n) for a graph algorithm, the constants must work uniformly for all sufficiently large values of every variable. CLRS spells this out because it is easy to write a bound that quietly holds only when one parameter dominates.

Comparing functions

The notations obey the algebraic properties you would expect from the ≤, ≥, =, <, > analogy.

PropertyStatementHolds for
Transitivityf = Θ(g) and g = Θ(h) imply f = Θ(h)All five
Reflexivityf(n) = Θ(f(n))Θ, O, Ω — not o or ω
Symmetryf = Θ(g) if and only if g = Θ(f)Θ only
Transpose symmetryf = O(g) if and only if g = Ω(f); likewise f = o(g) iff g = ω(f)O/Ω and o/ω
Where the analogy breaks: trichotomy fails. For real numbers, exactly one of a < b, a = b, a > b holds. For functions, two functions may be incomparable — neither is O of the other. The book’s example is n and n¹⁺ˢᵢⁿⁿ, which oscillates between n⁰ and and so is never trapped either way. Real running times are almost never like this, but it means you cannot assume two growth rates are comparable; you have to check.

Bounding a running time, not a function

The 4th edition is careful about a slippage that causes real confusion, and it is worth getting straight because it explains statements that otherwise look contradictory.

A running time is not always a single function of n. For a given n, insertion sort takes different amounts of time on different inputs of that size, so “the running time of insertion sort” is a range of values for each n, not one value. That is why we talk about best case, worst case, and average case: each of those is a single function, and asymptotic notation applies to those cleanly.

When we apply the notation directly to the running time itself, this is the convention:

StatementMeans
The running time is O(f(n))There is a function in O(f(n)) that upper-bounds the running time on every input of size n
The running time is Ω(f(n))There is a function in Ω(f(n)) that lower-bounds the running time on every input of size n
The running time is Θ(f(n))Both of the above, so the running time is pinned regardless of input

This resolves the apparent paradox in Chapter 2. All of the following are simultaneously true of insertion sort, and each says something different:

worst-case running time = Θ(n²) // reverse-sorted input best-case running time = Θ(n) // already-sorted input running time = O(n²) // true on EVERY input running time = Ω(n) // true on EVERY input running time ≠ Θ(n²) // false: sorted input is faster running time ≠ Θ(n) // false: reversed input is slower

Merge sort, by contrast, genuinely has running time Θ(n lg n) without qualification, because it does the same amount of work on every input of a given size. That insensitivity to input is a real property, and it is one of merge sort’s selling points.

Standard functions and identities

Section 3.3 is a reference section. You do not read it end to end; you come back to it. These are the parts that get used.

Monotonicity, floors, and ceilings

That last identity is why merge sort can split into ⌈n/2⌉ and ⌊n/2⌋ and still cover the array exactly. Chapter 4 shows that dropping the floors and ceilings from a recurrence does not change its asymptotic solution, which is why nobody carries them around.

Logarithms

CLRS notation, and it is not the notation from your calculus course:

WrittenMeans
lg nlog₂ n — binary logarithm. The default in this book.
ln nlogᵉ n — natural logarithm
lgᵏ n(lg n)ᵏ — exponentiation of the logarithm
lg lg nlg(lg n) — composition, which grows extremely slowly
Why the base rarely matters. Changing base multiplies by a constant: logᵀ n = logᴸ n / logᴸ a. Since asymptotic notation discards constant factors, Θ(lg n) = Θ(ln n) = Θ(log₁₀ n). The base does matter inside an exponent, though: 2ⁿ and 3ⁿ are not asymptotically equal.

The identities that come up:

a = blog_b a logᴸ(ab) = logᴸ a + logᴸ b logᴸ(aⁿ) = n logᴸ a logᴸ(a/b) = logᴸ a - logᴸ b alog_b c = clog_b a lg(n!) = Θ(n lg n)

Exponentials and factorials

Functional iteration and the iterated logarithm

f⁽ⁱ⁾(n) means f applied i times to n. The iterated logarithm counts how many times you must apply lg before the result drops to 1 or below:

lg* n = min { i ≥ 0 : lg(i) n ≤ 1 }
n2416655362⁶⁵⁵³⁶
lg* n12345

The number of atoms in the observable universe is far below 2⁶⁵⁵³⁶, so for every input you will ever process, lg* n ≤ 5. It is effectively a constant, and it shows up as the bound for disjoint-set operations in Chapter 19.

Fibonacci numbers

F₀ = 0, F₁ = 1, Fᵢ = Fᵢ₋₁ + Fᵢ₋₂. They grow exponentially: Fᵢ = φᵢ/√5 rounded to the nearest integer, where φ = (1 + √5)/2 ≈ 1.618 is the golden ratio. The point for algorithms is that Fibonacci numbers grow exponentially, which is why the naive recursive Fibonacci is exponential-time and why the sequence appears in the analysis of worst-case inputs.

The growth hierarchy

Everything above collapses into one ordering worth memorising. Each entry is o of the next.

GrowthNameTypical sourcen = 10⁶ means roughly
Θ(1)constantArray index, hash lookup1 step
Θ(lg* n)iterated logDisjoint sets (Ch 19)4 steps
Θ(lg n)logarithmicBinary search, balanced tree20 steps
Θ(√n)rootTrial division to √n1,000 steps
Θ(n)linearOne scan of the input10⁶ steps
Θ(n lg n)linearithmicMerge sort, heapsort2 × 10⁷ steps
Θ(n²)quadraticInsertion sort, all pairs10¹² steps — minutes to hours
Θ(n³)cubicFloyd-Warshall, naive matrix multiply10¹⁸ steps — not happening
Θ(2ⁿ)exponentialAll subsetshopeless past n ≈ 40
Θ(n!)factorialAll permutationshopeless past n ≈ 12
The three facts that settle most comparisons: logarithms beat roots (lgᵇ n = o(nᵉ) for any b and any a > 0), polynomials beat exponentials’ inverses (nᵇ = o(aⁿ) for any a > 1), and factorials beat exponentials (2ⁿ = o(n!)).

Recap

The eight things to carry forward

Where this goes next

Chapter 4 puts the notation to work on recurrences. The T(n) = 2T(n/2) + Θ(n) from merge sort gets solved three different ways — substitution, recursion trees, and the master method — and the master theorem in particular becomes a lookup table you will use for the rest of the book.


Ch 2 — Getting Started Ch 4 — Divide-and-Conquer