Day4.2 Algorithms Part1
Day4.2 Algorithms Part1
n
Lower Bound Notation
• We say InsertionSort’s run time is (n)
• In general a function
– f(n) is (g(n)) if positive constants c and n0 such
that 0 cg(n) f(n) n n0
• Proof:
– Suppose run time is an + b
• Assume a and b are positive (what if b is negative?)
– an an + b
Asymptotic Tight Bound
• A function f(n) is (g(n)) if positive
constants c1, c2, and n0 such that
• Theorem
– f(n) is (g(n)) iff f(n) is both O(g(n)) and (g(n))
The problem of sorting
Example:
Input: 8 2 4 9 3 6
Output: 2 3 4 6 8 9
Example of insertion sort
8 2 4 9 3 6
Example of insertion sort
8 2 4 9 3 6
Example of insertion sort
8 2 4 9 3 6
2 8 4 9 3 6
Example of insertion sort
8 2 4 9 3 6
2 8 4 9 3 6
Example of insertion sort
8 2 4 9 3 6
2 8 4 9 3 6
2 4 8 9 3 6
Example of insertion sort
8 2 4 9 3 6
2 8 4 9 3 6
2 4 8 9 3 6
Example of insertion sort
8 2 4 9 3 6
2 8 4 9 3 6
2 4 8 9 3 6
2 4 8 9 3 6
Example of insertion sort
8 2 4 9 3 6
2 8 4 9 3 6
2 4 8 9 3 6
2 4 8 9 3 6
Example of insertion sort
8 2 4 9 3 6
2 8 4 9 3 6
2 4 8 9 3 6
2 4 8 9 3 6
2 3 4 8 9 6
Example of insertion sort
8 2 4 9 3 6
2 8 4 9 3 6
2 4 8 9 3 6
2 4 8 9 3 6
2 3 4 8 9 6
Example of insertion sort
8 2 4 9 3 6
2 8 4 9 3 6
2 4 8 9 3 6
2 4 8 9 3 6
2 3 4 8 9 6
2 3 4 6 8 9 done
Running time
DEF:
(g(n)) = { f (n) : there exist positive constants c1, c2, and
n0 such that 0 c1 g(n) f (n) c2 g(n)
for all n n0 }
Basic manipulations:
• Drop low-order terms; ignore leading constants.
• Example: 3n3 + 90n2 – 5n + 6046 = (n3)
Asymptotic performance
When n gets large enough, a (n2) algorithm
always beats a (n3) algorithm.
.
• Asymptotic analysis is a
useful tool to help to
structure our thinking
toward better algorithm
• We shouldn’t ignore
T(n) asymptotically slower
algorithms, however.
• Real-world design
n0 situations often call for a
n
careful balancing
Insertion sort analysis
Worst case: Input reverse sorted.
n
T (n) = ( j ) = ( n 2) [arithmetic series]
j=2
Average case: All permutations equally likely.
n
T (n) = ( j / 2 ) = (n 2 )
j=2
Is insertion sort a fast sorting algorithm?
• Moderately so, for small n.
• Not at all, for large n.
Example 2:Merge sort
MERGE-SORT A[1 . . n]
1. If n = 1, done.
2. Recursively sort A[ 1 . . n/2 ]
and A[ n/2+1 . . n ] .
3. “Merge” the 2 sorted lists.
1
Merging two sorted arrays
20 12 20 12
13 11 13 11
7 9 7 9
2 1 2
1
Merging two sorted arrays
20 12 20 12
13 11 13 11
7 9 7 9
2 1 2
1 2
Merging two sorted arrays
20 12 20 12 20 12
13 11 13 11 13 11
7 9 7 9 7 9
2 1 2
1 2
Merging two sorted arrays
20 12 20 12 20 12
13 11 13 11 13 11
7 9 7 9 7 9
2 1 2
1 2 7
Merging two sorted arrays
20 12 20 12 20 12 20 12
13 11 13 11 13 11 13 11
7 9 7 9 7 9 9
2 1 2
1 2 7
Merging two sorted arrays
20 12 20 12 20 12 20 12
13 11 13 11 13 11 13 11
7 9 7 9 7 9 9
2 1 2
1 2 7 9
Merging two sorted arrays
20 12 20 12 20 12 20 12 20 12
13 11 13 11 13 11 13 11 13 11
7 9 7 9 7 9 9
2 1 2
1 2 7 9
Merging two sorted arrays
20 12 20 12 20 12 20 12 20 12
13 11 13 11 13 11 13 11 13 11
7 9 7 9 7 9 9
2 1 2
1 2 7 9 11
Merging two sorted arrays
20 12 20 12 20 12 20 12 20 12 20 12
13 11 13 11 13 11 13 11 13 11 13
7 9 7 9 7 9 9
2 1 2
1 2 7 9 11
Merging two sorted arrays
20 12 20 12 20 12 20 12 20 12 20 12
13 11 13 11 13 11 13 11 13 11 13
7 9 7 9 7 9 9
2 1 2
1 2 7 9 11 12
Merging two sorted arrays
20 12 20 12 20 12 20 12 20 12 20 12
13 11 13 11 13 11 13 11 13 11 13
7 9 7 9 7 9 9
2 1 2
1 2 7 9 11 12
(1)
Recursion tree
Solve T(n) = 2T(n/2) + cn, where c > 0 is constant.
cn
cn/2 cn/2
h = lg n cn/4 cn/4 cn/4 cn/4
(1)
Recursion tree
Solve T(n) = 2T(n/2) + cn, where c > 0 is constant.
cn cn
cn/2 cn/2
h = lg n cn/4 cn/4 cn/4 cn/4
(1)
Recursion tree
Solve T(n) = 2T(n/2) + cn, where c > 0 is constant.
cn cn
cn/2 cn/2 cn
h = lg n cn/4 cn/4 cn/4 cn/4
(1)
Recursion tree
Solve T(n) = 2T(n/2) + cn, where c > 0 is constant.
cn cn
cn/2 cn/2 cn
h = lg n cn/4 cn/4 cn/4 cn/4 cn
…
(1)
Recursion tree
Solve T(n) = 2T(n/2) + cn, where c > 0 is constant.
cn cn
cn/2 cn/2 cn
h = lg n cn/4 cn/4 cn/4 cn/4 cn
…
(1) #leaves = n (n)
Recursion tree
Solve T(n) = 2T(n/2) + cn, where c > 0 is constant.
cn cn
cn/2 cn/2 cn
h = lg n cn/4 cn/4 cn/4 cn/4 cn
…
(1) #leaves = n (n)
Total = (n lg n)
Conclusions
14 10
8 7 9 3
2 4 1
14 10
8 7 9 3
2 4 1 1 1 1 1 1
14 10
8 7 9 3
A = 16 14 10 8 7 9 3 2 4 1 =
2 4 1
Heaps
• To represent a complete binary tree as an
array:
– The root node is A[1]
– Node i is A[i]
– The parent of node i is A[i/2] (note: integer divide)
– The left child of node i is A[2i]
– The right child of node i is A[2i + 1] 16
14 10
8 7 9 3
A = 16 14 10 8 7 9 3 2 4 1 =
2 4 1
Referencing Heap Elements
• So…
Parent(i) { return i/2; }
Left(i) { return 2*i; }
right(i) { return 2*i + 1; }
• An aside: How would you implement this
most efficiently?
• Another aside: Really?
The Heap Property
• Heaps also satisfy the heap property:
A[Parent(i)] A[i] for all nodes i > 1
– In other words, the value of a node is at most the
value of its parent
– Where is the largest element in a heap stored?
• Definitions:
– The height of a node in the tree = the number of
edges on the longest downward path to a leaf
– The height of a tree = the height of its root
Heap Height
• What is the height of an n-element heap?
Why?
• This is nice: basic heap operations take at
most time proportional to the height of the
heap
Heap Operations: Heapify()
• Heapify(): maintain the heap property
– Given: a node i in the heap with children l and r
– Given: two subtrees rooted at l and r, assumed to
be heaps
– Problem: The subtree rooted at i may violate the
heap property (How?)
– Action: let the value of the parent node “float
down” so subtree at i satisfies the heap property
• What do you suppose will be the basic operation
between i, l, and r?
Heap Operations: Heapify()
Heapify(A, i)
{
l = Left(i); r = Right(i);
if (l <= heap_size(A) && A[l] > A[i])
largest = l;
else
largest = i;
if (r <= heap_size(A) && A[r] > A[largest])
largest = r;
if (largest != i)
Swap(A, i, largest);
Heapify(A, largest);
}
Heapify() Example
16
4 10
14 7 9 3
2 8 1
A = 16 4 10 14 7 9 3 2 8 1
Heapify() Example
16
4 10
14 7 9 3
2 8 1
A = 16 4 10 14 7 9 3 2 8 1
Heapify() Example
16
4 10
14 7 9 3
2 8 1
A = 16 4 10 14 7 9 3 2 8 1
Heapify() Example
16
14 10
4 7 9 3
2 8 1
A = 16 14 10 4 7 9 3 2 8 1
Heapify() Example
16
14 10
4 7 9 3
2 8 1
A = 16 14 10 4 7 9 3 2 8 1
Heapify() Example
16
14 10
4 7 9 3
2 8 1
A = 16 14 10 4 7 9 3 2 8 1
Heapify() Example
16
14 10
8 7 9 3
2 4 1
A = 16 14 10 8 7 9 3 2 4 1
Heapify() Example
16
14 10
8 7 9 3
2 4 1
A = 16 14 10 8 7 9 3 2 4 1
Heapify() Example
16
14 10
8 7 9 3
2 4 1
A = 16 14 10 8 7 9 3 2 4 1
Analyzing Heapify(): Informal
• Aside from the recursive call, what is the
running time of Heapify()?
• How many times can Heapify() recursively
call itself?
• What is the worst-case running time of
Heapify() on a heap of size n?
Analyzing Heapify(): Formal
• Fixing up relationships between i, l, and r
takes (1) time
• If the heap at i has n elements, how many
elements can the subtrees at l or r have?
– Draw it
• Answer: 2n/3 (worst case: bottom row 1/2
full)
• So time taken by Heapify() is given by
T(n) T(2n/3) + (1)
2/3 bound
• In the worst case, for the heap with i internal
nodes and r leaves,
• 2i = r + i – 1
Fraction of nodes in left subtree =
• => r = i + 1 (2m+1)/(3m+2) ≤ 2/3
m m
m +1
Analyzing Heapify(): Formal
• So we have
T(n) T(2n/3) + (1)
• By case 2 of the Master Theorem,
T(n) = O(lg n)
• Thus, Heapify() takes logarithmic time
Heap Operations: BuildHeap()
• We can build a heap in a bottom-up manner
by running Heapify() on successive
subarrays
– Fact: for array of length n, all elements in range
A[n/2 + 1 .. n] are heaps (Why?)
– So:
• Walk backwards through the array from n/2 to 1, calling
Heapify() on each node.
• Order of processing guarantees that the children of
node i are heaps when i is processed
BuildHeap()
// given an unsorted array A, make A a heap
BuildHeap(A)
{
heap_size(A) = length(A);
for (i = length[A]/2 downto 1)
Heapify(A, i);
}
BuildHeap() Example
• Work through example
A = {4, 1, 3, 2, 16, 9, 10, 14, 8, 7}
1 3
2 16 9 10
14 8 7
Analyzing BuildHeap()
• Each call to Heapify() takes O(lg n) time
• There are O(n) such calls (specifically, n/2)
• Thus the running time is O(n lg n)
– Is this a correct asymptotic upper bound?
– Is this an asymptotically tight bound?
• A tighter bound is O(n)
– How can this be? Is there a flaw in the above
reasoning?
Analyzing BuildHeap(): Tight
• To Heapify() a subtree takes O(h) time
where h is the height of the subtree
– h = O(lg m), m = # nodes in subtree
– The height of most subtrees is small
• Fact: an n-element heap has at most n/2h+1
nodes of height h
• Can be used to prove that BuildHeap()
takes O(n) time
Heapsort
• Given BuildHeap(), an in-place sorting
algorithm is easily constructed:
– Maximum element is at A[1]
– Discard by swapping with element at A[n]
• Decrement heap_size[A]
• A[n] now contains correct value
– Restore heap property at A[1] by calling
Heapify()
– Repeat, always swapping A[1] for A[heap_size(A)]
Heapsort
Heapsort(A)
{
BuildHeap(A);
for (i = length(A) downto 2)
{
Swap(A[1], A[i]);
heap_size(A) -= 1;
Heapify(A, 1);
}
}
Analyzing Heapsort
• The call to BuildHeap() takes O(n) time
• Each of the n - 1 calls to Heapify() takes
O(lg n) time
• Thus the total time taken by HeapSort()
= O(n) + (n - 1) O(lg n)
= O(n) + O(n lg n)
= O(n lg n)
Problems
• 1. Design an algorithm to merge k large sorted
arrays of a total of n elements using no more
than O(k) additional storage. Here k << n.
• 2. Design an algorithm which reads in n
integers one by one and then returns the k
smallest ones. Here k << n and n is unknown
to start with.
Priority Queues
• Heapsort is a nice algorithm, but in practice
Quicksort usually wins
• But the heap data structure is incredibly
useful for implementing priority queues
– A data structure for maintaining a set S of
elements, each with an associated value or key
– Supports the operations Insert(),
Maximum(), and ExtractMax()
– What might a priority queue be useful for?
Priority Queue Operations
• Insert(S, x) inserts the element x into set S
• Maximum(S) returns the element of S with
the maximum key
• ExtractMax(S) removes and returns the
element of S with the maximum key
• How could we implement these operations
using a heap?
Example of Insertion to Max Heap
• Add at the end, move upwards
20 20 21
15 2 15 5 15 20
14 10 14 10 2 14 10 2
initial location of new node insert 5 into heap insert 21 into heap
128
Example of Deletion from Max Heap
• EXTRACT_MAX
remove
20 10 15
15 2 15 2 14 2
14 10 14 10
129
Analysis of Algorithms
r s t
Rearranging Elements In An Array
• while(s ≤ t) {
if(A[s] < pivot) {
swap A[r] with A[s];
increment r and s;
}
else if(A[s] == pivot) s++;
else {
swap A[s] with A[t];
t--;
}
}
}
Quicksort
• Sorts in place
• Sorts O(n lg n) in the average case
• Sorts O(n2) in the worst case
Quicksort
• Another divide-and-conquer algorithm
– The array A[p..r] is partitioned into two non-empty
subarrays A[p..q] and A[q+1..r]
• Invariant: All elements in A[p..q] are less than all
elements in A[q+1..r]
– The subarrays are recursively sorted by calls to
quicksort
– Unlike merge sort, no combining step: two
subarrays form an already-sorted array
Quicksort Code
Quicksort(A, p, r)
{
if (p < r)
{
q = Partition(A, p, r);
Quicksort(A, p, q);
Quicksort(A, q+1, r);
}
}
Partition
• Clearly, all the action takes place in the
partition() function
– Rearranges the subarray in place
– End result:
• Two subarrays
• All values in first subarray all values in second
– Returns the index of the “pivot” element separating
the two subarrays
• How do you suppose we implement this function?
Partition In Words
• Partition(A, p, r):
– Select an element to act as the “pivot” (which?)
– Grow two regions, A[p..i] and A[j..r]
• All elements in A[p..i] <= pivot
• All elements in A[j..r] >= pivot
– Increment i until A[i] >= pivot
– Decrement j until A[j] <= pivot
– Swap A[i] and A[j]
– Repeat until i >= j
– Return j
Partition Code
Partition(A, p, r)
x = A[p];
Illustrate on
i = p - 1;
j = r + 1;
A = {5, 3, 2, 6, 4, 1, 3, 7};
while (TRUE)
repeat
j--;
until A[j] <= x;
repeat
i++; What is the running time of
until A[i] >= x; partition()?
if (i < j)
Swap(A, i, j);
else
return j;
Analyzing Quicksort
• What will be the worst case for the algorithm?
– Partition is always unbalanced
• What will be the best case for the algorithm?
– Partition is perfectly balanced
• Which is more likely?
– The latter, by far, except...
• Will any particular input elicit the worst case?
– Yes: Already-sorted input
Analyzing Quicksort
• In the worst case:
T(1) = (1)
T(n) = T(n - 1) + (n)
• Works out to
T(n) = (n2)
Analyzing Quicksort
• In the best case:
T(n) = 2T(n/2) + (n)
• What does this work out to?
T(n) = (n lg n)
Improving Quicksort
• The real liability of quicksort is that it runs in
O(n2) on already-sorted input
• Book discusses two solutions:
– Randomize the input array, OR
– Pick a random pivot element
• How will these solve the problem?
– By insuring that no particular input can be chosen
to make quicksort run in O(n2) time
Analyzing Quicksort: Average Case
• Assuming random input, average-case running
time is much closer to O(n lg n) than O(n2)
• First, a more intuitive explanation/example:
– Suppose that partition() always produces a 9-to-1
split. This looks quite unbalanced!
– The recurrence is thus: Use n instead of O(n)
for convenience (how?)
T(n) = T(9n/10) + T(n/10) + n
– How deep will the recursion go? (draw it)
Analyzing Quicksort: Average Case
• Intuitively, a real-life run of quicksort will
produce a mix of “bad” and “good” splits
– Randomly distributed among the recursion tree
– Pretend for intuition that they alternate between
best-case (n/2 : n/2) and worst-case (n-1 : 1)
– What happens if we bad-split root node, then
good-split the resulting size (n-1) node?
Analyzing Quicksort: Average Case
• Intuitively, a real-life run of quicksort will
produce a mix of “bad” and “good” splits
– Randomly distributed among the recursion tree
– Pretend for intuition that they alternate between
best-case (n/2 : n/2) and worst-case (n-1 : 1)
Analyzing Quicksort: Average Case
• Intuitively, a real-life run of quicksort will
produce a mix of “bad” and “good” splits
– Randomly distributed among the recursion tree
– Pretend for intuition that they alternate between
best-case (n/2 : n/2) and worst-case (n-1 : 1)
– What happens if we bad-split root node, then good-
split the resulting size (n-1) node?
• We end up with three subarrays, size 1, (n-1)/2, (n-1)/2
• Combined cost of splits = n + n -1 = 2n -1 = O(n)
• No worse than if we had good-split the root node!
Analyzing Quicksort: Average Case
• Intuitively, the O(n) cost of a bad split
(or 2 or 3 bad splits) can be absorbed
into the O(n) cost of each good split
• Thus running time of alternating bad and good
splits is still O(n lg n), with slightly higher
constants
• How can we be more rigorous?
Analyzing Quicksort: Average Case
• For simplicity, assume:
– All inputs distinct (no repeats)
– Slightly different partition() procedure
• partition around a random element, which is not
included in subarrays
• all splits (0:n-1, 1:n-2, 2:n-3, … , n-1:0) equally likely
• What is the probability of a particular split
happening?
• Answer: 1/n
Analyzing Quicksort: Average Case
• So partition generates splits
(0:n-1, 1:n-2, 2:n-3, … , n-2:1, n-1:0)
each with probability 1/n
• If T(n) is the expected running time,
1 n −1
T (n ) = T (k ) + T (n − 1 − k ) + (n )
n k =0
• What is each term under the summation for?
• What is the (n) term for?
Analyzing Quicksort: Average Case
• So…
1 n −1
T (n ) = T (k ) + T (n − 1 − k ) + (n )
n k =0
2 n −1
= T (k ) + (n )
n k =0
Analyzing Quicksort: Average Case
• We can solve this recurrence using the
dreaded substitution method
– Guess the answer
– Assume that the inductive hypothesis holds
– Substitute it in for some value < n
– Prove that it follows for n
Analyzing Quicksort: Average Case
• We can solve this recurrence using the
dreaded substitution method
– Guess the answer
• What’s the answer?
– Assume that the inductive hypothesis holds
– Substitute it in for some value < n
– Prove that it follows for n
Analyzing Quicksort: Average Case
• We can solve this recurrence using the
dreaded substitution method
– Guess the answer
• T(n) = O(n lg n)
– Assume that the inductive hypothesis holds
– Substitute it in for some value < n
– Prove that it follows for n
Tightly Bounding
The Key Summation
(n − 1)(n)
n −1 n 2 −1
The summation bound so
k lg k
lg n − kfar
k =1 2 k =1
n 2 −1
n(n − 1)lg n − k
1 Rearrange
What arefirst
we term,
doing place
upper bound on second
here?
2 k =1
1 n n
n(n − 1)lg n − − 1
1
X What
Guassian series
are we doing?
2 2 2 2
1 2
2
( 8
)
1 2 n
n lg n − n lg n − n +
4
Multiply it
What are we doing?
all out
Tightly Bounding
The Key Summation
( )
n −1
1 2 1 2 n
k =1
k lg k n lg n − n lg n − n +
2 8 4
1 2 1 2
n lg n − n when n 2
2 8
Done!!!
How Fast Can We Sort?
• We will provide a lower bound, then beat it
– How do you suppose we’ll beat it?
• First, an observation: all of the sorting
algorithms so far are comparison sorts
– The only operation used to gain ordering
information about a sequence is the pairwise
comparison of two elements
– Theorem: all comparison sorts are (n lg n)
• A comparison sort must do O(n) comparisons (why?)
• What about the gap between O(n) and O(n lg n)
David Luebke
159
12/08/2022
Decision Trees
• Decision trees provide an abstraction of
comparison sorts
– A decision tree represents the comparisons made
by a comparison sort. Every thing else ignored
David Luebke
160
12/08/2022
Decision Trees
• Decision trees can model comparison sorts.
For a given algorithm:
– One tree for each n
– Tree paths are all possible execution traces
– What’s the longest path in a decision tree for
insertion sort? For merge sort?
• What is the asymptotic height of any decision
tree for sorting n elements?
• Answer: (n lg n) (now let’s prove it…)
David Luebke
161
12/08/2022
Lower Bound For
Comparison Sorting
• Thm: Any decision tree that sorts n elements
has height (n lg n)
• What’s the minimum # of leaves?
• What’s the maximum # of leaves of a binary
tree of height h?
• Clearly the minimum # of leaves is less than or
equal to the maximum # of leaves
David Luebke
162
12/08/2022
Lower Bound For
Comparison Sorting
• So we have…
n! 2h
• Taking logarithms:
lg (n!) h
• Stirling’s approximation tells us:
n
n
n!
e n
n
• Thus: h lg e
David Luebke
163
12/08/2022
Lower Bound For
Comparison Sorting
• So we have
n
n
h lg
e
= n lg n − n lg e
= (n lg n )
164
12/08/2022
Lower Bound For
Comparison Sorts
• Thus the time to comparison sort n elements
is (n lg n)
• Corollary: Heapsort and Mergesort are
asymptotically optimal comparison sorts
• But Sorting in linear time is possible!
– How can we do better than (n lg n)?
David Luebke
165
12/08/2022
Search Vs. Sort and Search