Professional Documents
Culture Documents
Algorithm Analysis
Algorithm Analysis
Algorithm Analysis
Lecture 2
MHA,2015,AAiT
Algorithm Analysis
• The goal of algorithm analysis is to take a block code
and determine the asymptotic run time and asymptotic
memory requirement based on various parameters:
• Given an array of size n:
– Selection sort requires O(n2) time
– Merge sort , quick sort, and heap sort all require O(nln(n))
time
• However
– Merge sort requires O(n) additional memory
– Quick sort requires O(ln(n)) additional memory
– Heap sort requires O(1) memory
MHA,2015,AAiT
Algorithm Analysis
We will examine the code to determine the
runtime of various operations asymptotically:
• Operators +,-,=,+=,++, etc
• Control structures , if , for , while, do while,
switch
• Functions,
• Recursive functions
MHA,2015,AAiT
Worst case analysis
• Even measuring run time as a function of input size is
not really well defined, because for example , it may be
possible to sort a list that is already sorted, than it is to
sort a list that is randomly permuted. For this reason ,
we usually talk about worst case running time . Over all
possible inputs of size n, what is the maximum running
time. It is often more reasonable to consider expected
case running time where we average over all inputs of
size n. we will usually do worst case analysis, except
where it is clear that the worst case is significantly
different from the expected case.
MHA,2015,AAiT
Review of Asymptotics
MHA,2015,AAiT
Review of Asymptotics
MHA,2015,AAiT
MHA,2015,AAiT
Example
the time taken to find the largest object in an array of n random integers
will take n operations
return max;
}
MHA,2015,AAiT
Landau symbols
A function f(n) = O(g(n)) if there exists N
and c such that
f(n) < c g(n)
whenever n > N
– The function f(n) has a rate of growth no
greater than that of g(n)
MHA,2015,AAiT
Landau symbols
Before we begin, however, we will make some
assumptions:
– Our functions will describe the time or memory
required to solve a problem of size n
– We conclude we are restricting ourselves to
certain functions:
• They are defined for n ≥ 0
• They are strictly positive for all n
– In fact, f(n) > c for some value c > 0
– That is, any problem requires at least one instruction and
byte
• They are increasing (monotonic increasing)
MHA,2015,AAiT
Landau symbols
Another Landau symbol is Q
MHA,2015,AAiT
2.3.5 Landau Symbols
These definitions are often unnecessarily
tedious
lim
f( n )
c 0c
n g( n )
MHA,2015,AAiT
Landau symbols
MHA,2015,AAiT
Landau symbols
MHA,2015,AAiT
Landau symbols
MHA,2015,AAiT
Landau symbols
MHA,2015,AAiT
Landau symbols
MHA,2015,AAiT
Landau symbols
MHA,2015,AAiT
Operators
MHA,2015,AAiT
Operators
Of these, memory allocation and deallocation
are the slowest by a significant factor
– A quick test on unix shows a factor of over 100
– They require communication with the operation
system
– This does not account for the time required to call
the constructor and destructor
MHA,2015,AAiT
Block of operations
Each operation runs in Q(1) time and therefore any
fixed number
of operations also run in Q(1) time, for example:
// Swap variables a and b
int tmp = a;
a = b;
b = tmp;
MHA,2015,AAiT
Blocks in sequence
MHA,2015,AAiT
Block in sequence
MHA,2015,AAiT
Block in sequence
MHA,2015,AAiT
Control Statements
Given any collection of nested control
statements, it is always necessary to work
inside out
– Determine the run times of the inner-most
statements and work your way out
MHA,2015,AAiT
Control Statements
Given
if ( condition ) {
// true body
} else {
// false body
}
MHA,2015,AAiT
Control Statements
In some cases, it is easy to determine
which statement must be run:
int factorial ( int n ) {
if ( n == 0 ) {
return 1;
} else {
return n * factorial ( n – 1 );
}
}
MHA,2015,AAiT
Control Statements
In others, it is less obvious
– Find the maximum entry in an array:
int find_max( int *array, int n ) {
max = array[0];
return max;
}
MHA,2015,AAiT
Analysis of Statements
In this case, we don’t know
MHA,2015,AAiT
Condition-controlled Loops
The C++ for loop is a condition controlled
statement:
for ( int i = 0; i < N; ++i ) {
// ...
}
is identical to
int i = 0; // initialization
while ( i < N ) { // condition
// ...
++i; // increment
}
MHA,2015,AAiT
Condition-controlled Loops
The initialization, condition, and increment
usually are single statements running in
Q(1)
for ( int i = 0; i < N; ++i ) {
// ...
}
MHA,2015,AAiT
Condition-controlled Loops
The initialization, condition, and increment
statements are usually Q(1)
For example,
for ( int i = 0; i < n; ++i ) {
// ...
}
is Q(n f(n))
MHA,2015,AAiT
Condition-controlled Loops
For example,
int sum = 0;
for ( int i = 0; i < n; ++i ) {
sum += 1; Theta(1)
}
MHA,2015,AAiT
Condition-controlled Loops
Another example example,
int sum = 0;
for ( int i = 0; i < n; ++i ) {
for ( int j = 0; j < n; ++j ) {
sum += 1; Theta(1)
}
}
MHA,2015,AAiT
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;
MHA,2015,AAiT
Analysis of repetition statement
MHA,2015,AAiT
Analysis of repetition statement
MHA,2015,AAiT
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;
}
}
}
MHA,2015,AAiT
Control Statements
Switch statements appear to be nested if
statements:
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 */
}
MHA,2015,AAiT
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 */ }
MHA,2015,AAiT
Control Statements
Question:
Why would you introduce something into
programming language which is redundant?
MHA,2015,AAiT
Control Statements
However, switch statements were included in the
original C language... why?
MHA,2015,AAiT
Control Statements
The compiler looks at the different cases and
calculates an appropriate jump
MHA,2015,AAiT
Serial Statements
Consider the following two problems:
– search through a random list of size n to find
the maximum entry, and
– search through a random list of size n to find if
it contains a particular entry
MHA,2015,AAiT
Serial Statements
Searching for the maximum entry requires
that each element in the array be
examined, thus, it must run in Q(n) time
MHA,2015,AAiT
Serial Statements
Therefore:
– if the leading term is big-Q, then the result must
be big-Q, otherwise
– if the leading term is big-O, we can say the result
is big-O
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)
MHA,2015,AAiT
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
MHA,2015,AAiT
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
MHA,2015,AAiT
Functions
Fortunately, this is such a common task
that all modern processors have
instructions that perform most of these
steps in one instruction
MHA,2015,AAiT
Functions
Because any function requires the
overhead of a function call and return, we
will always assume that
Tf = Ω(1)
MHA,2015,AAiT
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)
MHA,2015,AAiT
Functions
Consider this function:
void Disjoint_sets::set_union( int m, int n ) {
m = find( m );
n = find( n ); 2Tfind
if ( m == n ) {
return;
}
if ( tree_height[m] == tree_height[n] ) {
++( tree_height[m] );
max_height = std::max( max_height, tree_height[m] ); Q(1)
}
} else {
parent[m] = n;
}
}
MHA,2015,AAiT
Recursive Functions
A function is relatively simple (and boring)
if it simply performs operations and calls
other functions
MHA,2015,AAiT
Recursive Functions
As an example, we could implement the
factorial function recursively:
int factorial( int n ) {
if ( n <= 1 ) { (1)
return 1;
} else { T! ( n 1) (1)
return n * factorial( n – 1 );
}
}
MHA,2015,AAiT
Recursive Functions
Thus, we may analyze the run time of this
function as follows:
Θ(1) n 1
T! ( n )
T! ( n 1) Θ(1) n 1
MHA,2015,AAiT
Recursive Functions
Unfortunately, you don’t have Maple on
the examination, thus, we can examine the
first few steps:
T!(n) = T!(n – 1) + 1
= T!(n – 2) + 1 + 1 = T!(n – 2) + 2
= T!(n – 3) + 3
MHA,2015,AAiT
Recursive Functions
If k = n – 1 then
T!(n) = T!(n – (n – 1)) + n – 1
= T!(1) + n – 1
=1+n–1=n
MHA,2015,AAiT
Recursive Functions
Incidentally, we may write the factorial
function using the ternary ?: operator
– Ternary operators take three arguments—C++
has only one
int factorial( int n ) {
return (n <= 1) ? 1 : n * factorial( n – 1 );
}
MHA,2015,AAiT
Recursive Functions
Suppose we want to sort a array of n items
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
int tmp = array[n - 1]; // swap the largest entry with the last
array[n - 1] = array[posn];
array[posn] = tmp;
MHA,2015,AAiT
Recursive Functions
We could call this function as follows:
int array[7] = {5, 8, 3, 6, 2, 4, 7};
sort( array, 7 ); // sort an array of seven items
MHA,2015,AAiT
Recursive Functions
The first call finds the largest element
MHA,2015,AAiT
Recursive Functions
The next call finds the 2nd-largest element
MHA,2015,AAiT
Recursive Functions
The third finds the 3rd-largest
MHA,2015,AAiT
Recursive Functions
And the 4th
MHA,2015,AAiT
Recursive Functions
And the 5th
MHA,2015,AAiT
Recursive Functions
Finally the 6th
MHA,2015,AAiT
Recursive Functions
And the array is sorted:
MHA,2015,AAiT
Recursive Functions
Analyzing the function, we get:
MHA,2015,AAiT
Recursive Functions
Thus, replacing each Landau symbol with
a representative, we are required to solve
the recurrence relation
T(n) = T(n – 1) + n T(1) = 1
n
1n( n1 ) 1
2
> expand( % );
1 1 2
n n
2 MHA,2015,AAiT
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 1)
n n n
T(1) i 1
i 2 MHA,2015,AAiT
i 2
i
i 1
i
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
MHA,2015,AAiT
Recursive Functions
Also, if n = 1, then T(1) = Q(1)
MHA,2015,AAiT
Recursive Functions
Thus, we can write
T( n ) T( 2 k 1)
2k 1 1
T 1
2
T( 2 k 1 1) 1
2 k 1 1 1
T 11
2
T( 2 k 2 1) 2
MHA,2015,AAiT
Recursive Functions
Notice the pattern with one more step:
T( 2 k 1 1) 1
2 k 1 1 1
T 11
2
T( 2 k 2 1) 2
T( 2 k 3 1) 3
MHA,2015,AAiT
Recursive Functions
Thus, in general, we may deduce that after
k – 1 steps:
T( n ) T( 2 k 1)
T( 2 k ( k 1) 1) k 1
T(1) k 1 k
because T(1) = 1
MHA,2015,AAiT
Recursive Functions
Thus, T(n) = k, but n = 2k – 1
Therefore k = lg(n + 1)
f n
However, recall that f(n) = Θ(g(n))
lim if c 0c for
n g n
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
Thus, T(n) = Q(lg(n + 1)) = Q(ln(n))
MHA,2015,AAiT
Cases
As well as determining the run time of an
algorithm, because the data may not be
deterministic, we may be interested in:
– Best-case run time
– Average-case run time
– Worst-case run time
MHA,2015,AAiT
Cases
Assume the case we are looking for is in the list
and equally likely distributed
Thus, we sum
1 n( n 1) n 1
n
1
i
n i 1
n 2 2
which is O(n)
MHA,2015,AAiT
Cases
Suppose we have a different distribution:
– there is a 50% chance that the element is the
first
– for each subsequent element, the probability
is reduced by ½
n
i i
i
i
?
i 1 2 i 1 2
We could write:
MHA,2015,AAiT
Cases
You’re not expected to know this for the
final, however, for interest:
> sum( i/2^i, i = 1..infinity );
MHA,2015,AAiT
Cases
Previously, we had an example where we
were looking for the number of times a
particular assignment statement was
executed:
int find_max( int * array, int n ) {
max = array[0];
return max;
}
MHA,2015,AAiT
Cases
This example is taken from Preiss
– The best case was once (first element is
largest)
– The worst case was n times
MHA,2015,AAiT
Cases
To consider this question, we must assume that
elements in the array are evenly distributed
1 1
?
i 0
i 1 i 1
i
Thus, given a value i, there are i + 1 objects, hence
MHA,2015,AAiT
Cases
We can approximate the sum by an
integral – what is the area under:
MHA,2015,AAiT
Cases
We can approximate this by the 1/(x + 1)
integrated from 0 to n
MHA,2015,AAiT
Cases
From calculus:
n n 1
1 1 n 1
0 x 1 dx 1
x
dx ln( x) 1 ln(n 1) ln(1) ln(n 1)
MHA,2015,AAiT
Cases
Consider the following image which
highlights the errors
– The errors can be fit into the box [0, 1] × [0, 1]
MHA,2015,AAiT
Cases
Consequently, the error must be < 1
MHA,2015,AAiT
Cases
Thus, the number of times that the
assignment statement will be executed,
assuming an even distribution is O(ln(n))
MHA,2015,AAiT
Cases
Thus, the total run of:
int find_max( int *array, int n ) {
max = array[0];
return max;
}
is n 1 1
1 1 1 n ln(n) n
i 1 i 1
MHA,2015,AAiT
Summary
In these slides we have looked at:
– The run times of
• Operators
• Control statements
• Functions
• Recursive functions
– We have also defined best-, worst-, and average-
case scenarios
We will be considering all of these each time
we inspect any algorithm used in this class
MHA,2015,AAiT
Summary
In this topic, we looked at:
– Justification for using analysis
– Quadratic and polynomial growth
– Counting machine instructions
– Landau symbols o O Q W w
– Big-Qas an equivalence relation
– Little-o as a linear ordering for the
equivalence classes
MHA,2015,AAiT