Evanalysis
3.1Estimated reading time: 23 min

3.1 Complexity growth and algorithmic cost

Build an explicit cost model, prove asymptotic bounds, and derive tight growth classes from code instead of guessing from syntax.

Course contents

Motivation

Saying that one program “runs faster” is incomplete until we say what is being measured, how input size is defined, and which legal inputs are being compared. The lecture deck motivates this point with selection sort: on one recorded machine, doubling the array size made the measured time roughly four times as large. Those timings are useful evidence about scaling, but they are not a proof. Hardware, compiler choices, memory behavior, and finite-input constants all affect a stopwatch result.

Complexity analysis replaces the machine-specific question with a mathematical one. We define a cost function T(n)T(n), count selected basic operations, and ask how that count grows as nn becomes large. The result is meaningful only under the stated model. An array comparison may be constant time when keys have fixed size, for example, but comparing arbitrarily long strings has its own cost.

This unit develops the notation and counting rules needed for that analysis. The goal is not to attach a familiar label to code by sight. It is to write an exact count or a justified bound first, then state a tight asymptotic class when the evidence supports one.

Definitions

Definition

Input size and the RAM cost model

For an input II, let n=∣I∣n=|I| be the declared size parameter and let T(I)T(I) be the number of charged basic operations. Unless a different model is stated, this note uses a unit-cost RAM model: fixed-width arithmetic, index arithmetic, array access, assignment, and comparison of fixed-size keys each cost one constant-size unit. The cost function T(n)T(n) must also state a case, such as the maximum over all legal inputs of size nn for worst-case cost.

The model separates time from auxiliary space. A claim about running time does not automatically describe memory use, and the cost of a helper call must be included rather than treated as one source-code line.

Definition

Asymptotic upper, lower, and tight bounds

Let f,g:N→R≥0f,g:\mathbb N\to\mathbb R_{\ge 0} be eventually nonnegative, with g(n)g(n) eventually positive.

  • f(n)=O(g(n))f(n)=O(g(n)) if there are constants C>0C>0 and n0n_0 such that f(n)≤Cg(n)f(n)\le Cg(n) for every n≥n0n\ge n_0.
  • f(n)=Ω(g(n))f(n)=\Omega(g(n)) if there are constants c>0c>0 and n0n_0 such that f(n)≥cg(n)f(n)\ge cg(n) for every n≥n0n\ge n_0.
  • f(n)=Θ(g(n))f(n)=\Theta(g(n)) if both bounds hold: there are c,C>0c,C>0 and n0n_0 such that cg(n)≤f(n)≤Cg(n)cg(n)\le f(n)\le Cg(n) whenever n≥n0n\ge n_0.

Big-O is an eventual upper bound, not a unique name for a growth rate. A linear function is also O(n2)O(n^2). When we mean that a function genuinely grows at the same asymptotic rate as gg, the informative statement is Θ(g)\Theta(g).

Definition

Case labels, expectations, and amortized cost

Best-case and worst-case costs take the minimum and maximum over legal inputs of size nn. Average-case cost is an expectation under an explicitly stated input distribution. Amortized cost instead averages over a specified sequence of operations without assuming random inputs. For example, start with an empty dynamic array whose initial capacity is a fixed positive constant, and whenever it fills, replace its capacity by a fixed factor ρ>1\rho>1 times the old capacity (with consistent integer rounding). In an insertion-only sequence of mm pushes, charge constant cost for an ordinary push and one unit for every element copied during resize. One resizing push can cost Θ(n)\Theta(n), but the sequence has Θ(m)\Theta(m) total cost, giving Θ(1)\Theta(1) amortized cost per push.

From code to a cost function

A defensible analysis follows a repeatable sequence. First, state the legal inputs and choose the size parameter. For an array routine this is often its length, but a graph routine may need both ∣V∣|V| and ∣E∣|E|, and a numerical routine may depend on the number of input bits rather than the numeric value itself. Compressing a genuinely two-parameter problem into one symbol can hide the case that dominates the cost.

