Algo Analysis Lect
Algo Analysis Lect
18.5?
1.42
3?
10 Number of classrooms 130
2 / 34
Introduction
Compare algorithms?
Search for a certain number in a sorted list of numbers
6 12 46 47 52 73 84 91 6 12 46 47 52 73 84 91
3 / 34
Introduction
Algorithm analysis
1. Predict performance
What should be the running time of my program if the amount of input data change?
What about the memory occupation?
2. Compare algorithms
Before starting a costly implementation, can we ensure a specific algorithm is better
than an other?
Can we define "better"?
3. Characterize
What is the worst-case efficiency?
Average-case? Best-case?
4 / 34
Experimental approach
3-SUM problem
Definition Example
In a set of N integers, how many (unique) $ cat 8ints.txt
triplets sum to zero? 30 -40 -20 -10 40 0 10 5
30 -40 10 0
30 -20 -10 0
-40 40 0 0
-10 0 10 0
5 / 34
Experimental approach
Brute-force algorithm
int Count3Sum(const std::vector<int> &a) {
Read integers from standard
int N = a.size(); input into a vector
int count = 0;
Examine all possible triplets!
for (int i = 0; i < N; i++)
for (int j = i + 1; j < N; j++)
for (int k = j + 1; k < N; k++)
if (a[i] + a[j] + a[k] == 0)
count++;
return count;
}
int main(void) {
int n;
std::vector<int> a;
return 0;
}
3sum.cc
6 / 34
Experimental approach
Timing measurement *
Multiple runs, each with a different amount of data:
500
400
Running time T(N)
300
200
100
0
1K 2K 4K 8K
Problem size N
8 / 34
Experimental approach
Plot (log-log)
Running time T (N ) vs input size N on a log-log plot
1000
Typical linear curve:
f (x) = m ∗ x + b
Line equation on a log-log scale:
100 lg(T (N )) = m ∗ lg(N ) + b
lg(x2 /x1 )
1K 2K 4K 8K
Problem size lg(N)
9 / 34
Experimental approach
Back to standard plot
Running time T (N ) vs input size N
600
?
500
400
Running time T(N)
300
200
100
0
1K 2K 4K 8K
Problem size N
T (N ) = N m ∗ 2b
T (N ) = N 2.9978 ∗ 2−30.00
10 / 34
Experimental approach
Validation of prediction?
Hypothesis:
T (N ) = N 2.9978 ∗ 2−30.00
Predictions:
For N = 8K, running time should be about 502 seconds (08m:22s)
For N = 16K, running time should be about 4122 seconds (01h:08m:42s)
Experiments:
Hypothesis is validated!
11 / 34
Experimental approach
Conclusion
Issues
Need to actually implement the algorithm in order to characterize it experimentally!
Difficult to get precise measurements as it heavily depends on:
The input data
The hardware (CPU, memory, caches, etc.)
The software (compiler, garbage collector, etc.)
The system (OS, network, etc.)
Require to run a significant (potentially huge) number of experiments to gather data
points
Next time
Need for a better model...
12 / 34
ECS 36C - Algorithm analysis (part 2)
Prof. Joël Porquet-Lupine
500
For N = 8K, running time should be about
502 seconds (08m:22s)
400
Running time T(N)
200
4122 seconds (01h:08m:42s)
100
0
1K 2K 4K 8K
Problem size N
14 / 34
Mathematical approach
Total running time is sum of (cost x frequency) for all operations:
∑ cost(op) ∗ f req(op)
op
15 / 34
Mathematical approach
1-SUM
int count = 0; Statement Frequency
i++; N
if (a[i] == 0) N
count++; 0 to N
return count; 1
16 / 34
Mathematical approach
2-SUM
int count = 0; Statement Frequency
1
j++; 2
(N − 1)N
1
if (a[i] + a[j] == 0) 2
(N − 1)N
count++; 0 to 12 (N − 1)N
return count; 1
17 / 34
Mathematical approach
Simplify some more
Identify the operation that represents
for (int i = 0; i < N; i++)
the overall running time the best for (int j = i + 1; j < N; j++)
Usually the body of the inner loop if (a[i] + a[j] == 0)
1
− 1) = 12 N 2 − 12 N count++;
2 N (N
2
1/2x
1 2 1
2N − 2N
2
1/2x - 1/2x
∼ 12 N 2
4x10
7
7
3x10
Tilde notation: Hint: both curves are
indistinguishable!
f (N )
f (N ) ∼ g(N ) means lim =1
g(N )
7
2x10
N →∞
7
1x10
0
0 2000 4000 6000 8000 10000
18 / 34
Mathematical approach
Tilde approximation and order of growth
lg N + 1 ∼ lg N lg N
3 ∼3 1
Conclusion
1. In principle, accurate mathematical models are available
2. In practice, approximate models do a good job: T (N ) ∼ cN x
3. For the mass, order of growth is usually enough...
19 / 34
Order-of-growth classification
Typical set of functions describing common algorithms
Constant (1)
a = b + c;
Simple statements
Linear (N )
for (int i = 0; i < N; i++)
Simple loop
Quadratic (N 2 )
for (int i = 0; i < N; i++)
Double loop for (int j = 0; j < N; j++)
Cubic (N 3 )
for (int i = 0; i < N; i++)
Triple loop for (int j = 0; j < N; j++)
for (int k = 0; k < N; k++)
20 / 34
Order-of-growth classification
Visual representation
100
exponential
ar
ic
quadratic
rithm
e
lin
linea
80
Running time
60
40
20
logarithmic
constant
0
0 20 40 60 80 100
Input size
21 / 34
Order-of-growth classification
Practical implications
Assume a computer with a 1 GHz processor (and IPC of 1),
And an input size N of 100,000.
How long would it take to process the input according to different algorithm's growth
rates?
Quadratic 10 seconds
Cubic 11 days
22 / 34
ECS 36C - Algorithm analysis (part 3)
Prof. Joël Porquet-Lupine
count++;
Order of growth: N 2
r
ic
rithm
lin
linea
80
Logarithmic 1.7e−8 seconds
60
Cubic 11 days
20
Input size
24 / 34
Big O
Definition
Indicates upper asymptotic bound, using order-of-growth classification
Most usual notation for describing computational complexity of algorithms
Formal definition
T (N ) is O(F (N )) if there exist
c > 0 and n0 > 0 such as
T (N ) ≤ cF (N ) when N ≥ n0
90
x2
3x 2 +2x+4
Example
80
9x 2
3N 2 + 2N + 4 = O(N 2 )
70
60
2 2
Because 3N + 2N + 4 ≤ 9N when 50
N ≥1
40
c=9
30
n0 = 1
20
10
0
0 0.5 1 1.5 2 2.5 3
25 / 34
Big O
Big-Ω (omega)
Same idea as Big-O but indicates lower bound
T (N ) is Ω(F (N )) if there exist c > 0 and n0 > 0 such as T (N ) ≥ cF (N ) when
N ≥ n0
Big-Θ (theta)
Asymptotic tight bound
T (N ) is Θ(F (N )) if T (N ) is both
O(F (N )) and Ω(F (N ))
There exist c1 > 0 and c2 > 0 and n0 > 0 such as
T (N ) ≥ c1 F (N ) and T (N ) ≤ c2 F (N ) when N ≥
n0
26 / 34
Big O
For the math nerds!
Big-O is only an upper bound
3N 2 + 2N + 4 = O(N 2 )
And also O(N 3 ), and O(N 4 ), and O(N !)
Equality vs inclusion
O(N ) = O(N 2 ) but O(N 2 ) = O(N )
= is assumed unidirectional
∈ is considered a better and non-ambiguous notation
O(N ) ∈ O(N 2 )
27 / 34
Big O
Algorithm complexity, in practice...
Be aware of the mathematical background and notations
But using Big-O is usually good enough
Ignore lower order terms and drop the leading term's coefficient!
E.g. T (N ) = 4N 2 + N + 3 is considered to be O(N 2 )
Examples
1-SUM: T (N ) = O(N ) for (int i = 0; i < N; i++)
if (a[i] == 0)
count++;
1sum.cc
28 / 34
Big O
Some pitfalls...
O(N 2 )?
No, O(N )
for (int i = 0; i < N; i++) {
for (int j = 0; j < 5; j++) { Innermost loop is
... constant!
}
}
O(N 2 )?
No, O(N )
for (int i = 0; i < N; i++) {
if (i == (N - 1)) { Innermost loop is
for (int j = 0; j < N; j++) { executed only once
...
}
}
}
29 / 34
Big O
Function calls (1)
double sum_squares(int count) {
double sum = 0.0;
return sum;
}
30 / 34
Big O
Function calls (2)
bool str_contains(std::string str, std::string pattern)
{
for (int i = 0; i < str.length() - pattern.length(); i++)
if (!std::strncmp(str + i, pattern, pattern.length()))
return true;
return false;
}
31 / 34
Big O
Log
int BSearch(const std::vector<int> &array, Observations:
int key) {
int lo = 0, hi = array.size() - 1; Divides the input data by 2 at each
while (lo <= hi) { iteration
int mid = lo + (hi - lo) / 2;
Until it finds the searched item (or
if (array[mid] == key) doesn't)
return mid;
}
binary_search.cc ⇒ x = log2 (N )
32 / 34
Space complexity
Intro
Estimate how much memory an algorithm uses
Input space: space taken by the algorithm's input values
Auxiliary space: temporary extra space used during the execution
Count number of variables and their size
Is total amount of memory related to input size N?
33 / 34
Space complexity
Examples (1)
Input size is N characters int CountLetter(const std::string &str,
const char letter) {
Fixed amount of additional variables int count = 0;
regardless of input's size for (auto c : str)
Overall memory complexity is O(N ) + if (c == letter)
count++;
O(1) = O(N )
return count;
}
count_letter.cc
Examples (2)
Input size is N std::string StrUppercase(const std::string &input) {
std::string output = input;
characters
for (auto &c : output)
Extra memory allocation c = std::toupper(static_cast<unsigned char>(c));
proportional to input's
return output;
size }
uppercase.cc
34 / 34