(a) computer used, the harware platform (b) representation of abstract data types (ADT's) (c) efficiency of compiler (d) competence of implementer (programming skills) (e) complexity of underlying algorithm (f) size of the inputWe will show that of those above (e) and (f) are generally the most significant
t(n) = 60*n*n + 5*n + 1
for input of size n.
Assume we have differing machine and compiler combinations, then it is safe to say that
t(n) = n*n + 5*n/60 + 1/60
That is, we ignore the coefficient that is applied to the most
significant (dominating) term in t(n). Consequently this only affects
the "units" in which we measure. It does not affect how the worst case
time grows with n (input size) but only the units in which we measure
worst case time
Under these assumptions we can say ...
"t(n) grows like n*n as n increases"
or
t(n) = O(n*n)
which reads "t(n) is of the order n squared" or as "t(n) is big-oh n squared"
In summary, we are interested only in the dominant term, and we ignore coefficients.
A = (log2 n) {log to base 2 of n}
B = n {linear in n}
C = (* n (log2 n)) {n log n}
D = (* n n) {quadratic in n}
E = (* n n n) {cubic in n}
F = (expt 2 n) {exponential in n}
G = (expt 3 n) {exponential in n}
H = (fact n) {factorial in n}
n A B C D E F G H
1 0.0 1 0.0 1 1 2 3 1
2 1.0 2 2.0 4 8 4 9 2
3 1.0 3 4.0 9 27 8 27 6
4 2.0 4 8.0 16 64 16 81 24
5 2.0 5 11.0 25 125 32 243 120
6 2.0 6 15.0 36 216 64 729 720
7 2.0 7 19.0 49 343 128 2187 5040
8 3.0 8 24.0 64 512 256 6561 40320
9 3.0 9 28.0 81 729 512 19683 362880
10 3.0 10 33.0 100 1000 1024 59049 3628800
Think of this as algorithms A through H with complexities as defined
above, showing growth rate versus input size n.
Tabulated below are functions F, G and H from above (ie 2 to power n,
3 to power n, and n factorial). Problem size n varies from 10 to 100
in steps of 10. I have assumed that we have a machine that can perform
"the principle activity of the algorithm" in a micro second (ie. if we
are considering a graph colouring algorithm, it can compare the colour
of two nodes in a millionth of a second). The columns give the number
of years this machine would take to execute those algorithms on
problems of size n (note: YEARS). This is expressed as 10^x, "10 raised
to the power x".
n F G H
10 10^-10 10^-8 10^-6
20 10^-7 10^-3 10^4
30 10^-4 10^0 10^18
40 10^-1 10^5 10^34
50 10^1 10^10 10^50
60 10^4 10^15 10^68
70 10^7 10^19 10^86
80 10^10 10^24 10^105
90 10^13 10^29 10^124
100 10^16 10^34 10^144
Therefore, if we have a problem of size (lets say 40) and the machine
specified above, if the best algorithm is O(2**n) it will take 1 year,
if the best algorithm is O(3**n) it will take 100,000 years, and
if the best algorithm is O(n!) it will take
10,000,000,000,000,000,000,000,000,000,000,000 years
approximately. Out of interest, the age of the universe is estimated to
be between 15 and 20 billion years old, ie 20,000,000,000 years.
That is, even at modest values of n we are presented with problems that
will never be solved. It is almost tempting to say, that from a
computational perspective, the universe is a small thing.
A.t(n) = 100*n*n milliseconds
B.t(n) = 5*n*n*n milliseconds
Should we always choose A, because A is O(n*n) and B is O(n*n*n)
n A B
1 0.1 0.005
2 0.4 0.04
3 0.9 0.135
4 1.6 0.32
5 2.5 0.625
6 3.6 1.08
7 4.9 1.715
8 6.4 2.56
9 8.1 3.645
10 10 5
11 12.1 6.655
12 14.4 8.64
13 16.9 10.985
14 19.6 13.72
15 22.5 16.875
16 25.6 20.48
17 28.9 24.565
18 32.4 29.16
19 36.1 34.295
The table above gives the run times for A and B with varying size of
input. As can be seen, although B is cubic (ie O(n*n*n)) it is a better
algorithm to use so long as n<20. Consequently, things aren't as clear
cut as we might think. When choosing an algorithm it helps to know
something about the environment in which it will be run.
Other considerations when coosing an algorithm:
1. How often will the program be used? If only once, or a few times
do we care about run time? Will it take longer to code than to run
for the few times it is used?
2. Will it only be used on small inputs, or large inputs.
3. An efficient algorithm might require carefull coding, be difficult
to implement, difficult to understand, and difficult to maintain.
Can we afford those expenses?