Contents / Previous / Next


Algorithmic Complexity Measures

Our aim is to compare the performance of algorithms.

We can consider the average case or the worst case performance (refering to an instance of a given problem).

Usually we treat only the worst case performance:
The worst case occurs fairly often, example: looking for in entry in a database that is not present.
The result of the worst case analysis is often not different to the average case analysis (same order of complexity).



Running Times

One way to compare running times is simply to run several tests for each algorithm and compare the timings.

Another way is to estimate the time required for an algorithm to solve a problem.

The result of the analysis of an algorithm is usually a formula giving the amount of machine operations (e.g., floating point operations, number of memory accesses, number of comparisons, etc ) or some other metric (e.g., number of elements to sort, neurons or connections in a neural network, vertices or edges in a tree, etc. ), that the algorithm takes to complete.

In the following we write running times as a function of n.



Asymptotic Notation

If we want to treat large problems (these are the critical ones), we are interested in the asymptotic behavior of the growth of the running time function.

Thus, when comparing the running times of two algorithms:
Constant factors can be ignored.
Lower order terms are unimportant when the higher order terms are different.

For instance, when we analyze selection sort, we find that it takes T(n) = n2 + 3n - 4 array accesses.
For large values of n, the 3n - 4 part is insignificant compared to the n2 part.

An algorithm that takes a time of 100n2 will still be faster
than an algorithm that n3 for any value of n larger than 100.


Asymptotically Tight Upper and Lower Bound: Big-Theta "Θ"-Notation

Definition: Let f(n) and g(n) be real-valued functions of a single non-negative integer argument. We write f(n) = Θ(g(n)) if there exist positive real-valued constants c1, c2 and a positive integer n0 such that:

0 ≤ c1 g(n) ≤ f(n) ≤ c2 g(n) for all n ≥ n0 .

In other words: For large inputs (asymptotically) f(n) is "sandwiched" between c1 g(n) and c2 g(n) .

Examples:

2n2+3n is θ(n2).
2n3+3n is not θ(n2).


Asymptotically Tight Upper Bound: Big-Oh "O"-Notation

Definition: Let f(n) and g(n) be real-valued functions of a single non-negative integer argument. We write f(n) = O(g(n)) if there exist a positive real-valued constant c and a positive integer n0 such that:

0 ≤ f(n) ≤ c g(n) for all n ≥ n0 .

In other words: For large inputs (asymptotically) f(n) is below c g(n):

The "Θ"-Notation is stronger than the O-notation.

Examples:

3n-6 is O(n).
9n4+12n2+1234 is O(n4).
n+log(n) is O(n).
log(n)+5log(log(n)) is O(log(n)).
1234554321 is O(1).
3/n is O(1/n).


Asymptotically Tight Lower Bound: Big-Omega "Ω"-Notation

Definition: Let f(n) and g(n) be real-valued functions of a single non-negative integer argument. We write f(n) = Ω(g(n)) if there exist a positive real-valued constant c and a positive integer n0 such that:

0 ≤ c g(n) ≤ f(n) for all n ≥ n0 .

In other words: For large inputs (asymptotically) f(n) is above c g(n) .

Note:
We have f(n) = Θ(g(n)) iff (if and only if)
f(n) = O(g(n)) and f(n) = Ω(g(n)).

In other words:
If f(n) is Θ(g(n)), the f(n) is Ω(g(n)).
If f(n) is Θ(g(n)), then f(n) is O(g(n)).  

Example:

2n3+3n is Ω(n3).


Asymptotically Non-Tight Upper Bound: Little-oh "o"-Notation

Definition: Let f(n) and g(n) be real-valued functions of a single non-negative integer argument. We write f(n) = o(g(n)) if for any positive real-valued constant c exists a positive integer n0 such that:

0 ≤ f(n) ≤ c g(n) for all n ≥ n0 .

In other words: The inequality holds for all constants c.
For any constant c we can come up with large enough inputs n so that f(n) is below c g(n) .

g(n) grows a lot faster than f(n) : f(n)/g(n)→0 as n→∞.

Example:

log(n) is o(n)


Asymptotically Non-Tight Lower Bound: Little-Omega "ω" Notation

Definition: Let f(n) and g(n) be real-valued functions of a single non-negative integer argument. We write f(n) = ω(g(n)) if for any positive real-valued constant c exists a positive integer n0 such that:

0 ≤ c g(n) ≤ f(n) for all n ≥ n0 .

As an alternative definition we can say the following.

Definition: Let f(n) and g(n) be real valued functions of an integer variable. We say f(n) is ω(g(n)) if g(n) is o(f(n)). This is pronounced as "f(n) is little-omega of g(n)".

f(n) grows a lot faster than f(n) : f(n)/g(n)→∞ as n→∞.

Example:

n2 is ω(nlog(n)).



Space Complexity

Running time is usually the thing we care most about. But it can be as well important to analyze the amount of memory used by a program.

If a program takes a lot of time, you can still run it, and just wait longer for the result.
However if a program takes a lot of memory, you may not be able to run it at all, so this is an important parameter to understand.

We analyze things differently for recursive and iterative programs.


For an iterative program, it is usually just a matter of looking at the variable declarations and storage allocation calls, e.g., array of n numbers.


Analysis of recursive program space is more complicated: the space used at any time is the total space used by all recursive calls active at that time.

Each recursive call takes a constant amount of space: some space for local variables and function arguments, and also some space for remembering where each call should return to.