Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Asymptotic Notation

Open in Colab

First, let’s review some asymptotic notation. We’ll see it a fair amount in this course.

Let f(x),g(x)f(x), g(x) be some functions. We say

f=O(g)f = O(g)

as x→∞x\to \infty if there exists some constant c≥0c\ge 0 and x0≥0x_0\ge 0 so that ∣f(x)∣≤cg(x) |f(x)| \le c g(x) for all x≥x0x \ge x_0.

We say

f=Ω(g)f = \Omega(g)

as x→∞x\to \infty if there exists some constant c≥0c\ge 0 and x0≥0x_0\ge 0 so that ∣f(x)∣≥cg(x) |f(x)| \ge c g(x) for all x≥x0x \ge x_0.

Finally,

f=Θ(g)f = \Theta(g)

if f=O(g)f = O(g) and f=Ω(g)f = \Omega(g) (some constant multiples of gg bound ff above and below).

Often we’re interested in bounding worst-case behavior. This means you’ll see a lot of OO, and less of Ω\Omega and Θ\Theta.

You may also see little-o notation such as f=o(g)f = o(g), or the corresponding f=ω(g)f = \omega(g). In these cases the inequalities are strict. If f=o(g)f = o(g), then for any c>0c > 0, there exists an x0≥0x_0\ge 0 so that ∣f(x)∣<cg(x)|f(x)| < c g(x) for x≥x0x\ge x_0. If f=O(g)f = O(g) it can grow at the same rate, but if f=o(g)f = o(g), it grows at a slower rate.

Note that because we can scale by constant multiples, we typically drop them.

  1. O(5x)=O(x)O(5x) = O(x)

  2. O(c)=O(1)O(c) = O(1)

  3. O(cf(x))=O(f(x))O(c f(x)) = O(f(x))

Examples

  1. x=O(x)x = O(x)

  2. x=O(x2)x = O(x^2)

  3. xa=O(xb)x^a = O(x^b) for a≤ba \le b

  4. xa=O(bx)x^a = O(b^x) for any a,b>1a, b > 1

  5. f=O(g)f = O(g) and g=O(h)g = O(h) implies f=O(h)f = O(h) (exercise: prove this)

  6. f=O(g)f = O(g) and h=O(k)h = O(k) implies f×h=O(g×k)f\times h = O(g \times k) (exercise: prove this as well)

  7. log⁡n=O(nε)\log n = O(n^{\varepsilon}) for any ε>0\varepsilon > 0. Logarithm growth is slower than any polynomial.

Answers to example 5 and 6

Answer to example 5:

By definition, since f=O(g)f = O(g), we know there exists some constant p≥0p\ge 0 and x0≥0x_0\ge 0 so that ∣f(x)∣≤pg(x) |f(x)| \le p g(x) for all x≥x0x \ge x_0; since g=O(h)g = O(h), we know there exists some constant q≥0q\ge 0 and x1≥0x_1\ge 0 so that ∣g(x)∣≤qh(x) |g(x)| \le q h(x) for all x≥x1x \ge x_1. Therefore, we get ∣f(x)∣≤pg(x) |f(x)| \le p g(x) ≤(p∗q)h(x) \le (p*q) h(x) as x≥x \ge max⁡\max {x0x_0, x1x_1}, which implies f=O(h)f = O(h).

Answer to example 6: Still, by definition, since f=O(g)f = O(g), we know there exists some constant p≥0p\ge 0 and x0≥0x_0\ge 0 so that ∣f(x)∣≤pg(x) |f(x)| \le p g(x) for all x≥x0x \ge x_0; since h=O(k)h = O(k), we know there exists some constant q≥0q\ge 0 and x1≥0x_1\ge 0 so that ∣h(x)∣≤qk(x) |h(x)| \le q k(x) for all x≥x1x \ge x_1. Now, take x′x' = max⁡\max {x0x_0, x1x_1}, we know as x≥x′x\ge x', we have both ∣f(x)∣≤pg(x) |f(x)| \le p g(x) and ∣h(x)∣≤qk(x) |h(x)| \le q k(x). Therefore, now we have: ∣(f×h)(x)∣≤∣f(x)∣∗∣h(x)∣≤(p∗q)g(x)k(x)=(p∗q)(g×k)(x)|(f\times h) (x)| \le |f(x)| * |h(x)| \le (p*q) g(x) k(x) = (p*q) (g\times k) (x), proved.

Complexity

When we talk about computational complexity, we are typically talking about the time or memory resources needed for an algorithm.

For example, if an algorithm operates on an array of nn elements, we say the algorithm runs in O(n2)O(n^2) time if the run time scales (at worst) quadratically with the size nn.

For example, consider the following function:

our maximum function loops over the list once, and at each step of the iteration we do a constant amount of work. If maximum takes in an array of length n as input, this means there are n iterations of the for-loop, so maximum takes O(n)O(n) time to compute. Note that we don’t need to create any additional arrays, so we can also say maximum uses O(1)O(1) (constant) extra space.

When plotting functions with polynomial complexity (O(xa)O(x^a) for some aa), it is standard to use a loglog plot.

Why? If t(n)=Θ(na)t(n) = \Theta(n^a), then

t(n)∼cnalog⁡t(n)∼alog⁡n+log⁡c\begin{align*} t(n) &\sim c n^a\\ \log t(n) &\sim a \log n + \log c \end{align*}

I.e. the polynomial exponent is the slope of the line.

Let’s now consider the following function

we see that the length of output of all_subarrays grows as 2n2^n, where n = len(x). This is a good hint that the function has exponential time complexity and space complexity O(2n)O(2^n).

In this case, it is better to use a semilogy plot.

Exercise

Why does a semilogy plot make sense for plotting the time it takes to run a function with exponential time complexity?

How can you interpret the expected slope?

Notebook Cell

For a function with exponential time complexity, if we do not use semilogy plot, the values on the y-axis could be distributed in a large range. Also, using semilogy plot could provide us a nicer visualization since linear-like pattern could be shown.