1 Algorithm Analysis and Big-O Notation

A progressive guide to measuring algorithm efficiency, simplifying asymptotic expressions, analyzing recursion, and comparing data-structure operations under explicit assumptions.

Foundations of Analysis

analysis studies how resource requirements change as the input grows. The two resources considered most often are time, measured by the number of basic operations, and space, measured by memory use.

An is a finite, precisely defined sequence of steps for solving a problem. Algorithmic thinking proceeds by:

  1. Defining valid inputs and required outputs.

  2. Choosing an appropriate representation for the data.

  3. Identifying essential operations such as comparison, insertion, deletion, or lookup.

  4. Designing steps that produce the correct result.

  5. Analyzing correctness and efficiency.

The same task can have different costs depending on the representation and the . Searching an unsorted array generally requires scanning elements one at a time. If the data is sorted, binary search can repeatedly discard half of the remaining elements. A balanced search tree or a hash table can also provide faster searches under appropriate assumptions.

A useful distinction is between an and a . An specifies what operations mean, while a specifies how those operations are implemented. A stack can therefore be implemented with an array or a linked list, with different memory and operation costs.

Takeaway: Analyze both the algorithmic steps and the representation that makes those steps possible.

Input Size, Cost Models, and Resources

Begin by defining the input size. It is commonly represented by nn, but its meaning depends on the problem:

  • For an array, nn may be the number of elements.

  • For a string, nn may be the number of characters.

  • For a graph, nn may be the number of vertices and mm the number of edges.

  • For an integer, input size may be the number of bits used to represent it rather than its numerical value.

A identifies the basic operations being counted. Arithmetic, assignment, comparison, and array access are often treated as constant-time operations in an introductory model. This is an approximation, because hardware, memory layout, caching, and language implementation can affect actual cost.

For a one-pass summation of an array, each of the nn elements is processed once. The runtime is therefore Θ(n)\Theta(n). If the stores only a running total, its auxiliary space is Θ(1)\Theta(1).

Keep three space measures separate:

  • Input space stores the input itself.

  • Auxiliary space is additional memory used by the .

  • Total space is input space plus auxiliary space.

For example, modifying an array in place can use Θ(1)\Theta(1) auxiliary space, while creating a second array of nn elements uses Θ(n)\Theta(n) auxiliary space. A recursive may also use call-stack space proportional to its maximum recursion depth.

Takeaway: State what nn measures, what operations count, and whether a space bound refers to input, auxiliary, or total space.

Runtime Patterns and Growth Rates

describes how computational work grows with input size. Combine the costs of program parts according to their control flow:

  • Sequential statements add. A Θ(n)\Theta(n) part followed by a Θ(n2)\Theta(n^2) part costs Θ(n+n2)=Θ(n2)\Theta(n+n^2)=\Theta(n^2).

  • Consecutive loops add. Two separate loops of nn iterations cost Θ(n+n)=Θ(n)\Theta(n+n)=\Theta(n).

  • Independent nested loops multiply. Two nested loops that each run nn times perform Θ(n2)\Theta(n^2) work.

  • For conditionals, analyze the branch appropriate to the selected case; worst-case analysis uses the most expensive branch.

  • Repeatedly halving or doubling a problem size commonly produces Θ(log⁡n)\Theta(\log n).

To simplify an expression, remove constant multipliers, retain the fastest-growing term, and express the result asymptotically. For example:

  • 7n+207n+20 becomes Θ(n)\Theta(n).

  • 4n2+3n+94n^2+3n+9 becomes Θ(n2)\Theta(n^2).

  • 12log⁡n+512\log n+5 becomes Θ(log⁡n)\Theta(\log n).

The logarithm base is normally omitted because changing bases changes the value only by a constant factor. Thus Θ(log⁡2n)\Theta(\log_2 n) and Θ(log⁡10n)\Theta(\log_{10} n) are both written as Θ(log⁡n)\Theta(\log n).

Common growth rates, from generally more scalable to generally less scalable, are:

  1. Constant: Θ(1)\Theta(1), such as array access by index.

  2. Logarithmic: Θ(log⁡n)\Theta(\log n), such as binary search in sorted data.

  3. Linear: Θ(n)\Theta(n), such as one pass through an array.

  4. Linearithmic: Θ(nlog⁡n)\Theta(n\log n), such as merge sort.

  5. Quadratic: Θ(n2)\Theta(n^2), such as many pairwise comparisons.

  6. Cubic: Θ(n3)\Theta(n^3), such as some three-level nested loops.

  7. Exponential: Θ(2n)\Theta(2^n), such as enumerating many subsets.

  8. Factorial: Θ(n!)\Theta(n!), such as enumerating permutations.

This ordering describes eventual growth. For small inputs, a higher-growth can sometimes run faster because of smaller constant factors.

Takeaway: Follow the control-flow structure, then discard constants and lower-order terms only after forming the total cost.

Asymptotic Bounds and Input Cases

Asymptotic notation describes growth for sufficiently large inputs while ignoring constant factors and lower-order terms.