Second, name the operation being charged. Selection sort is especially clean when key comparisons are counted, because its loop bounds determine that count independently of the input order. If assignments, allocations, or key-copying costs are also relevant, write separate counts and combine them only after their models are clear. A statement such as “the loop costs nn” is incomplete unless it identifies what happens on each of those nn executions.

Third, translate control flow into arithmetic before simplifying it. Consecutive blocks contribute a sum. A fixed-cost body repeated over a rectangular iteration space contributes a product. A bound that depends on the outer index contributes a sum such as ∑i=0n−1(n−i−1)\sum_{i=0}^{n-1}(n-i-1). A recursive routine contributes a recurrence only after the number and sizes of recursive calls, plus the non-recursive work, have been justified.

Finally, prove both sides when claiming a tight class. An upper bound alone can be deliberately loose; a matching lower bound shows that the chosen representative function is unavoidable for the implementation and case being analyzed. This derivation-first discipline also exposes hidden assumptions, such as constant-time indexing, a bounded key size, or a helper whose own scan was mistakenly counted as one operation.

For any fixed bases a,b>1a,b>1, log⁡an=log⁡bn/log⁡ba\log_a n=\log_b n/\log_b a, so changing the logarithm base changes only a constant factor. For fixed parameters p>0p>0, 0<ε<q0<\varepsilon<q, and A>1A>1, the source hierarchy can therefore be read as

1≺(log⁡n)p≺nε≺nq≺An(p>0, 0<ε<q, A>1),1 \prec (\log n)^p \prec n^\varepsilon \prec n^q \prec A^n \qquad (p>0,\ 0<\varepsilon<q,\ A>1),

with the order interpreted through ratios or tight classes, not through Big-O labels alone. In particular, (log⁡n)p/nε→0(\log n)^p/n^\varepsilon\to0 and nq/An→0n^q/A^n\to0 for fixed positive parameters.

Read and try

Compare asymptotic growth at one n

The widget now ties each growth class to a concrete code shape, so readers can change n and see how the chosen sample behaves against the comparison table.

Choose an algorithm shape

Code sample

for (int width = 1; width < n; width *= 2) {
  for (int i = 0; i < n; i += 2 * width) {
    merge_block(i, width);
  }
}

Growth class: O(n log n)

Estimated primitive steps: 64.00

Interpretation: There are about log n rounds, and each round still touches a linear amount of data.

ClassValue at n=16
O(1)1.00
O(log n)4.00
O(n)16.00
O(n log n)64.00
O(n^2)256.00

Comparing growth classes responsibly

The hierarchy describes eventual growth, not the running time at every finite input. A quadratic implementation with a small constant can outperform a linear one for a limited range, and cache behavior can shift the crossover again. Asymptotic notation intentionally discards those fixed constants so that the long-run scaling can be compared independently of one machine. Engineering decisions should therefore use both the asymptotic result and measurements over the input range that actually matters.

A ratio gives a precise comparison when both costs have tight positive bounds. If f(n)/g(n)→0f(n)/g(n)\to0, then ff has smaller order than gg; this is the meaning of f=o(g)f=o(g). If the ratio approaches a positive finite constant, the functions belong to the same Θ\Theta class, although their exact costs may differ. If the ratio grows without bound, ff has larger order. This method is safer than trying to rank two set-membership statements such as f=O(n2)f=O(n^2) and g=O(n3)g=O(n^3), because either statement may be a deliberately non-tight upper bound.

Theorem/Proposition

Theorem

A positive-leading polynomial has dominant-term growth

Let f(n)=∑k=0daknkf(n)=\sum_{k=0}^{d}a_kn^k, where dd is a nonnegative integer, the coefficients are fixed real constants, ad>0a_d>0, and ff is eventually nonnegative. Then f(n)=Θ(nd)f(n)=\Theta(n^d).

Theorem

Selection sort makes a triangular number of comparisons

For n≥1n\ge1, consider the displayed selection-sort implementation on a random-access array. In the unit-cost model, each key comparison a[j]<a[min]a[j]<a[\mathrm{min}] costs Θ(1)\Theta(1). The algorithm makes exactly ∑i=1n−1(n−i)=n(n−1)/2\sum_{i=1}^{n-1}(n-i)=n(n-1)/2 such comparisons on every input and therefore takes Θ(n2)\Theta(n^2) time. The conditional swap executes at most n−1n-1 times and does not change the tight bound.

