5.2 Lower bounds and tight bounds

Big-OO notation describes an upper bound. In other words, big-OO states a claim about the greatest amount of some resource (usually time) that is required by an algorithm for some class of inputs of size nn. A similar notation is used to describe the least amount of a resource that an algorithm needs for some class of input. This is denoted by the symbol Ω\Omega, pronounced “big-Omega” or just “Omega”.

The definition for big-OO allows us to greatly overestimate the cost for an algorithm. But sometimes we know a tight bound – that is, a bound that truly reflects the cost of the algorithm or program with a constant factor. In that case, we can express this more accurate state of our knowledge using the tight bound Θ\Theta instead of using big-OO.

However, it is usually much more difficult to reason about the tight bound, for example, the simplifying rules for addition and multiplication do not hold for Θ\Theta. Furthermore, we are very rarely interested in the lower bound when we analyse algorithms, so therefore we will almost exclusively use the upper bound big-OO notation.

In this section we assume that all functions are monotonically increasing, just as we did in the previous section. If we didn’t assume this, the definitions would be slightly more complicated. But, as discussed in the previous section, the runtime of all algorithms never decreases, so it is a safe assumtion.

5.2.1 Lower bounds: the Ω\Omega notation

Big-OO notation describes an upper bound. In other words, big-OO states a claim about the greatest amount of some resource (usually time) that is required by an algorithm for some class of inputs of size nn.

A similar notation is used to describe the least amount of a resource that an algorithm needs for some class of input. Like big-OO, this is a measure of the algorithm’s growth rate. And like big-OO, it works for any resource (usually time), and for some particular class of inputs of size nn. The lower bound for an algorithm (or a problem, as we will discuss in Section 5.3) is denoted by the symbol Ω\Omega, pronounced “big-Omega” or just “Omega”. The following definition for Ω\Omega is symmetric with the definition of big-OO.

Lower bound

fΩ(g)f\in\Omega(g) iff there exist positive numbers kk and n0n_0 such that f(n)kg(n)f(n)\geq k\cdot g(n) for all n>n0n>n_0

Example: Quadratic algorithm

Assume T(n)=c1n2+c2nT(n) = c_1 n^2 + c_2 n for c1c_1 and c2>0c_2 > 0. Then,

c1n2+c2nc1n2 c_1 n^2 + c_2 n \geq c_1 n^2

for all n>1n > 1. So, T(n)cn2T(n) \geq c n^2 for c=c1c = c_1 and n0=1n_0 = 1. Therefore, T(n)T(n) is in Ω(n2)\Omega(n^2) by the definition.

It is also true that the equation of the example above is in Ω(n)\Omega(n). However, as with big-OO notation, we wish to get the “tightest” (for Ω\Omega notation, the largest) bound possible. Thus, we prefer to say that this running time is in Ω(n2)\Omega(n^2).

Recall the sequential search algorithm to find a value within an array of integers. In the worst case this algorithm is in Ω(n)\Omega(n), because in the worst case we must examine at least nn values.

Alternative definition for Ω\Omega

An alternate (non-equivalent) definition for Ω\Omega is

Lower bound (alt.)

fΩ(g)f\in\Omega(g) iff there exists a positive number kk such that f(n)kg(n)f(n)\geq k\cdot g(n) for an infinite number of values for nn.

This definition says that for an “interesting” number of cases, the algorithm takes at least kg(n)k\cdot g(n) time. Note that this definition is not symmetric with the definition of big-OO. For gg to be a lower bound, this definition does not require that f(n)kg(n)f(n) \geq k\cdot g(n) for all nn greater than some constant. It only requires that this happen often enough, in particular that it happen for an infinite number of values for nn. Motivation for this alternate definition can be found in the following example.

Assume a particular algorithm has the following behaviour:

