Divide and Conquer II
Summer 2018 • Lecture 07/03
Announcements
Course Overview
Today’s Outline
So far...
Proving correctness
Proving runtime
Iterative
Recursive
Induction on the iteration, leveraging a loop variant
(e.g. insertion sort)
Induction on the input size (e.g. mergesort)
Defining and solving recurrence relations
Intuition
So far...
Proving correctness
Proving runtime
Iterative
Recursive
Induction on the iteration, leveraging a loop variant
(e.g. insertion sort)
Induction on the input size (e.g. mergesort)
Defining and solving recurrence relations
Intuition
So far...
Substitution Method
Substitution Method
Substitution Method
T(1) ≤ 1
T(n) ≤ 2T(n/2) + n
Substitution Method
T(1) ≤ 1
T(n) ≤ 2T(n/2) + n
Substitution Method
Substitution Method
T(n) = 10n when 1 ≤ n ≤ 10
T(n) = 3n + T(n/5) + T(n/2) otherwise
Guess O(n)
Substitution Method
T(n) = 10n when 1 ≤ n ≤ 10
T(n) = 3n + T(n/5) + T(n/2) otherwise
C is some constant we’ll have to fill in later!
C must be ≥ 10 since the recurrence states T(k) = 10k when 1 ≤ k ≤ 10
Solve for C to satisfy the inequality. C = 10 works.
Substitution Method
T(n) = 10n when 1 ≤ n ≤ 10
T(n) = 3n + T(n/5) + T(n/2) otherwise
Pretend we knew C = 10 all along.
Substitution Method
Today’s Outline
Linear-Time Selection
Linear-Time Selection
1
64
9
49
16
4
0
25
36
81
Linear-Time Selection
1
64
9
49
16
4
0
25
36
81
0
1
4
9
16
25
36
49
64
81
k=3
Not Quite Linear-Time Selection
def naive_select(A, k):
A = mergesort(A)
return A[k]
Worst-case runtime Θ(n log(n))
Linear-Time Selection
1
64
9
49
16
4
0
25
36
81
Linear-Time Selection
1
64
9
49
16
4
0
25
36
81
Select a pivot at random (for now)
Linear-Time Selection
1
64
9
49
16
4
0
25
36
81
Select a pivot at random (for now)
1
9
16
4
0
25
36
64
49
81
Partition around the pivot, such that all elements to the left are less than it and all elements to the right are greater than it�(Notice that the halves remain unsorted.)
Find element k=3 in this half since 36 occupies index 6 and k=3 < 6.
Linear-Time Selection
36
64
49
81
1
9
16
4
0
25
Linear-Time Selection
Select another pivot at random (for now)
36
64
49
81
1
9
16
4
0
25
Linear-Time Selection
Select another pivot at random (for now)
36
64
49
81
1
9
16
4
0
25
36
64
49
81
0
1
9
16
4
25
Partition around the pivot
Find element k=3-(1+1) in this half since 1 occupies index 1 and k=3 > 1.
Linear-Time Selection
Select another pivot at random (for now)
36
64
49
81
0
1
9
16
4
25
Linear-Time Selection
Select another pivot at random (for now)
36
64
49
81
0
1
4
9
16
25
Partition around the pivot
We found the element!
36
64
49
81
0
1
9
16
4
25
Linear-Time Selection
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k:
# The pivot is the kth smallest element!
return pivot
elif len(left) > k:
# The kth smallest element is left of the pivot
return select(left, k, c)
else:
# The kth smallest element is right of the pivot
return select(right, k-len(left)-1, c)
Linear-Time Selection
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k:
# The pivot is the kth smallest element!
return pivot
elif len(left) > k:
# The kth smallest element is left of the pivot
return select(left, k, c)
else:
# The kth smallest element is right of the pivot
return select(right, k-len(left)-1, c)
Linear-Time Selection
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k:
# The pivot is the kth smallest element!
return pivot
elif len(left) > k:
# The kth smallest element is left of the pivot
return select(left, k, c)
else:
# The kth smallest element is right of the pivot
return select(right, k-len(left)-1, c)
Linear-Time Selection
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k:
# The pivot is the kth smallest element!
return pivot
elif len(left) > k:
# The kth smallest element is left of the pivot
return select(left, k, c)
else:
# The kth smallest element is right of the pivot
return select(right, k-len(left)-1, c)
Linear-Time Selection
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k:
# The pivot is the kth smallest element!
return pivot
elif len(left) > k:
# The kth smallest element is left of the pivot
return select(left, k, c)
else:
# The kth smallest element is right of the pivot
return select(right, k-len(left)-1, c)
Linear-Time Selection
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k:
# The pivot is the kth smallest element!
return pivot
elif len(left) > k:
# The kth smallest element is left of the pivot
return select(left, k, c)
else:
# The kth smallest element is right of the pivot
return select(right, k-len(left)-1, c)
Linear-Time Selection
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k:
# The pivot is the kth smallest element!
return pivot
elif len(left) > k:
# The kth smallest element is left of the pivot
return select(left, k, c)
else:
# The kth smallest element is right of the pivot
return select(right, k-len(left)-1, c)
“Worst-case” runtime Θ(n2)
Linear-Time Selection
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k:
# The pivot is the kth smallest element!
return pivot
elif len(left) > k:
# The kth smallest element is left of the pivot
return select(left, k, c)
else:
# The kth smallest element is right of the pivot
return select(right, k-len(left)-1, c)
“Worst-case” runtime Θ(n2)
We’ll discuss this runtime later...
Linear-Time Selection
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k:
# The pivot is the kth smallest element!
return pivot
elif len(left) > k:
# The kth smallest element is left of the pivot
return select(left, k, c)
else:
# The kth smallest element is right of the pivot
return select(right, k-len(left)-1, c)
“Worst-case” runtime Θ(n2)
We’ll discuss this runtime later...
Note: this is different from the “worst-case” we saw for insertion sort (we’ll revisit during Randomized Algs).
Linear-Time Selection
def partition_about_pivot(A, pivot):
left, right = [], []
for i in range(len(A)):
if A[i] == pivot: continue
elif A[i] < pivot:
left.append(A[i])
else:
right.append(A[i])
return left, right
Linear-Time Selection
def partition_about_pivot(A, pivot):
left, right = [], []
for i in range(len(A)):
if A[i] == pivot: continue
elif A[i] < pivot:
left.append(A[i])
else:
right.append(A[i])
return left, right
Linear-Time Selection
def partition_about_pivot(A, pivot):
left, right = [], []
for i in range(len(A)):
if A[i] == pivot: continue
elif A[i] < pivot:
left.append(A[i])
else:
right.append(A[i])
return left, right
Linear-Time Selection
def partition_about_pivot(A, pivot):
left, right = [], []
for i in range(len(A)):
if A[i] == pivot: continue
elif A[i] < pivot:
left.append(A[i])
else:
right.append(A[i])
return left, right
Linear-Time Selection
def partition_about_pivot(A, pivot):
left, right = [], []
for i in range(len(A)):
if A[i] == pivot: continue
elif A[i] < pivot:
left.append(A[i])
else:
right.append(A[i])
return left, right
Linear-Time Selection
def partition_about_pivot(A, pivot):
left, right = [], []
for i in range(len(A)):
if A[i] == pivot: continue
elif A[i] < pivot:
left.append(A[i])
else:
right.append(A[i])
return left, right
Linear-Time Selection
def partition_about_pivot(A, pivot):
left, right = [], []
for i in range(len(A)):
if A[i] == pivot: continue
elif A[i] < pivot:
left.append(A[i])
else:
right.append(A[i])
return left, right
Worst-case runtime Θ(n)
Linear-Time Selection
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k: return pivot
elif len(left) > k: return select(left, k, c)
else: return select(right, k-len(left)-1, c)
Linear-Time Selection
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k: return pivot
elif len(left) > k: return select(left, k, c)
else: return select(right, k-len(left)-1, c)
Linear-Time Selection
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k: return pivot
elif len(left) > k: return select(left, k, c)
else: return select(right, k-len(left)-1, c)
Proving Correctness
def proof_of_correctness_helper(algorithm):
if algorithm.type == "iterative":
# 1) Find the loop invariant
# 2) Define the inductive hypothesis
# (internal state at iteration i)
# 3) Prove the base case (i=0)
# 4) Prove the inductive step (i => i+1)
# 5) Prove the conclusion (i=n => correct)
elif algorithm.type == "recursive":
# 1) Define the inductive hypothesis
# (correct for inputs of sizes 1 to i)
# 2) Prove the base case (i < small constant)
# 3) Prove the inductive step (i => i+1 OR
{1,2,...,i} => i+1)
# 4) Prove the conclusion (i=n => correct)
# TODO
Proving Correctness
Proving Correctness
Proving Correctness
Proving Correctness
Proving Correctness
Proving Correctness
Proving Correctness
Today’s Outline
Analyzing Runtime
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k: return pivot
elif len(left) > k: return select(left, k, c)
else: return select(right, k-len(left)-1, c)
Analyzing Runtime
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k: return pivot
elif len(left) > k: return select(left, k, c)
else: return select(right, k-len(left)-1, c)
O(n) len(L) == k
T(len(left)) + O(n) len(left) > k
T(len(right)) + O(n) len(left) < k
{
T(n) =
Analyzing Runtime
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k: return pivot
elif len(left) > k: return select(left, k, c)
else: return select(right, k-len(left)-1, c)
O(n) len(L) == k
T(len(left)) + O(n) len(left) > k
T(len(right)) + O(n) len(left) < k
{
T(n) =
The runtime for the recursive call to select
Analyzing Runtime
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k: return pivot
elif len(left) > k: return select(left, k, c)
else: return select(right, k-len(left)-1, c)
O(n) len(L) == k
T(len(left)) + O(n) len(left) > k
T(len(right)) + O(n) len(left) < k
{
T(n) =
The runtime to partition
about the chosen pivot
The runtime for the recursive call to select
Analyzing Runtime
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k: return pivot
elif len(left) > k: return select(left, k, c)
else: return select(right, k-len(left)-1, c)
O(n) len(L) == k
T(len(left)) + O(n) len(left) > k
T(len(right)) + O(n) len(left) < k
{
T(n) =
The runtime to partition
about the chosen pivot
The runtime for the recursive call to select
len(left) and len(right) depend on how we pick the pivot!
Analyzing Runtime in an Ideal World
Suppose T(n) = a⋅T(n/b) + O(nd). The Master method states:
O(ndlogn) if a = bd
O(nd) if a < bd
O(nlog_b(a)) if a > bd
{
T(n) =
Analyzing Runtime in an Ideal World
Suppose T(n) = a⋅T(n/b) + O(nd). The Master method states:
O(ndlogn) if a = bd
O(nd) if a < bd
O(nlog_b(a)) if a > bd
{
T(n) =
Analyzing Runtime in an Ideal World
Suppose T(n) = a⋅T(n/b) + O(nd). The Master method states:
O(ndlogn) if a = bd
O(nd) if a < bd
O(nlog_b(a)) if a > bd
{
T(n) =
Analyzing Runtime in an Ideal World
Suppose T(n) = a⋅T(n/b) + O(nd). The Master method states:
O(ndlogn) if a = bd
O(nd) if a < bd
O(nlog_b(a)) if a > bd
{
T(n) =
Analyzing Runtime in an Ideal World
Suppose T(n) = a⋅T(n/b) + O(nd). The Master method states:
O(ndlogn) if a = bd
O(nd) if a < bd
O(nlog_b(a)) if a > bd
{
T(n) =
Analyzing Runtime
Linear-Time Selection
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k:
# The pivot is the kth smallest element!
return pivot
elif len(left) > k:
# The kth smallest element is left of the pivot
return select(left, k, c)
else:
# The kth smallest element is right of the pivot
return select(right, k-len(left)-1, c)
“Worst-case” runtime Θ(n2)
Linear-Time Selection
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k:
# The pivot is the kth smallest element!
return pivot
elif len(left) > k:
# The kth smallest element is left of the pivot
return select(left, k, c)
else:
# The kth smallest element is right of the pivot
return select(right, k-len(left)-1, c)
“Worst-case” runtime Θ(n2)
We discussed this runtime from earlier!
Analyzing Runtime
Analyzing Runtime
Analyzing Runtime
Analyzing Runtime
Analyzing Runtime in an Ideal World
Suppose T(n) = a⋅T(n/b) + O(nd). The Master method states:
O(ndlogn) if a = bd
O(nd) if a < bd
O(nlog_b(a)) if a > bd
{
T(n) =
Reasonable
Analyzing Runtime in an Ideal World
Suppose T(n) = a⋅T(n/b) + O(nd). The Master method states:
O(ndlogn) if a = bd
O(nd) if a < bd
O(nlog_b(a)) if a > bd
{
T(n) =
Reasonable
Analyzing Runtime in an Ideal World
Suppose T(n) = a⋅T(n/b) + O(nd). The Master method states:
O(ndlogn) if a = bd
O(nd) if a < bd
O(nlog_b(a)) if a > bd
{
T(n) =
Reasonable
Analyzing Runtime in an Ideal World
Suppose T(n) = a⋅T(n/b) + O(nd). The Master method states:
O(ndlogn) if a = bd
O(nd) if a < bd
O(nlog_b(a)) if a > bd
{
T(n) =
Reasonable
Analyzing Runtime in an Ideal World
Suppose T(n) = a⋅T(n/b) + O(nd). The Master method states:
O(ndlogn) if a = bd
O(nd) if a < bd
O(nlog_b(a)) if a > bd
{
T(n) =
Reasonable
Analyzing Runtime in an Ideal World
Reasonable
1
9
16
4
0
25
36
64
49
81
The goal is to pick a pivot such that
3n/10 < len(left) < 7n/10 and 3n/10 < len(right) < 7n/10
Analyzing Runtime in an Ideal World
Reasonable
1
9
16
4
0
25
36
64
49
81
The goal is to pick a pivot such that
3n/10 < len(left) < 7n/10 and 3n/10 < len(right) < 7n/10
Analyzing Runtime in an Ideal World
Reasonable
1
9
16
4
0
25
36
64
49
81
The goal is to pick a pivot such that
3n/10 < len(left) < 7n/10 and 3n/10 < len(right) < 7n/10
Another Divide and Conquer Algorithm
Another Divide and Conquer Algorithm
Pro tip: making the inductive hypothesis i.e. assuming correctness of the algorithm on smaller inputs is a helpful technique for designing divide and conquer algorithms.
Another Divide and Conquer Algorithm
Pro tip: making the inductive hypothesis i.e. assuming correctness of the algorithm on smaller inputs is a helpful technique for designing divide and conquer algorithms.
Another Divide and Conquer Algorithm
Another Divide and Conquer Algorithm
Another Divide and Conquer Algorithm
{
{
{
{
{
{
{
{
{
{
{
{
sub-median
sub-median
sub-median
sub-median
sub-median
sub-median
sub-median
sub-median
sub-median
sub-median
sub-median
sub-median
Another Divide and Conquer Algorithm
{
{
{
{
{
{
{
{
{
{
{
{
sub-median
sub-median
sub-median
sub-median
sub-median
sub-median
sub-median
sub-median
sub-median
sub-median
sub-median
sub-median
median of sub-medians
Another Divide and Conquer Algorithm
{
{
{
{
{
{
{
{
{
{
{
{
sub-median
sub-median
sub-median
sub-median
sub-median
sub-median
sub-median
sub-median
sub-median
sub-median
sub-median
sub-median
median of sub-medians
median of the whole list
≈
Another Divide and Conquer Algorithm
2
11
9
3
13
5
16
4
6
12
...
19
14
Another Divide and Conquer Algorithm
2
11
9
3
13
5
16
4
6
12
17
23
10
7
21
8
18
15
1
20
22
19
14
2
11
9
3
13
5
16
4
6
12
...
19
14
Divide A into g = ⌈n/5⌉ groups
of at most 5 elements
g = ⌈n/5⌉ groups
at most 5 elements
Another Divide and Conquer Algorithm
2
11
9
3
13
5
16
4
6
12
17
23
10
7
21
8
18
15
1
20
22
19
14
2
11
9
3
13
5
16
4
6
12
...
19
14
2
11
9
3
13
5
16
4
6
12
17
23
10
7
21
8
18
15
1
20
22
19
14
Divide A into g = ⌈n/5⌉ groups
of at most 5 elements
g = ⌈n/5⌉ groups
at most 5 elements
Find the sub-median of each of the groups (yellow) and recursively call select to find the median of these sub-medians (pink).
Linear-Time Selection
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k:
# The pivot is the kth smallest element!
return pivot
elif len(left) > k:
# The kth smallest element is left of the pivot
return select(left, k, c)
else:
# The kth smallest element is right of the pivot
return select(right, k-len(left)-1, c)
“Worst-case” runtime Θ(n2)
Linear-Time Selection
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A) median_of_medians(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k:
# The pivot is the kth smallest element!
return pivot
elif len(left) > k:
# The kth smallest element is left of the pivot
return select(left, k, c)
else:
# The kth smallest element is right of the pivot
return select(right, k-len(left)-1, c)
Analyzing Runtime
2
11
9
3
13
5
16
4
6
12
17
23
10
7
21
8
18
15
1
20
22
19
14
Analyzing Runtime
2
3
9
11
13
5
4
6
16
12
8
1
15
18
20
22
19
14
10
7
17
23
21
Analyzing Runtime
2
3
9
11
13
5
4
6
16
12
8
1
15
18
20
22
19
14
10
7
17
23
21
Analyzing Runtime
2
3
9
11
13
5
4
6
16
12
8
1
15
18
20
22
19
14
10
7
17
23
21
3 elements from each small group with a median smaller than the median of medians.
2 elements from the small group containing the median of medians.
Analyzing Runtime
2
3
9
11
13
5
4
6
16
12
8
1
15
18
20
22
19
14
10
7
17
23
21
3 elements from each small group with a median smaller than the median of medians.
2 elements from the small group containing the median of medians.
To exclude the list with the median of medians.
Analyzing Runtime
2
3
9
11
13
5
4
6
16
12
8
1
15
18
20
22
19
14
10
7
17
23
21
3 elements from each small group with a median smaller than the median of medians.
2 elements from the small group containing the median of medians.
To exclude the list with the median of medians.
To exclude the list with the leftovers.
Analyzing Runtime
2
3
9
11
13
5
4
6
16
12
8
1
15
18
20
22
19
14
10
7
17
23
21
At most everything besides these elements
n-1 is for all of the elements except for the median of medians.
Analyzing Runtime
2
3
9
11
13
5
4
6
16
12
8
1
15
18
20
22
19
14
10
7
17
23
21
At most everything besides these elements
n-1 is for all of the elements except for the median of medians.
3n/10 - 4 ≤ len(left) ≤ 7n/10 + 3
3n/10 - 4 ≤ len(right) ≤ 7n/10 + 3
Analyzing Runtime
median_of_medians will choose a pivot greater than at least
3⋅(⌈g/2⌉ - 2) + 2 ≥ 3n/10 - 4 elements.
median_of_medians will choose a pivot less than at most 7n/10 + 3 elements.
3n/10 - 4 ≤ len(left) ≤ 7n/10 + 3
3n/10 - 4 ≤ len(right) ≤ 7n/10 + 3
Analyzing Runtime
median_of_medians will choose a pivot greater than at least
3⋅(⌈g/2⌉ - 2) + 2 ≥ 3n/10 - 4 elements.
median_of_medians will choose a pivot less than at most 7n/10 + 3 elements.
Linear-Time Selection
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A) median_of_medians(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k:
# The pivot is the kth smallest element!
return pivot
elif len(left) > k:
# The kth smallest element is left of the pivot
return select(left, k, c)
else:
# The kth smallest element is right of the pivot
return select(right, k-len(left)-1, c)
Analyzing Runtime
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = median_of_medians(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k: return pivot
elif len(left) > k: return select(left, k, c)
else: return select(right, k-len(left)-1, c)
Analyzing Runtime
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = median_of_medians(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k: return pivot
elif len(left) > k: return select(left, k, c)
else: return select(right, k-len(left)-1, c)
Analyzing Runtime
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = median_of_medians(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k: return pivot
elif len(left) > k: return select(left, k, c)
else: return select(right, k-len(left)-1, c)
Analyzing Runtime
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = median_of_medians(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k: return pivot
elif len(left) > k: return select(left, k, c)
else: return select(right, k-len(left)-1, c)
Analyzing Runtime
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = median_of_medians(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k: return pivot
elif len(left) > k: return select(left, k, c)
else: return select(right, k-len(left)-1, c)
Analyzing Runtime
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = median_of_medians(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k: return pivot
elif len(left) > k: return select(left, k, c)
else: return select(right, k-len(left)-1, c)
Analyzing Runtime
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = median_of_medians(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k: return pivot
elif len(left) > k: return select(left, k, c)
else: return select(right, k-len(left)-1, c)
Substitution Method
T(n) = nlog(n) when n ≤ 100
T(n) ≤ T(n/5) + T(7n/10) + O(n)
Guess O(n)
Substitution Method
C is some constant we’ll have to fill in later!
C must be ≥ log(n) for n ≤ 100, so C ≥ 7.
Solve for C to satisfy the inequality. C ≥ 10d works.
T(n) = nlog(n) when n ≤ 100
T(n) ≤ T(n/5) + T(7n/10) + O(n)
Substitution Method
T(n) = nlog(n) when n ≤ 100
T(n) ≤ T(n/5) + T(7n/10) + O(n)
Substitution Method
Today’s Outline
Linear-Time Selection
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A) median_of_medians(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k:
# The pivot is the kth smallest element!
return pivot
elif len(left) > k:
# The kth smallest element is left of the pivot
return select(left, k, c)
else:
# The kth smallest element is right of the pivot
return select(right, k-len(left)-1, c)
Worst-case runtime Θ(n)
Linear-Time Selection
def select(A, k, c=100):
if len(A) <= c:
return naive_select(A, k)
pivot = random.choice(A) median_of_medians(A)
left, right = partition_about_pivot(A, pivot)
if len(left) == k:
# The pivot is the kth smallest element!
return pivot
elif len(left) > k:
# The kth smallest element is left of the pivot
return select(left, k, c)
else:
# The kth smallest element is right of the pivot
return select(right, k-len(left)-1, c)
Worst-case runtime Θ(n)
Note: back to talking about the same worst-case we saw for insertion sort (we’ll revisit during Randomized Algs).