Proof sketch or proof idea

Proof

Proof of dominant-term polynomial growth

If d=0d=0, then f(n)=a0>0f(n)=a_0>0 for every nn, so f(n)=Θ(1)=Θ(n0)f(n)=\Theta(1)=\Theta(n^0). Now assume d≥1d\ge1. For n≥1n\ge1, every lower power nkn^k with k<dk<d is at most ndn^d. Hence

f(n)≤∣f(n)∣≤(ad+∑k=0d−1∣ak∣)nd,f(n)\le |f(n)|\le \left(a_d+\sum_{k=0}^{d-1}|a_k|\right)n^d,

which gives the required upper bound. For the lower bound, divide the lower terms by ndn^d. Each ratio nk/ndn^k/n^d tends to zero, so there is a threshold n0n_0 after which ∑k<d∣ak∣nk≤(ad/2)nd\sum_{k<d}|a_k|n^k\le (a_d/2)n^d. Therefore f(n)≥(ad/2)ndf(n)\ge(a_d/2)n^d for n≥n0n\ge n_0. Positive constants now bound ff above and below by ndn^d, proving f(n)=Θ(nd)f(n)=\Theta(n^d). The sign and fixed-coefficient assumptions prevent “drop the lower terms” from becoming an unsafe cancellation rule.

Proof

Proof of the selection-sort comparison count

On outer pass i=0i=0, the inner loop uses j=1,…,n−1j=1,\ldots,n-1, so it performs n−1n-1 comparisons. Pass i=1i=1 performs n−2n-2, and pass i=n−2i=n-2 performs one. The loop bounds do not depend on the key order, so the count is identical on sorted, reverse-sorted, and arbitrary arrays. Thus

(n−1)+(n−2)+⋯+1=∑i=1n−1(n−i)=n(n−1)2.(n-1)+(n-2)+\cdots+1 =\sum_{i=1}^{n-1}(n-i) =\frac{n(n-1)}2.

For n≥2n\ge2, this count lies between n2/4n^2/4 and n2/2n^2/2, establishing Θ(n2)\Theta(n^2) comparisons. Constant-time loop control and at most linear many swaps add only O(n2)O(n^2) work, while the comparisons already give an Ω(n2)\Omega(n^2) lower bound for this implementation.

The familiar Ω(nlog⁡n)\Omega(n\log n) lower bound for general comparison sorting has a different scope: it assumes a comparison decision-tree model and arbitrary orderable inputs. Its proof, together with quickselect, counting sort, and radix sort, belongs to the later sorting note. It should not be used here without those model assumptions.

Worked examples

Worked example

Whyn2dominatesnWhy n^2 dominates n

Compare the lower-order term with the proposed dominant term:

nn2=1n⟶0.\frac{n}{n^2}=\frac1n\longrightarrow0.

Equivalently, n2/n=nn^2/n=n grows without bound. The conclusion is not that the linear term vanishes from an exact formula. It is that the linear term becomes negligible relative to n2n^2, which supports a tight Θ(n2)\Theta(n^2) statement when the leading coefficient is positive.

Worked example

Constant-time statements

int x = a + b;
int y = x * 2;
return y;

Under the stated fixed-width RAM model, each statement executes once and costs constant time. Their sequential costs add to another constant, so the fragment is Θ(1)\Theta(1). This conclusion would need revision if, for example, a and b represented unbounded integers whose arithmetic cost grows with their bit length.

Worked example

Linear scan

int sum(const int a[], int n) {
   int total = 0;
   for (int i = 0; i < n; i++) {
      total += a[i];
   }
   return total;
}

The body runs exactly nn times and has constant cost, so the total is an+b=Θ(n)an+b=\Theta(n) for fixed constants a>0a>0 and bb. The lecture deck's Average function has the same structure: one full pass plus a final division. Calling such a helper is therefore a Θ(n)\Theta(n) operation, not an automatically constant-cost source line.

Worked example

Nested loops create quadratic cost