gives an asymptotic upper bound. Formally, T(n)T(n) is O(f(n))O(f(n)) if there are positive constants cc and n0n_0 such that

T(n)≤cf(n)for all n≥n0.T(n) \leq c f(n) \quad \text{for all } n \geq n_0.

gives an asymptotic lower bound. Formally, T(n)T(n) is Ω(f(n))\Omega(f(n)) if there are positive constants cc and n0n_0 such that

T(n)≥cf(n)for all n≥n0.T(n) \geq c f(n) \quad \text{for all } n \geq n_0.

gives a tight bound. A function is Θ(f(n))\Theta(f(n)) when it is both O(f(n))O(f(n)) and Ω(f(n))\Omega(f(n)). For example, a loop that always processes exactly nn elements has runtime Θ(n)\Theta(n), and therefore also has O(n)O(n) and Ω(n)\Omega(n) bounds.

If T(n)=3n2+5n+7T(n)=3n^2+5n+7, then T(n)=O(n2)T(n)=O(n^2) and, more tightly, T(n)=Θ(n2)T(n)=\Theta(n^2). It is also technically O(n3)O(n^3), but that upper bound communicates less useful information.

Do not confuse a case analysis with an asymptotic notation. Sequential search in an array of nn elements has:

  • Best case Θ(1)\Theta(1) when the target is first.

  • Worst case Θ(n)\Theta(n) when the target is last or absent.

  • Average case Θ(n)\Theta(n) under a common uniform assumption, because about half the elements are examined on average.

An average-case result requires an explicit assumption about the input distribution. Worst-case analysis is preferable when a guaranteed bound matters.

Takeaway: Use OO for an upper bound, Ω\Omega for a lower bound, and Θ\Theta when the growth rate is tight; separately state whether the result is best-case, average-case, or worst-case.

Recurrences and Data-Structure Costs

Recursive algorithms call themselves on smaller instances. Their runtime is often expressed with a , and their space analysis must include the maximum call-stack depth.

Binary search performs constant additional work and continues with half the input:

T(n)=T(n2)+Θ(1).T(n)=T\left(\frac{n}{2}\right)+\Theta(1).

The input can be halved only Θ(log⁡n)\Theta(\log n) times before reaching size one, so binary search has runtime Θ(log⁡n)\Theta(\log n).

Merge sort divides the input into two halves and performs Θ(n)\Theta(n) work to merge the results:

T(n)=2T(n2)+Θ(n).T(n)=2T\left(\frac{n}{2}\right)+\Theta(n).

This recurrence solves to Θ(nlog⁡n)\Theta(n\log n). Its implementation also requires attention to the memory used during merging and to the recursion stack.

Complexity claims for data structures depend on the operation and on implementation assumptions:

  • Array index access is typically Θ(1)\Theta(1); searching an unsorted array is Θ(n)\Theta(n); insertion near the beginning can be Θ(n)\Theta(n) because elements may need to shift.

  • A linked list can insert at a known position in Θ(1)\Theta(1), but finding that position by traversal is generally Θ(n)\Theta(n), and direct random access is not typically Θ(1)\Theta(1).

  • A stack typically supports push and pop in Θ(1)\Theta(1) with an appropriate implementation.

  • A queue can support enqueue and dequeue in Θ(1)\Theta(1) with a circular array or a linked-list implementation that stores both ends.

  • A balanced search tree typically supports search, insertion, and deletion in Θ(log⁡n)\Theta(\log n) because its height remains logarithmic.

  • A hash table commonly provides expected Θ(1)\Theta(1) search, insertion, and deletion with a suitable hash function and controlled load factor, but the worst case can degrade to Θ(n)\Theta(n).

Time and space can trade off. For example, extra memory for a hash table can reduce the expected time of repeated searches compared with scanning a list.

Takeaway: For recursion, analyze work across all calls and maximum depth; for data structures, attach every bound to a specific operation and its assumptions.

Interpreting Complexity in Practice

Asymptotic analysis is a model of scalability, not a complete prediction of elapsed time. It does not directly capture constant factors, cache behavior, allocation and garbage-collection costs, input distributions, parallelism, hardware differences, implementation errors, or input/output costs.

A quadratic may outperform a linearithmic on a small input, while the linearithmic generally scales better as the input becomes large. Similarly, two algorithms with the same Θ\Theta bound can differ substantially in practical runtime because of constant factors or memory behavior.

Before accepting a complexity claim, check four points:

  1. What does the input-size variable measure?

  2. Which operations does the count?

  3. Is the claim about the best, average, or worst case?

  4. What implementation assumptions are required, such as sorted data, a balanced tree, controlled hash-table load, or a known pointer?

Use asymptotic analysis to compare scalability, then use testing or profiling to measure real implementations. A strong analysis makes its model and assumptions explicit.

Final takeaway: Complexity analysis is most useful when it connects a precise input definition and to a justified bound, a stated case, and the or implementation assumptions behind that bound.