T(n)={nfor all oddnn2/100for all evenn T(n) = \left\{ \begin{array}{ll} n & \mbox{for all odd}\ n \\ n^2/100 \;& \mbox{for all even}\ n \end{array} \right.

From this definition, n2/100kn2n^2/100 \geq k\cdot n^2 for all even nn, for any k<0.01k<0.01. So, T(n)kn2T(n) \geq k\cdot n^2 for an infinite number of values of nn. Therefore, T(n)T(n) is in Ω(n2)\Omega(n^2) by the definition.

For this equation for T(n)T(n), it is true that all inputs of size nn take at least cncn time. But an infinite number of inputs of size nn take cn2cn^2 time, so we would like to say that the algorithm is in Ω(n2)\Omega(n^2). Unfortunately, using our first definition will yield a lower bound of Ω(n)\Omega(n) because it is not possible to pick constants kk and n0n_0 such that T(n)kn2T(n) \geq k\cdot n^2 for all n>n0n>n_0. The alternative definition does result in a lower bound of Ω(n2)\Omega(n^2) for this algorithm, which seems to fit common sense more closely. Fortunately, few real algorithms or computer programs display the pathological behaviour of this example. Our first definition for Ω\Omega generally yields the expected result.

As you can see from this discussion, asymptotic bounds notation is not a law of nature. It is merely a powerful modeling tool used to describe the behaviour of algorithms.

5.2.2 Tight bounds: the Θ\Theta notation

The definitions for big-OO and Ω\Omega give us ways to describe the upper bound for an algorithm (if we can find an equation for the maximum cost of a particular class of inputs of size nn) and the lower bound for an algorithm (if we can find an equation for the minimum cost for a particular class of inputs of size nn). When the upper and lower bounds are the same within a constant factor, we indicate this by using Θ\Theta (big-Theta) notation. An algorithm is said to be Θ(h(n))\Theta(h(n)) if it is in O(h(n))O(h(n)) and it is in Ω(h(n))\Omega(h(n)). Note that we drop the word “in” for Θ\Theta notation, because there is a strict equality for two equations with the same Θ\Theta. In other words, if f(n)f(n) is Θ(g(n))\Theta(g(n)), then g(n)g(n) is Θ(f(n))\Theta(f(n)).

Because the sequential search algorithm is both in O(n)O(n) and in Ω(n)\Omega(n) in the worst case, we say it is Θ(n)\Theta(n) in the worst case.

Given an algebraic equation describing the time requirement for an algorithm, the upper and lower bounds always meet. That is because in some sense we have a perfect analysis for the algorithm, embodied by the running-time equation. For many algorithms (or their instantiations as programs), it is easy to come up with the equation that defines their runtime behaviour. The analysis for most commonly used algorithms is well understood and we can almost always give a Θ\Theta analysis for them. However, the class of NP-Complete problems all have no definitive Θ\Theta analysis, just some unsatisfying big-OO and Ω\Omega analyses. Even some “simple” programs are hard to analyse. Nobody currently knows the true upper or lower bounds for the following code fragment.

while n > 1:
    if n is odd:
        n = 3 * n + 1
    else:
        n = n / 2

But even though Θ\Theta is a more accurate description of the behaviour of an algorithm, we have chosen to almost exclusively use the upper bound big-OO notation. The reason for this because it is more difficult to reason about the tight bound than about big-OO. For example, the simplifying rules for addition and multiplication do not hold for Θ\Theta. Another reason is that most other textbooks, research papers, and programmers will usually say that an algorithm is “order of” or “big-OO” of some cost function, implicitly meaning that this is the tightest possible bound.

5.2.3 Strict bounds

The upper and lower bounds are not strict, meaning that a function is in its own class, fO(f)f\in O(f) and fΩ(f)f\in\Omega(f). We can also define strict versions of upper and lower bounds:

Strict upper bound (little-oo)

fo(g)f\in o(g) iff fO(g)f\in O(g) and fΩ(g)f\not\in\Omega(g)

Strice lower bound (ω\omega)

fω(g)f\in\omega(g) iff fΩ(g)f\in\Omega(g) and fO(g)f\not\in O(g)

5.2.4 Asymptotic notation and comparing complexity classes

We can summarise the different asymptotic notations (OO, oo, Ω\Omega, ω\omega, and Θ\Theta) in the following table:

Name Bound Notation Definition
Little-O Strict upper bound f(n)o(g(n))f(n) \in o(g(n)) |f(n)|<kg(n)|f(n)| < k\cdot g(n)
Big-O Upper bound f(n)O(g(n))f(n) \in O(g(n)) |f(n)|kg(n)|f(n)| \leq k\cdot g(n)
Theta Tight bound f(n)Θ(g(n))f(n) \in \Theta(g(n)) k1g(n)|f(n)|k2g(n)k_1\cdot g(n) \leq |f(n)| \leq k_2\cdot g(n)
Big-Omega Lower bound f(n)Ω(g(n))f(n) \in \Omega(g(n)) f(n)kg(n)f(n) \geq k\cdot g(n)
Little-Omega Strict lower bound f(n)ω(g(n))f(n) \in \omega(g(n)) f(n)>kg(n)f(n) > k\cdot g(n)

All these different bounds correspond to comparison operators between complexity classes:

Comparison Complexity class
O(f)<O(g)O(f) < O(g) fo(g)f\in o(g)
O(f)O(g)O(f) \leq O(g) fO(g)f\in O(g)
O(f)=O(g)O(f) = O(g) fΘ(g)f\in\Theta(g)
O(f)O(g)O(f) \geq O(g) fΩ(g)f\in\Omega(g)
O(f)>O(g)O(f) > O(g) fω(g)f\in\omega(g)

Using these correspondences and the simplifying rules we can infer the following hierarchy of complexity classes:

O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n2logn)<O(n3)<<O(nk)<O(2n)< O(1) < O(\log n) < O(n) < O(n\log n) < O(n^2) < O(n^2\log n) < O(n^3) < \cdots < O(n^k) < O(2^n) < \cdots

Zooming in on the very efficient (sub-linear) complexity classes we have:

O(1)<O(loglogn)<O(logn)=O(logn2)=O(logn3)<O(log2n)<O(log3n)<O(n3)<O(n)<O(n) O(1) < O(\log \log n) < O(\log n) = O(\log n^2) = O(\log n^3) < O(\log^2 n) < O(\log^3 n) < O(\sqrt[3]{n}) < O(\sqrt{n}) < O(n)

And if we instead look closer on the extreme other end of the scale:

<O(n1000)<O(1.0001n)<O(2n)<O(10n)<O(1000n)<O(n!)<O(nn)< \cdots < O(n^1000) < O(1.0001^n) < O(2^n) < O(10^n) < O(1000^n) < O(n!) < O(n^n) < \cdots

5.2.5 Classifying functions using limits

There are alternative definitions of the upper, lower and tight bounds. Instead of finding constants kk and n0n_0, we can see how the quotient between the two functions behave in the limit.

Given functions ff and gg, we can take the limit of the quotient of the two as nn grows towards infinity:

limnf(n)g(n) \lim_{n \rightarrow \infty} \frac{f(n)}{g(n)}

We have the following possibilities:

Name Notation Limit, lim(f/g)k\lim(f/g) \rightarrow k
Little-O fo(g)f \in o(g) k=0k = 0
Big-O fO(g)f \in O(g) k<k < \infty
Theta fΘ(g)f \in \Theta(g) 0<k<0 < k < \infty
Big-Omega fΩ(g)f \in \Omega(g) k>0k > 0
Little-Omega fω(g)f \in \omega(g) k=k = \infty

Example: Comparing two functions

Assume f(n)=n2f(n) = n^2 and g(n)=1000nlogng(n) = 1000n\log n. Is ff in O(g)O(g), Ω(g)\Omega(g), or Θ(g)\Theta(g)? To answer this we can calculate the limit of the quotient f(n)/g(n)f(n)/g(n) when nn grows:

limnf(n)g(n)=limnn21000nlogn=11000limnnlogn= \lim_{n \rightarrow \infty} \frac{f(n)}{g(n)} = \lim_{n \rightarrow \infty} \frac{n^2}{1000n\log n} = \frac{1}{1000}\cdot\lim_{n \rightarrow \infty} \frac{n}{\log n} = \infty

because nn grows faster than logn\log n. Thus, fΩ(g)f\in\Omega(g) (or equivalently, gO(f)g\in O(f)).