int countPairs(int n) {
   int c = 0;
   for (int i = 0; i < n; i++) {
      for (int j = 0; j < n; j++) {
         c++;
      }
   }
   return c;
}

For this particular program, the inner statement runs exactly n⋅nn\cdot n times, so the cost is Θ(n2)\Theta(n^2). The conclusion follows from the bounds, not merely from seeing two loop keywords.

Function calls can create the same product invisibly. In the source deck's naive variance routine, an nn-iteration loop calls Average(array,n) on every iteration. A Θ(n)\Theta(n) helper repeated nn times gives Θ(n2)\Theta(n^2) work; the final average adds only Θ(n)\Theta(n). Computing the mean once before the loop changes the composition to several consecutive linear passes, whose costs add to Θ(n)\Theta(n). If temporary storage is used, its auxiliary-space cost must be reported separately.

The same counting method handles loops that are not rectangular. If the inner loop runs from i + 1 to n - 1, its execution count is ∑i=0n−2(n−i−1)\sum_{i=0}^{n-2}(n-i-1), a triangular sum. If an index doubles on every iteration, the condition 2k<n2^k<n gives k=Θ(log⁡n)k=\Theta(\log n) executions. If the inner bound is i, the count is ∑ii\sum_i i rather than nn times a fixed quantity. Thus the relevant object is the iteration space described by the bounds, not the number of syntactic loop headers.

Worked example

Selection-sort growth

void selectionSort(int a[], int n) {
   for (int i = 0; i < n - 1; i++) {
      int min = i;
      for (int j = i + 1; j < n; j++) {
         if (a[j] < a[min]) min = j;
      }
      if (min != i) {
         int tmp = a[i];
         a[i] = a[min];
         a[min] = tmp;
      }
   }
}

The exact comparison count is n(n−1)/2n(n-1)/2, not an approximation. Replacing nn by 2n2n changes the leading quadratic expression by a factor approaching four; replacing it by 10n10n gives a factor approaching one hundred. Those ratios explain the lecture timing pattern, while the theorem—not the timing table—proves the asymptotic class.

Worked example

A clean simplification proof

To prove (n2+n)/2=O(n2)(n^2+n)/2=O(n^2), choose C=1C=1 and n0=1n_0=1. For every n≥1n\ge1,

n2+n2≤n2+n22=n2.\frac{n^2+n}{2}\le\frac{n^2+n^2}{2}=n^2.

For a tight result, also note (n2+n)/2≥n2/2(n^2+n)/2\ge n^2/2. Thus the expression is Θ(n2)\Theta(n^2). Giving both inequalities makes clear why O(n3)O(n^3) would be true but uninformative.

Worked example

Binary search does not need a full scan

int binarySearch(const int a[], int n, int target) {
   int left = 0, right = n - 1;
   while (left <= right) {
      int mid = left + (right - left) / 2;
      if (a[mid] == target) return mid;
      if (a[mid] < target) left = mid + 1;
      else right = mid - 1;
   }
   return -1;
}

The precondition is a sorted random-access array with constant-time key comparisons. A first-probe match gives best-case Θ(1)\Theta(1). In the worst case, each iteration leaves at most half the candidates, so after kk iterations at most n/2kn/2^k remain. Reaching one candidate requires k=Θ(log⁡n)k=\Theta(\log n). The midpoint formula avoids overflowing left + right.

An unsuccessful search has the same logarithmic worst-case bound: the candidate interval keeps shrinking until it becomes empty. If duplicate keys are allowed, this version may return any matching position; finding the first or last match requires a modified invariant. Binary search also does not make an unsorted search logarithmic for free. Sorting the data first costs additional work, so that preprocessing is justified only when its cost can be shared across enough later queries. On a linked list, locating the midpoint is not constant time, so the random-access assumption is part of the complexity claim rather than an implementation detail.

Worked example

Why merge-style structure gives n log n

Suppose an algorithm splits a problem in half until subproblems have size one, and every recursion level performs Θ(n)\Theta(n) total combination work. There are Θ(log⁡n)\Theta(\log n) levels, so the level costs add to Θ(nlog⁡n)\Theta(n\log n). Equivalently, the recurrence T(n)=2T(n/2)+Θ(n)T(n)=2T(n/2)+\Theta(n) has that solution for powers of two, with ceilings and floors changing only constants for general nn.

