Lecture 04
Lecture 04
Algorithm Analysis
Lecture Recap
In our last session we cover following:
• Asymptotic Analysis
• Justification for analysis
• Quadratic and polynomial growth
• Counting machine instructions
• Landau symbols
• Big-Q as an equivalence relation
• Little-o as a weak ordering
Lecture Agenda
Today we will cover following:
• We will introduce machine instructions
• We will calculate the run times of:
• Operators +, -, =, +=, ++, etc.
• Control statements if, for, while, do-
while, switch
• Functions
• Recursive functions
Motivation
• The goal of algorithm analysis is to take a block of code
and determine the asymptotic run time or asymptotic
memory requirements based on various parameters
• Given an array of size n:
• Selection sort requires Q(n2) time
• Merge sort, quick sort, and heap sort all require Q(n ln(n)) time
• However:
• Merge sort requires Q(n) additional memory
• Quick sort requires Q(ln(n)) additional memory
• Heap sort requires Q(1) memory
Motivation
The asymptotic behaviour of algorithms indicates
the ability to scale
• Suppose we are sorting an array of size n
However:
• Merge sort will require twice and 10 times as much memory
• Quick sort will require one or four additional memory
locations
• Heap sort will not require any additional memory
Motivation
We will see algorithms which run in Q(nlg(3)) time
• lg(3) ≈ 1.585
• For 2n entries require (2n)lg(3) = 2lg(3) nlg(3) = 3 nlg(3)
• Three times as long to sort
• 10n entries require (10n)lg(3) = 10lg(3) nlg(3) = 38.5 nlg(3)
• 38 times as long to sort
Motivation
We will see algorithms which run in Q(nlg(3)) time
• lg(3) ≈ 1.585
• For 2n entries require (2n)lg(3) = 2lg(3) nlg(3) = 3 nlg(3)
• Three times as long to sort
• 10n entries require (10n)lg(3) = 10lg(3) nlg(3) = 38.5 nlg(3)
• 38 times as long to sort
• Functions
• Recursive functions
Machine Instructions
Given any processor, it is capable of performing only a limited
number of operations
delete[] array_old;
} Q(1) or W(n)
To calculate the total run time, add the entries: Q(1 + n + 1) = Q(n)
Blocks in Sequence
• This is taken from code found at
http://ece.uwaterloo.ca/~ece250/Algorithms/Sparse_systems/
return max;
}
Analysis of Statements
In this case, we don’t know
is identical to
int i = 0; // initialization
while ( i < N ) { // condition
// ...
++i; // increment
}
Condition-controlled Loops
The initialization, condition, and increment usually are single
statements running in Q(1)
For example,
for ( int i = 0; i < n; ++i ) {
// ...
}
is Q(n f(n))
If the body is O(f(n)), then the run time of the loop is O(n f(n))
Condition-controlled Loops
For example,
int sum = 0;
for ( int i = 0; i < n; ++i ) {
sum += 1; Theta(1)
}
The previous example showed that the inner loop is Q(n), thus
the outer loop is
Q(n·n) = Q(n2)
Analysis of Repetition Statements
Suppose with each loop, we use a linear search an array of size
m:
for ( int i = 0; i < n; ++i ) {
// search through an array of size m
// O( m );
}
max_height = 0; Q(1)
num_disjoint_sets = n;
n 1 n 1
n n 1
+ 1) ) = Q(i) hence 2the outer is
2
The inner 1 1 i
is Q(1 + i(1
loop 1 n i 1 n n
i 0 i 0
Analysis of Repetition Statements
As another example:
int sum = 0;
for ( int i = 0; i < n; ++i ) {
for ( int j = 0; j < i; ++j ) {
for ( int k = 0; k < j; ++k ) {
sum += i + j + k;
}
}
}
switch( i ) {
case 1: /* do stuff */ break;
case 2: /* do other stuff */ break;
case 3: /* do even more stuff */ break;
case 4: /* well, do stuff */ break;
case 5: /* tired yet? */ break;
default: /* do default stuff */
}
Control Statements
Thus, a switch statement would appear to run in O(n) time
where n is the number of cases, the same as nested if
statements
• Why then not use:
if ( i == 1 ) { /* do stuff */ }
else if ( i == 2 ) { /* do other stuff */ }
else if ( i == 3 ) { /* do even more stuff */ }
else if ( i == 4 ) { /* well, do stuff */ }
else if ( i == 5 ) { /* tired yet? */ }
else { /* do default stuff */ }
Control Statements
Question:
Why would you introduce something into
programming language which is redundant?
First, you may recall that the cases must be actual values,
either:
• integers
• characters
Then the compiler simply makes a jump size based on the variable,
jumping ahead either 0, 24, 48, 72, ..., or 240 instructions
Serial Statements
Suppose we run one block of code followed by
another block of code
For example,
O(n) + O(n2) + O(n4) = O(n + n2 + n4) = O(n4)
O(n) + Q(n2) = Q(n2)
O(n2) + Q(n) = O(n2)
O(n2) + Q(n2) = Q(n2)
Functions
A function (or subroutine) is code which has been separated
out, either to:
• and repeated operations
• e.g., mathematical functions
• group related tasks
• e.g., initialization
Functions
Because a subroutine (function) can be called from anywhere,
we must:
• prepare the appropriate environment
• deal with arguments (parameters)
• jump to the subroutine
• execute the subroutine
• deal with the return value
• clean up
Functions
Fortunately, this is such a common task that all modern
processors have instructions that perform most of these steps in
one instruction
That is, it is impossible for any function call to have a zero run
time
Functions
Thus, given a function f(n) (the run time of which depends on n)
we will associate the run time of f(n) by some function Tf(n)
• We may write this to T(n)
if ( m == n ) {
return;
}
if ( tree_height[m] == tree_height[n] ) {
++( tree_height[m] );
Q(1)
max_height = std::max( max_height, tree_height[m] );
}
} else {
parent[m] = n;
}
}
Recursive Functions
If k = n – 1 then
T!(n) = T!(n – (n – 1)) + n – 1
= T!(1) + n – 1
=1+n–1=n
We could:
• go through the list and find the largest item
• swap the last entry in the list with that largest item
• then, go on and sort the rest of the array
1
1 2
n n
> expand( % ); 2 2
Recursive Functions
Consequently, the sorting routine has the run time
T(n) = Q(n2)
To see this by hand, consider the following
T( n ) T( n 1) n
T( n 2) ( n 1) n
T( n 2) n ( n 1)
T( n 3) n ( n 1) ( n 2)
n n n
n( n 1)
T(1)
i 2
i 1
i 2
i i
i 1
2
Recursive Functions
Consider, instead, a binary search of a sorted list:
• Check the middle entry
• If we do not find it, check either the left- or right-hand side, as
appropriate
Solving this can be difficult, in general, so we will consider only special values
of n
because T(1) = 1
Recursive Functions
Thus, T(n) = k, but n = 2k – 1
Therefore k = lg(n + 1)
f n
lim c 0c
n g n
However, recall that f(n) = Q(g(n)) if for
1
lg n 1 n 1 ln 2 n 1
lim lim lim
n ln n n 1 n n 1 ln 2 ln 2
n
If the list is of size n, then there is a 1/n chance of it being in the kth location
Thus, we sum
1 n 1 n(n 1) n 1
n k 1
k
n 2
2
which is O(n)
Cases
We could write:
n
i i
k 1 2
k
k ?
k 1 2
Cases
You’re not expected to know this for the final, however, for
interest:
return max;
}
Cases
This example is taken from Preiss
• The best case was once (first element is largest)
• The worst case was n times
return max; n 1 1
} 1 1 1 n ln(n) n
i 1 i 1
is
Lecture Recap
Today we covered following:
• We will introduce machine instructions
• We will calculate the run times of:
• Operators +, -, =, +=, ++, etc.
• Control statements if, for, while, do-while,
switch
• Functions
• Recursive functions
End of Lecture
04