lecture 2

Analysis of Algorithms

The analysis of an algorithm answers the question about how much an algorithm uses resources for a problem as a function of size of the problem.

Why analysis

Analysis and Synthesis are compliment.  If you want to design some program you would be better of having some guidance about its behaviour in order to explore the alternative designs.  To do something that has never been done before needs a lot of foresight (the ability to anticipate the future).

Relationship between algorithmics and other subjects

Analysis requires mathematics.  The model of computation uses for analysis is based on formal languages.  (where you learn theory of computation and model of computation such as Turing machines). Discrete mathematics gives you the basic tools for mathematical analysis (such as induction proof, counting problems, number theory).  The subject in design such as Logic design is a 'concrete' idea of the abtract (in model of computation).  A finite state machine is a special form of a more general Turing machine (with restriction of no move back to reread the input tape).  The model of computation we use in this course is a single processor, executing an instruction one at a time, and have a random access memory (large enough to hold the entire problem and the solution so that accessing to secondary storage is of no concern to us).

Size of the problem

--  number of bits (with a reasonable representation)
--  for a set, size is the number of elements in the set
--  for a graph, size is the number of nodes and edges
--  for a number, sometimes size is the "value" of that number

The resource can be the time and the space.  We will concentrate mostly on time in this course.  However, sometimes 'time' and 'space' can be trade-off.

Comparing running time

Principle of invariance
t1(n) <= c t2(n)
t2(n) <= d t1(n)   for any c, d
means two algorithms are "similar" in efficiency.
(their running times are differred not more than a constant factor)

n -- linear
n^2 -- quadratic
n^3 -- cubic
n^k -- polynomial
c^n -- exponential

The problem is said to be 'easy' if it is in the group of 'polynomial' and is 'hard' if it is in the group of 'exponential'.

asymtotic  "in the order of t(n)"

average case, worst case

The algorithm must accept "every" instances of size n.  The behaviour of some algorithm depends on the instance of input for example insertion sort vs selection sort.

              best     worst
Insertion     n       n^2
Selection     n^2     n^2

to analyse average case, the distribution of instances of size n must be known.

elementary operations

t <= a t1 + b t2 + c t3    a,b,c are the number of times
  <= max(t1,t2,t3) * (a+b+c)

We call the running time of each operation -- unit cost

efficiency

We will illustrate the different of efficiency between algorithms in solving some well-known problems.

find determinant
-- recursive def                   n!
-- Gauss-Jordan                    n^3
-- Strassen (1971) divide-conquer  n^lg7 = n^2.81

sorting
                                    average          worst
--  quicksort                   n log n          n^2
--  heapsort (Williams 1964) n log n      n log n
--  mergesort               n log n             n log n
--  pigeonhole sort            n                  n
--  multiplication m, n                        mn
--  mul divide-conquer                    n m^log(3/2)
      if n = m, n^1.59
--  mul best (ref?)                           n log n log log n

show multiplication normal and divide and conquer

normal mul

     981
    1234
    ----
    3924
   2943
  1962
  981
  ------
 1210554

divide and conquer

let's divide the number into two digits multiplications

        shift
09  12   4  108....
09  34   2    306..
81  12   2    972..
81  34   0     2754
            -------
            1210554

each two digits can be furthur divided into one digit mul.
(09 * 12)

0  1  2  0..
0  2  1   0.
9  1  1   9.
9  2  0   18
         ---
         108

show analysis

simple loops and conditions

                        cost     times
insertion-sort(A)
for j=2 to length(A)      c1     n
  key = A[j]              c2     n-1
  i = j - 1               c4     n-1
  while i>0 and A[i]>key  c5    sum j=2 to n (t_j)
    A[i+1] = A[i]         c6    sum j=2 to n (t_j - 1)
    i = i - 1             c7    sum j=2 to n (t_j - 1)
  A[i+1] = key            c8     n-1

t_j is the number of times the while loop is executed for that value of j.

t(n) = c1 n + c2(n-1) + c4(n-1) + c5 sum j=2 to n (t_j) +
   c6 sum j=2 to n (t_j - 1) + c7 sum j=2 to n (t_j - 1) + c8(n-1)

Best case, input is already sorted
t_j = 1 for j = 2,3,...

t(n) = c1 n + c2(n-1) + c4(n-1) + c5(n-1) + c8(n-1)
  = (c1 + c2 + c4 + c5 + c8)n - (c2 + c4 + c5 + c8)
  = an + b

Worst case, reverse sort order

t(n) = c1 n + c2(n-1) + c4(n-1) + c5 sum j=2 to n (t_j) +
   c6 sum j=2 to n (t_j - 1) + c7 sum j=2 to n (t_j - 1) + c8(n-1)

use some identities:
sum j=2 to n (j) = n(n+1)/2 - 1
sum j=2 to n (j-1) = n(n-1)/2

t(n) = c1 n + c2(n-1) + c4(n-1) + c5(n(n+1)/2 - 1) +
   c6(n(n-1)/2) + c7 (n(n-1)/2) + c8(n-1)
  = (c5/2 + c6/2 + c7/2) n^2 +
    (c1 + c2 + c4 + c5/2 - c6/2 - c7/2 + c8)n - (c2 + c4 + c5 + c8)
  = an^2 + bn + c

We will show an analysis of a recursive program.

divide-conquer approach

merge-sort(A,p,r)
if p<r
  then  q = floor( (p+r)/2 )    // p <= q < r
    merge-sort(A,p,q)
    merge-sort(A,q+1,r)
    merge(A,p,q,r)

call merge-sort(A,1,length(A)).   merge  takes  time big-theta(n)  n = r - p + 1

divide problem into 'a' subproblems of size 'b' each
D is dividing time
C is combining time

t(n) = big-theta(1)   if n <= c
   at(n/b) + D(n) + C(n) otherwise

big-theta(1) is a constant time

Analysis  assume size is the power of 2
D(n) = big-theta(1)
time for subproblems = 2 t(n/2)
C(n) = big-theta(n)   // merge

t(n) = big-theta(1)  if n <= c
  2 t(n/2) + big-theta(1) + big-theta(n)

The solution of the above recurrent equation is : t(n) = big-theta(n lg n)

Lower bound (sec 9.1 in CLR)

Lower bound is very important.  The analysis is done on the problem. It indicates that the 'efficiency' gap between the known algorithm and the difficulty of the problem.

lower bound of comparison sort
decision tree
binary tree , for sorting n items the leaves are n!

Thm :  the height is big-ohmega(n lg n)

Proof
h = height
n! <= 2^h
h >= lg(n!)
n! > (n/e)^n
h >= lg(n/e)^n
h >= n lg n - n lg e
h = big-omega(n lg n)

In this case, we have proof that the merge-sort algorithm is optimal because it is as good as the lower-bound of the problem. (sorting by comparison).

Mathematical notation about logarithm

for b != 1 and x positive real
log_b x = y
b^y = x
i.e.  log_10 1000 = 3

lg   denotes  log_2
log  denotes  log_10

some property:
log_a x = log_b x /  log_b a
log^2 n = (log n)^2  != log log n

lg * n   is the number of times in taking log until the value <= 1
(see def. in CLR p.36 iterated log function)

lg* 65536 = 4

lg 65536 = 16
lg 16 = 4
lg 4 = 2
lg 2 = 1