The premise “linear work per level” must be proved. Divide-and-conquer alone does not guarantee an nlog⁡nn\log n bound.

This level argument counts time, not automatically space. If the two recursive calls are completed one after the other, the active recursion depth is Θ(log⁡n)\Theta(\log n), while temporary merge storage may still require Θ(n)\Theta(n) auxiliary space. A different implementation or parallel execution can change the space profile without changing the recurrence used for total work, so the resource being bounded must remain explicit.

Common mistakes

Common mistake

Using Big-O as though it were a tight class

Because nn is both O(n)O(n) and O(n2)O(n^2), two Big-O memberships cannot by themselves prove that one algorithm eventually outgrows another. Compare the actual functions or establish Θ\Theta bounds first.

Common mistake

Counting loop syntax instead of executions

Two independent length-nn loops nested as in countPairs give n2n^2 body executions. Consecutive loops add to 2n2n, a triangular inner bound sums to n(n−1)/2n(n-1)/2, and a halving loop has logarithmically many iterations. Write the sum or product before simplifying.

Common mistake

Ignoring preconditions, helper costs, or the quoted case

Binary search requires sorted random-access input; average case requires a distribution; a helper call contributes its full cost. Every conclusion should name the legal input, cost model, and best, worst, expected, or amortized case.

Common mistake

Dropping terms as blind algebra

Lower-order terms remain in the exact cost and can matter for finite nn. Dominant-term simplification is justified by inequalities or ratios under fixed coefficients and eventual nonnegativity, not by deleting arbitrary signed or nn-dependent terms.

Common mistake

Calling every dynamic-array push constant-time

A push that triggers a resize can cost Θ(n)\Theta(n). Under geometric capacity growth, a specified sequence of pushes has Θ(1)\Theta(1) amortized cost per push; that does not make every individual push worst-case constant.

Summary

  • Define nn, the charged operation, the resource, and the input case before writing a complexity label.
  • OO is an upper bound, Ω\Omega a lower bound, and Θ\Theta a tight bound.
  • Fixed logarithm bases differ only by constants; fixed powers dominate logs, and fixed-base exponentials dominate powers.
  • Count sequential work by addition, repeated independent work by products, dependent bounds by sums, and recursive work by a recurrence or level count.
  • Selection sort makes exactly n(n−1)/2n(n-1)/2 comparisons and is Θ(n2)\Theta(n^2) in the stated model; empirical timings only illustrate that result.

Exercises

Checkpoint

If an algorithm has two independent nested loops that each run n times, what is the tight class of the total cost?

Assume the loop body is Θ(1)\Theta(1) and always executes.

Checkpoint

Why does a Theta(n log n) cost grow more slowly than a Theta(n^2) cost for sufficiently large n?

Compare the representative functions through their ratio.

Checkpoint

What is the tight asymptotic class of 0.0001n^3 + n?

Use the positive-leading polynomial theorem, not only a deletion slogan.

Checkpoint

Why does selection sort have quadratic cost even though only one element is placed each pass?

Count the comparisons in the shrinking inner loop.

Solutions

Solution · Answer 1

The body executes n⋅n=n2n\cdot n=n^2 times, so the total cost is Θ(n2)\Theta(n^2) under the stated assumptions. The independence and fixed-cost premises are essential.

Solution · Answer 2

The ratio is (nlog⁡n)/n2=(log⁡n)/n(n\log n)/n^2=(\log n)/n, which tends to zero. With positive tight bounds, this shows the quadratic cost eventually grows faster.

Solution · Answer 3

The leading coefficient is positive and the remaining term has lower degree, so the polynomial is Θ(n3)\Theta(n^3). It is also O(n4)O(n^4), but that weaker upper bound does not identify its tight growth.

Solution · Guided solution 4

Passes make (n−1),(n−2),…,1(n-1),(n-2),\ldots,1 comparisons. Their sum is n(n−1)/2=Θ(n2)n(n-1)/2=\Theta(n^2), independent of input order. Placing one element per pass does not eliminate the scan needed to choose that element.