Asymptotics 3
1
Lecture 15
CS61B, Fall 2024 @ UC Berkeley
Slides Credit: Josh Hug
Intuitive Definitions of Functions
If my function f(n) is… | Doubling N | Adding 1 to N |
Θ(1) | Doesn't affect runtime | |
Θ(log n) (base independent) | Adds 1 to runtime | Affects runtime minimally |
Θ(n) | Doubles runtime | Adds 1 to runtime |
Θ(n2) | Quadruples runtime | Adds n to runtime |
Θ(2n) (base dependent) | Squares runtime | Doubles runtime |
Summations
Lecture 13, CS61B, Spring 2025
Summations
Analyzing Programs
Mergesort
3
Summations and Recursive Functions
Sometimes, it's not easy to get an explicit runtime function, but it is possible to Theta bound the function. Often, you can reduce a runtime function to one of:
Triangle Summation
Example: f(N) = Σi=1 to N i = 1 + 2 + 3 + 4 + … + N ∈ Θ(N2)
Approach 1: Algebra
5
f(N) = (N - 1) + (N - 2) + (N - 3) + … + 3 + 2 + 1 +N
2f(N) = N + N + … + N (N-1 copies) + N + N
∴ f(N) = N(N + 1)/2
f(N) = 1 + 2 + 3 + … + (N - 3) + (N - 2) + (N - 1) + N
Triangle Summation
Example: f(N) = Σi=1 to N i = 1 + 2 + 3 + 4 + … + N ∈ Θ(N2)
Approach 2: Geometry
The sum is given by the area of this step-wise triangle,
which is approximately a triangle.
Area of triangle is base * height/2 = N * N/2 ∈ Θ(N2)
6
| 5 | ||||
| | 4 | |||
| | | 3 | ||
| | | | 2 | |
| | | | | 1 |
| | | | | |
Summation of Halves
What is 1 + ½ + ¼ + ⅛ + …
7
Summation of Halves
What is 1 + ½ + ¼ + ⅛ + …
2
Algebraic proof:
Let x = 1 + ½ + ¼ + ⅛ + …
Then 2x = 2 + 1 + ½ + ¼ + ⅛ + …
So 2x - x = 2 + 0…
So x = 2
8
Summation of Halves
What is 1 + ½ + ¼ + ⅛ + …
2
Geometric proof: Each step moves halfway to the "2", so you always approach, but never hit, 2.
9
1 | ½ | ¼ | ⅛ | | |||||||||||
| | ||||||||||||||
Summation of Powers of 2
What is 1 + ½ + ¼ + ⅛ + …
2
If you multiply everything in the below by 2N, you get the sequence
2N+2N-1+...+21+ 1 + ½ +¼ + ⅛ +... = 2*2N
So 1+2+4+...+2N = 2*2N-1
10
8 | 4 | 2 | 1 | | |||||||||||
8 | 8 | ||||||||||||||
1
Infinite Summation
For any r < 1, Σi=1 to ∞ ri = 1+r+r2+r3+... the sum can be evaluated:
Let x = 1+ r+r2+r3...
Then r*x = r+r2+r3…
So x-r*x = 1
x = 1/(1-r)
(Note: If r≥1, then the sum diverges to infinity. Formally, the power series has a radius of convergence of 1)
11
Exponential Summation
Show that for any r > 1, Σi=0 to N ri = 1+r+r2+r3+...+rN ∈ Θ(rN) by showing:
Σi=0 to N ri = 1+r+r2+r3+...+rN ∈ Ω(rN)
Σi=0 to N ri = 1+r+r2+r3+...+rN ∈ O(rN)
12
Exponential Summation
Show that for any r > 1, Σi=0 to N ri = 1+r+r2+r3+...+rN ∈ Θ(rN) by showing:
Σi=0 to N ri = 1+r+r2+r3+...+rN ∈ Ω(rN)
The sum is greater than the last term, so Σi=1 to N ri > rN ≥ 1*(rN)
Σi=0 to N ri = 1+r+r2+r3+...+rN ∈ O(rN)
The sum is the same as rN*(1+1/r+(1/r)2+...+(1/r)N) = rN*(Σi=1 to N (1/r)i)
Note that Σi=1 to N (1/r)i < Σi=1 to ∞ (1/r)i = 1/(1-1/r), because if r>1, then 1/r < 1.
So the sum is ≤rN*(1/(1-1/r)), where (1/(1-1/r)) is a constant
13
Repeat After Me...
There is no magic shortcut for asymptotic analysis problems
← Sum of First Natural Numbers (Link)
← Sum of First Powers of 2 (Link)
← Generalization of ^
← Generalization of ^
Repeat After Me...
There is no magic shortcut for asymptotic analysis problems
← Sum of First Natural Numbers (Link)
← Alternate form of sum of powers
← Generalization of ^
← Generalization of ^
Repeat After Me...
There is no magic shortcut for asymptotic analysis problems
← Sum of First Natural Numbers (Link)
← Sum of First Powers of 2 (Link)
← Generalization of ^
← Generalization of ^
Repeat After Me...
There is no magic shortcut for asymptotic analysis problems
QR decomposition runtime, from “Numerical Linear Algebra” by Trefethen.
Generalizing the two cases you need to know (Out of Scope)
Σi=0 to N f(i) (f continuous, increasing)
Generalizing the two cases you need to know (Out of Scope)
Σi=0 to N f(i) ∈ Θ(∫0N f(x)) (for most functions)
Some shortcuts exist
But they tend to be limited in scope
Summary (No pun intended)
Summations are difficult to evaluate
In practice, you won't be working with the raw math and proofs, because that adds too much complexity.
Fortunately, you don't have to!
Just as in programming, we can abstract the
formal math, and use the tools we proved work.
Let's discuss how to actually use
these tools to compute the runtime of programs.
Nested For Loops
Lecture 15, CS61B, Fall 2024
Summations
Analyzing Programs
Mergesort
Measuring a Program's Performance
Expressing a program's efficiency boils down to two main steps:
22
Loops Example:
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)). By simple, we mean there should be no unnecessary multiplicative constants or additive terms.
D. N log N
E. N2
F. Other
public static void printParty(int N) {
for (int i = 1; i <= N; i = i * 2) {
for (int j = 0; j < i; j += 1) {
System.out.println("hello");
int ZUG = 1 + 1;
}
}
}
Note that there’s only one case for this code and thus there’s no distinction between “worst case” and otherwise.
Loops Example
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Units of work C(N):
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
0
1
2
3
4
5
0 1 2 3 4 5
j
i
N
? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? |
public static void printParty(int N) {
for (int i = 1; i <= N; i = i * 2) {
for (int j = 0; j < i; j += 1) {
//1 unit of work
} } }
C(N)
Loops Example
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Units of work C(N):
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
0
1
2
3
4
5
0 1 2 3 4 5
Work
i
N
? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? |
public static void printParty(int N) {
for (int i = 1; i <= N; i = i * 2) {
//i units of work
}
}
C(N)
Loops Example
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Units of work C(N):
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| | | | | |
1 | | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
0
1
2
3
4
5
0 1 2 3 4 5
i
N
1 | | | | | | | | | | | | | | | | | |
C(N)
public static void printParty(int N) {
for (int i = 1; i <= N; i = i * 2) {
//i units of work
}
}
Work
Loops Example
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Units of work C(N):
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| | | | | |
1 | | | | | |
2 | | | | | |
| | | | | |
| | | | | |
| | | | | |
0
1
2
3
4
5
0 1 2 3 4 5
i
1 | 3 | | | | | | | | | | | | | | | | |
N
public static void printParty(int N) {
for (int i = 1; i <= N; i = i * 2) {
//i units of work
}
}
C(N)
Work
Loops Example
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Units of work C(N):
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| | | | | |
1 | | | | | |
2 | | | | | |
| | | | | |
| | | | | |
| | | | | |
0
1
2
3
4
5
0 1 2 3 4 5
i
1 | 3 | 3 | | | | | | | | | | | | | | | |
N
public static void printParty(int N) {
for (int i = 1; i <= N; i = i * 2) {
//i units of work
}
}
C(N)
N=3 doesn't do anything extra
Work
Loops Example
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Units of work C(N):
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| | | | | |
1 | | | | | |
2 | | | | | |
| | | | | |
4 | | | |||
| | | | | |
0
1
2
3
4
5
0 1 2 3 4 5
i
1 | 3 | 3 | 7 | | | | | | | | | | | | | | |
N
public static void printParty(int N) {
for (int i = 1; i <= N; i = i * 2) {
//i units of work
}
}
C(N)
Work
Loops Example
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Units of work C(N):
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| | | | | |
1 | | | | | |
2 | | | | | |
| | | | | |
4 | | | |||
| | | | | |
0
1
2
3
4
5
0 1 2 3 4 5
i
1 | 3 | 3 | 7 | 7 | 7 | 7 | | | | | | | | | | | |
N
public static void printParty(int N) {
for (int i = 1; i <= N; i = i * 2) {
//i units of work
}
}
C(N)
Work
N=4,5,6,7 all print 7 times
Loops Example
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Units of work C(N):
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| | | | | |
1 | | | | | |
2 | | | | | |
| | | | | |
4 | | | |||
| | | | | |
0
1
2
3
4
5
0 1 2 3 4 5
i
1 | 3 | 3 | 7 | 7 | 7 | 7 | | | | | | | | | | | |
N
public static void printParty(int N) {
for (int i = 1; i <= N; i = i * 2) {
//i units of work
}
}
C(N)
Work
N=4,5,6,7 all print 7 times
Loops Example
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Units of work C(N):
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| | | | | |
1 | | | | | |
2 | | | | | |
| | | | | |
4 | | | |||
| | | | | |
0
1
2
3
4
5
0 1 2 3 4 5
i
1 | 3 | 3 | 7 | 7 | 7 | 7 | 15 | 15 | 15 | 15 | 15 | 15 | 15 | 15 | | | |
N
public static void printParty(int N) {
for (int i = 1; i <= N; i = i * 2) {
//i units of work
}
}
C(N)
Work
These N all print 15 times
Loops Example
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Units of work C(N):
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| | | | | |
1 | | | | | |
2 | | | | | |
| | | | | |
4 | | | |||
| | | | | |
0
1
2
3
4
5
0 1 2 3 4 5
i
1 | 3 | 3 | 7 | 7 | 7 | 7 | 15 | 15 | 15 | 15 | 15 | 15 | 15 | 15 | 31 | 31 | 31 |
N
public static void printParty(int N) {
for (int i = 1; i <= N; i = i * 2) {
//i units of work
}
}
C(N)
Work
C(N) = 1 + 2 + 4 + … + N, if N is a power of 2
Loops Example
We’re trying to find the order of growth of C(N):
Units of work C(N):
N
C(N)
C(N) = 1 + 2 + 4 + … + N, if N is a power of 2
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
1 | 3 | 3 | 7 | 7 | 7 | 7 | 15 | 15 | 15 | 15 | 15 | 15 | 15 | 15 | 31 | 31 | 31 |
Loops Example:
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Cost model C(N):
D. N log N
E. N2
F. Other
public static void printParty(int N) {
for (int i = 1; i<=N; i = i * 2) {
for (int j = 0; j < i; j += 1) {
System.out.println("hello");
int ZUG = 1 + 1;
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
1 | 3 | 3 | 7 | 7 | 7 | 7 | 15 | 15 | 15 | 15 | 15 | 15 | 15 | 15 | 31 | 31 | 31 |
N
C(N)
C(N) = 1 + 2 + 4 + … + N, if N is a power of 2
Loops Example 2 [attempt #3] (no yellkey)
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
The "peaks" of C(N) are when N = 2k.
Total = 1+2+4+...+N ∈ Θ(N)
The "troughs" of C(N) are when N = 2k-1.
Total = 1+2+4+...+(N+1)/2 ∈ Θ((N+1)/2)
∈ Θ(N)
D. N log N
E. N2
F. Something else
Amortized Analysis
Lecture 15, CS61B, Fall 2024
Summations
Analyzing Programs
Mergesort
Surely no function does this, right?
Let's play with this function a bit
public static void printParty(int n) {
for (int i = 1; i <= n; i = i * 2) {
//i units of work
}
}
Surely no function does this, right?
Add Θ(N) work: Total runtime is still Θ(N)
public static void printParty(int n) {
for (int i = 1; i <= n; i = i * 2) {
//i units of work
}
for (int i = 1; i <= n; i++) {
//1 unit of work
}
}
Surely no function does this, right?
Combine the for loops. No asymptotic change in work done
public static void printParty(int n) {
for (int i = 1; i <= n; i++) {
//1 unit of work
if(i is a power of 2) {
//i units of work
}
}
}
Surely no function does this, right?
Put things in separate functions. Changes an O(1) thing to more O(1) things, so no runtime difference.
public static void printParty(int n) {
for (int i = 1; i <= n; i++) {
foo();
} }
public static void foo() {
if(i is a power of 2) {
bar(i);
}
// 1 unit of work
}
public static void bar(i) {
//i units of work
}
Surely no function does this, right?
Rename functions and define what we do in the commented code. No change in runtime.
public void addMany(int n) {
for (int i = 0; i < n; i++) {
addLast(1);
} }
public void addLast(int value) {
if(this.length == arr.length) {
resize(this.length * 2);
} //Happens every time length is 2k
//addLast code takes 1 unit of work
}
public void resize(int i) {
//resizing takes i units of work
}
Why geometric resizing is faster
When we discussed ArrayLists, we handwaved why geometric resizing is better than linear resizing. Now that we know asymptotics, we can finally prove this.
After N addLasts, runtime is Θ(N2)
After N addLasts,
runtime is Θ(N)
public void addLast(int x) {
if (size == items.length) {
resize(size + RFACTOR);
}
items[size] = x;
size += 1;
}
public void addLast(int x) {
if (size == items.length) {
resize(size * RFACTOR);
}
items[size] = x;
size += 1;
}
Why geometric resizing is faster
Even though the worst-case resize is still Θ(N), they happen so infrequently with geometric resizing that we get Θ(1) runtime on average regardless of how we order List operations.
Each addLast takes on average Θ(N) time
Each addLast takes on average Θ(1) time
public void addLast(int x) {
if (size == items.length) {
resize(size + RFACTOR);
}
items[size] = x;
size += 1;
}
public void addLast(int x) {
if (size == items.length) {
resize(size * RFACTOR);
}
items[size] = x;
size += 1;
}
Amortized Runtime
This is known as Amortized Runtime
addLast is Θ(1) amortized
public void addLast(int x) {
if (size == items.length) {
resize(size * RFACTOR);
}
items[size] = x;
size += 1;
}
Recursive Analysis
Lecture 15, CS61B, Fall 2024
Summations
Analyzing Programs
Mergesort
Recursion, Approach 1: Intuitive
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Using your intuition, give the order of growth of the runtime of this code as a function of N?
3
2
2
1
1
1
1
3
2
2
1
1
1
1
4
public static int f3(int n) {
if (n <= 1)
return 1;
return f3(n-1) + f3(n-1);
}
Recursion, Approach 1: Intuitive
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Using your intuition, give the order of growth of the runtime of this code as a function of N?
3
2
2
1
1
1
1
3
2
2
1
1
1
1
4
3
2
2
1
1
1
1
3
2
2
1
1
1
1
4
5
public static int f3(int n) {
if (n <= 1)
return 1;
return f3(n-1) + f3(n-1);
}
2N: Every time we increase N by 1, we double the work!
Recursion, Approach 2: Exact Counting
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Another approach: Count number of calls to f3, given by C(N).
Each function call does a constant amount of work (not counting recursive calls), so C(N) ∈ Θ(R(N))
3
2
2
1
1
1
1
3
2
2
1
1
1
1
4
public static int f3(int n) {
if (n <= 1)
return 1;
return f3(n-1) + f3(n-1);
}
Recursion, Approach 2: Exact Counting: http://yellkey.com/happy
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Another approach: Count number of calls to f3, given by C(N).
What is the final term of the sum?
3
2
2
1
1
1
1
3
2
2
1
1
1
1
4
public static int f3(int n) {
if (n <= 1)
return 1;
return f3(n-1) + f3(n-1);
}
D. 2N-1
E. 2N-1-1
Recursion, Approach 2: Exact Counting
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Another approach: Count number of calls to f3, given by C(N).
What is the final term of the sum?
3
2
2
1
1
1
1
3
2
2
1
1
1
1
4
public static int f3(int n) {
if (n <= 1)
return 1;
return f3(n-1) + f3(n-1);
}
D. 2N-1
Recursion, Approach 2: Exact Counting
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Another approach: Count number of calls to f3, given by C(N).
Give a simple expression for C(N).
3
2
2
1
1
1
1
3
2
2
1
1
1
1
4
public static int f3(int n) {
if (n <= 1)
return 1;
return f3(n-1) + f3(n-1);
}
Recursion, Approach 2: Exact Counting
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Another approach: Count number of calls to f3, given by C(N).
Give a simple expression for C(N).
3
2
2
1
1
1
1
3
2
2
1
1
1
1
4
public static int f3(int n) {
if (n <= 1)
return 1;
return f3(n-1) + f3(n-1);
}
Recursion, Approach 2: Exact Counting
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Another approach: Count number of calls to f3, given by C(N).
Since work during each call is constant:
3
2
2
1
1
1
1
3
2
2
1
1
1
1
4
public static int f3(int n) {
if (n <= 1)
return 1;
return f3(n-1) + f3(n-1);
}
Recursion, A minor change
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
What happens if we make this tiny change?
Give a simple expression for C(N).
public static int fib(int n) {
if (n <= 1)
return 1;
return fib(n-1) + fib(n-2);
}
2
1
0
4
3
2
1
1
0
Recursion, A minor change (Out of Scope)
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
For this one, we'll have to be a bit more creative:
C(N) = # yellow + # green = 2(FN)-1 = Θ(FN)
public static int fib(int n) {
if (n <= 1)
return 1;
return fib(n-1) + fib(n-2);
}
2
1
0
4
3
2
1
1
0
Recursion, A minor change (Out of Scope)
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
C(N) = # yellow + # green = 2(FN)-1 = Θ(FN)
If you do enough math, you find that FN∈ Θ(φN)
Where φ = (1+sqrt(5))/2 ≈ 1.618
Each function call does 1 unit of work
So R(N) = Θ(1.618N)
In conclusion: There is no Magic Shortcut for
Asymptotic Analysis
public static int fib(int n) {
if (n <= 1)
return 1;
return fib(n-1) + fib(n-2);
}
2
1
0
4
3
2
1
1
0
Recursion, Approach 3: Recurrence Relations (Out of Scope for 61B)
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Third approach: Count number of calls to f3, given by a “recurrence relation” for C(N).
3
2
2
1
1
1
1
3
2
2
1
1
1
1
4
2C(N-1) + 1
public static int f3(int n) {
if (n <= 1)
return 1;
return f3(n-1) + f3(n-1)
}
Recursion, Approach 3: Recurrence Relations (Out of Scope for 61B)
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Third approach: Count number of calls to f3, given by a “recurrence relation” for C(N).
3
2
2
1
1
1
1
3
2
2
1
1
1
1
4
public static int f3(int n) {
if (n <= 1)
return 1;
return f3(n-1) + f3(n-1)
}
More technical to solve. Won’t do this in our course. See next slide for solution.
2C(N-1) + 1
Recursion, Approach 3: Recurrence Relations (Out of Scope for 61B)
Find a simple f(N) such that the runtime R(N) ∈ Θ(f(N)).
Third approach: Count number of calls to f3, given by a “recurrence relation” for C(N).
This approach not covered in class. Provided for those of you who want to see a recurrence relation solution.
3
2
2
1
1
1
1
3
2
2
1
1
1
1
4
public static int f3(int n) {
if (n <= 1)
return 1;
return f3(n-1) + f3(n-1)
}
Mergesort
Lecture 15, CS61B, Fall 2024
Summations
Analyzing Programs
Mergesort
Sorting
Along with matrix multiplication, sorting is one of the problems that pops up most often in asymptotic analysis.
x = List.of({“he”, “is”, “the”, “agoyatis”, “of”, “mr.”, “conchis”})
public static List<Comparable> sort(List<Comparable> x)
x = {“agoyatis”, “conchis”, “he”, “is”, “mr.”, “of”, “the”}
Mergesort Pseudocode
Mergesort is a recursive way to sort a list:
static List<Comparable> sort(List<Comparable> x)
List<Comparable> firsthalf = sort(x.sublist(0, x.size()/2));
List<Comparable> secondhalf = sort(x.sublist(x.size()/2, x.size()));
return merge(firsthalf, secondhalf);
}
The Merge Operation
Given two sorted arrays, the merge operation combines them into a single sorted array by successively copying the smallest item from the two arrays into a target array.
Merging Demo (Link)
Merge Runtime
How does the runtime of merge grow with N, the total number of items?
2
3
6
10
11
4
5
7
8
2
3
4
5
6
7
8
10
11
Merge Runtime
How does the runtime of merge grow with N, the total number of items?
C. Θ(N). Why? Θ(1) time per element in the merged list, and the merged list has exactly N items
2
3
6
10
11
4
5
7
8
2
3
4
5
6
7
8
10
11
Determining Mergesort Runtime
Since we don't care about the list itself, let's simplify our code a bit
static List<Comparable> sort(List<Comparable> x)
List<Comparable> firsthalf = sort(x.sublist(0, x.size()/2));
List<Comparable> secondhalf = sort(x.sublist(x.size()/2, x.size()));
return merge(firsthalf, secondhalf);
}
Determining Mergesort Runtime
Since we don't care about the list itself, let's simplify our code a bit
static void sortRuntime(int n)
sortRuntime(n/2);
sortRuntime(n/2);
//n units of work
}
Example 5: Mergesort Order of Growth
For an array of size N, what is the worst case runtime of Mergesort?
N
N/2
N/2
N/4
N/4
N/4
….
N/8
N/8
….
N/4
N/8
N/8
k
static void sortRuntime(int n)
sortRuntime(n/2);
sortRuntime(n/2);
//n units of work
}
Example 5: Mergesort Order of Growth
Mergesort has worst case runtime = Θ(N log N).
Exact count explanation is tedious.
N
N/2
N/2
N/4
N/4
N/4
….
N/8
N/8
….
N/4
N/8
N/8
k
Mergesort using Recurrence Relations (Extra)
C(N): Number of calls to mergesort + number of array writes.
Only works for N=2k. Can be generalized at the expense of some tedium by separately finding Big O and Big Omega bounds.
N
N/2
N/2
N/4
N/4
N/4
….
N/8
N/8
….
N/4
N/8
N/8
k
Using Sorting as a Tool
Recall from Lecture 11 the dup functions, which checked if a sorted array contained any duplicates.
What if the input wasn't sorted?
public static boolean dup1(int[] A) {
for (int i = 0; i < A.length; i += 1) {
for (int j = i + 1; j < A.length; j += 1) {
if (A[i] == A[j]) {
return true;
}
}
}
return false;
}
public static boolean dup2(int[] A) {
for (int i = 0; i < A.length - 1; i += 1) {
if (A[i] == A[i + 1]) {
return true;
}
}
return false;
}
dup1
dup2
Using Sorting as a Tool
What if the input wasn't sorted?
public static boolean dup1(int[] A) {
for (int i = 0; i < A.length; i += 1) {
for (int j = i + 1; j < A.length; j += 1) {
if (A[i] == A[j]) {
return true;
}
}
}
return false;
}
public static boolean dup2(int[] A) {
for (int i = 0; i < A.length - 1; i += 1) {
if (A[i] == A[i + 1]) {
return true;
}
}
return false;
}
dup1
dup2
Using Sorting as a Tool
What if the input wasn't sorted?
public static boolean dup1(int[] A) {
for (int i = 0; i < A.length; i += 1) {
for (int j = i + 1; j < A.length; j += 1) {
if (A[i] == A[j]) {
return true;
}
}
}
return false;
}
public static boolean dup2(int[] A) {
A = A.sort()
for (int i = 0; i < A.length - 1; i += 1) {
if (A[i] == A[i + 1]) {
return true;
}
}
return false;
}
dup1
dup2
Using Sorting as a Tool
What if the input wasn't sorted?
public static boolean dup1(int[] A) {
for (int i = 0; i < A.length; i += 1) {
for (int j = i + 1; j < A.length; j += 1) {
if (A[i] == A[j]) {
return true;
}
}
}
return false;
}
public static boolean dup2(int[] A) {
A = A.sort()
for (int i = 0; i < A.length - 1; i += 1) {
if (A[i] == A[i + 1]) {
return true;
}
}
return false;
}
dup1
dup2
Linear vs. Linearithmic (N log N) vs. Quadratic
N log N is basically as good as N, and is vastly better than N2.
(from Algorithm Design: Tardos, Kleinberg)
Summary
Theoretical analysis of algorithm performance requires careful thought.
Different solutions to the same problem, e.g. sorting, may have different runtimes.
Once you prove runtime for one problem, you may be able to use it in other problems to speed things up!
Binary Search (Intuitive)
Lecture 15, CS61B, Fall 2024
Summations
Analyzing Programs
Mergesort
Binary Search (If time allows)
Binary Search (demo: https://goo.gl/3VvJNw)
Trivial to implement?
See Jon Bentley’s book Programming Pearls.
static int binarySearch(String[] sorted, String x, int lo, int hi)
if (lo > hi) return -1;
int m = (lo + hi) / 2;
int cmp = x.compareTo(sorted[m]);
if (cmp < 0) return binarySearch(sorted, x, lo, m - 1);
else if (cmp > 0) return binarySearch(sorted, x, m + 1, hi);
else return m;
}
Binary Search (Intuitive)
Goal: Find runtime in terms of N = hi - lo + 1 [i.e. # of items being considered]
static int binarySearch(String[] sorted, String x, int lo, int hi)
if (lo > hi) return -1;
int m = (lo + hi) / 2;
int cmp = x.compareTo(sorted[m]);
if (cmp < 0) return binarySearch(sorted, x, lo, m - 1);
else if (cmp > 0) return binarySearch(sorted, x, m + 1, hi);
else return m;
}
Binary Search (Intuitive)
Goal: Find runtime in terms of N = hi - lo + 1 [i.e. # of items being considered]
B. log2N
Why? Problem size halves over and over until it gets down to 1.
static int binarySearch(String[] sorted, String x, int lo, int hi)
if (lo > hi) return -1;
int m = (lo + hi) / 2;
int cmp = x.compareTo(sorted[m]);
if (cmp < 0) return binarySearch(sorted, x, lo, m - 1);
else if (cmp > 0) return binarySearch(sorted, x, m + 1, hi);
else return m;
}
N
≈N/2
≈N/4
≈N/8
Log Time Is Really Terribly Fast
In practice, logarithmic time algorithms have almost constant runtimes.
N | log2 N | Typical runtime (seconds) |
100 | 6.6 | 1 nanosecond |
100,000 | 16.6 | 2.5 nanoseconds |
100,000,000 | 26.5 | 4 nanoseconds |
100,000,000,000 | 36.5 | 5.5 nanoseconds |
100,000,000,000,000 | 46.5 | 7 nanoseconds |
Binary Search Exact (Bonus)
Lecture 15, CS61B, Fall 2024
Summations
Analyzing Programs
Mergesort
Binary Search (If time allows)
This section is available as a pre-recorded video.
It's not ”out of scope” since it’s just another example problem using the same techniques used throughout the lecture.
Binary Search (Exact Count): Not a Live Video (no yellkey)
Goal: Find worst case runtime in terms of N = hi - lo + 1 [i.e. # of items]
Each call does constant work (w/o recursive calls)
static int binarySearch(String[] sorted, String x, int lo, int hi)
if (lo > hi) return -1;
int m = (lo + hi) / 2;
int cmp = x.compareTo(sorted[m]);
if (cmp < 0) return binarySearch(sorted, x, lo, m - 1);
else if (cmp > 0) return binarySearch(sorted, x, m + 1, hi);
else return m;
}
N=6
Binary Search (Exact Count)
Goal: Find worst case runtime in terms of N = hi - lo + 1 [i.e. # of items]
Each call does constant work (w/o recursive calls)
B. 3
Three total calls, where N = 6, N = 3, and N = 1.
static int binarySearch(String[] sorted, String x, int lo, int hi)
if (lo > hi) return -1;
int m = (lo + hi) / 2;
int cmp = x.compareTo(sorted[m]);
if (cmp < 0) return binarySearch(sorted, x, lo, m - 1);
else if (cmp > 0) return binarySearch(sorted, x, m + 1, hi);
else return m;
}
N=6
N=3
N=1
3 calls
Binary Search (Exact Count)
Goal: Find worst case runtime in terms of N = hi - lo + 1 [i.e. # of items]
static int binarySearch(String[] sorted, String x, int lo, int hi)
if (lo > hi) return -1;
int m = (lo + hi) / 2;
int cmp = x.compareTo(sorted[m]);
if (cmp < 0) return binarySearch(sorted, x, lo, m - 1);
else if (cmp > 0) return binarySearch(sorted, x, m + 1, hi);
else return m;
}
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
N
1 | | | | | 3 | | | | | | | |
C(N)
N=1
Binary Search (Exact Count)
Goal: Find worst case runtime in terms of N = hi - lo + 1 [i.e. # of items]
static int binarySearch(String[] sorted, String x, int lo, int hi)
if (lo > hi) return -1;
int m = (lo + hi) / 2;
int cmp = x.compareTo(sorted[m]);
if (cmp < 0) return binarySearch(sorted, x, lo, m - 1);
else if (cmp > 0) return binarySearch(sorted, x, m + 1, hi);
else return m;
}
N=1
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
N
1 | 2 | 2 | | | 3 | | | | | | | |
C(N)
N=2
N=3
Binary Search (Exact Count)
Goal: Find worst case runtime in terms of N = hi - lo + 1 [i.e. # of items]
static int binarySearch(String[] sorted, String x, int lo, int hi)
if (lo > hi) return -1;
int m = (lo + hi) / 2;
int cmp = x.compareTo(sorted[m]);
if (cmp < 0) return binarySearch(sorted, x, lo, m - 1);
else if (cmp > 0) return binarySearch(sorted, x, m + 1, hi);
else return m;
}
N=1
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
N
1 | 2 | 2 | 3 | 3 | 3 | 3 | | | | | | |
C(N)
N=2
N=3
4
5
6
7
Binary Search (Exact Count)
Goal: Find worst case runtime in terms of N = hi - lo + 1 [i.e. # of items]
static int binarySearch(String[] sorted, String x, int lo, int hi)
if (lo > hi) return -1;
int m = (lo + hi) / 2;
int cmp = x.compareTo(sorted[m]);
if (cmp < 0) return binarySearch(sorted, x, lo, m - 1);
else if (cmp > 0) return binarySearch(sorted, x, m + 1, hi);
else return m;
}
N=1
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
N
1 | 2 | 2 | 3 | 3 | 3 | 3 | 4 | 4 | 4 | 4 | 4 | 4 |
C(N)
N=2
N=3
4
5
6
7
8
9
...
Binary Search (Exact Count)
Goal: Find worst case runtime in terms of N = hi - lo + 1 [i.e. # of items]
static int binarySearch(String[] sorted, String x, int lo, int hi)
if (lo > hi) return -1;
int m = (lo + hi) / 2;
int cmp = x.compareTo(sorted[m]);
if (cmp < 0) return binarySearch(sorted, x, lo, m - 1);
else if (cmp > 0) return binarySearch(sorted, x, m + 1, hi);
else return m;
}
N=1
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
N
1 | 2 | 2 | 3 | 3 | 3 | 3 | 4 | 4 | 4 | 4 | 4 | 4 |
C(N)
N=2
N=3
4
5
6
7
8
9
...
C(N) = ⌊log2(N)⌋+1
Binary Search (Exact Count)
Goal: Find worst case runtime in terms of N = hi - lo + 1 [i.e. # of items]
static int binarySearch(String[] sorted, String x, int lo, int hi)
if (lo > hi) return -1;
int m = (lo + hi) / 2;
int cmp = x.compareTo(sorted[m]);
if (cmp < 0) return binarySearch(sorted, x, lo, m - 1);
else if (cmp > 0) return binarySearch(sorted, x, m + 1, hi);
else return m;
}
N=1
N=2
N=3
4
5
6
7
8
9
...
Handy Big Theta Properties
Goal: Simplify Θ(⌊log2(N)⌋)
⌊log2(N)⌋ = Θ(log N)
For proof:
See online textbook exercises.
Since base is irrelevant, we omit from our big theta expression. We also omit the parenthesis around N for aesthetic reasons.
Binary Search (Exact Count)
Goal: Find worst case runtime in terms of N = hi - lo + 1 [i.e. # of items]
… and we’re done!
static int binarySearch(String[] sorted, String x, int lo, int hi)
if (lo > hi) return -1;
int m = (lo + hi) / 2;
int cmp = x.compareTo(sorted[m]);
if (cmp < 0) return binarySearch(sorted, x, lo, m - 1);
else if (cmp > 0) return binarySearch(sorted, x, m + 1, hi);
else return m;
}
N=1
N=2
N=3
4
5
6
7
8
9
...
Binary Search (using Recurrence Relations)
Approach: Measure number of string comparisons for N = hi - lo + 1.
Can show that C(N) = Θ(log N). Beyond scope of class, so won’t solve in slides.
static int binarySearch(String[] sorted, String x, int lo, int hi)
if (lo > hi) return -1;
int m = (lo + hi) / 2;
int cmp = x.compareTo(sorted[m]);
if (cmp < 0) return binarySearch(sorted, x, lo, m - 1);
else if (cmp > 0) return binarySearch(sorted, x, m + 1, hi);
else return m;
}