|
1
|
- Heapsort
- Priority Queues
- Quicksort
|
|
2
|
- A heap is a “complete” binary tree, usually represented as an array:
|
|
3
|
- To represent a heap as an array:
- Parent(i) { return ëi/2û; }
- Left(i) { return 2*i; }
- right(i) { return 2*i + 1; }
|
|
4
|
- 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
- The largest value is thus stored at the root (A[1])
- Because the heap is a binary tree, the height of any node is at most Q(lg n)
|
|
5
|
- 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
- Action: let the value of the parent node “float down” so subtree at i
satisfies the heap property
- If A[i] < A[l] or A[i] < A[r], swap A[i] with the largest of
A[l] and A[r]
- Recurse on that subtree
- Running time: O(h), h = height of heap = O(lg n)
|
|
6
|
- 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
|
|
7
|
- // 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);
- }
|
|
8
|
- 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?
|
|
9
|
- 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
- CLR 7.3 uses this fact to prove that BuildHeap() takes O(n) time
|
|
10
|
- 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)]
|
|
11
|
- Heapsort(A)
- {
- BuildHeap(A);
- for (i = length(A) downto 2)
- {
- Swap(A[1], A[i]);
- heap_size(A) -= 1;
- Heapify(A, 1);
- }
- }
|
|
12
|
- 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)
|
|
13
|
- Heapsort is a nice algorithm, but in practice Quicksort (coming up)
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?
|
|
14
|
- 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?
|
|
15
|
- And now, a real-world example…
|
|
16
|
- And now, a real-world example…combat billiards
- Sort of like pool...
- Except you’re trying to
kill the other players…
- And the table is the size
of a polo field…
- And the balls are the
size of Suburbans...
- And instead of a cue
you drive a vehicle
with a ram on it
- Problem: how do you simulate the physics?
|
|
17
|
- Simplifying assumptions:
- G-rated version: No players
- Just n balls bouncing around
- No spin, no friction
- Easy to calculate the positions of the balls at time Tn from
time Tn-1 if there are no collisions in between
- Simple elastic collisions
|
|
18
|
- Assume we know how to compute when two moving spheres will intersect
- Given the state of the system, we can calculate when the next collision
will occur for each ball
- At each collision Ci:
- Advance the system to the time Ti of the collision
- Recompute the next collision for the ball(s) involved
- Find the next overall collision Ci+1 and repeat
- How should we keep track of all these collisions and when they occur?
|
|
19
|
- HeapInsert(A, key) // what’s
running time?
- {
- heap_size[A] ++;
- i = heap_size[A];
- while (i > 1 AND
A[Parent(i)] < key)
- {
- A[i] = A[Parent(i)];
- i = Parent(i);
- }
- A[i] = key;
- }
|
|
20
|
- HeapMaximum(A)
- {
- // This one is really tricky:
- return A[i];
- }
|
|
21
|
- HeapExtractMax(A)
- {
- if (heap_size[A] < 1) {
error; }
- max = A[1];
- A[1] = A[heap_size[A]]
- heap_size[A] --;
- Heapify(A, 1);
- return max;
- }
|
|
22
|
- Extract the next collision Ci from the queue
- Advance the system to the time Ti of the collision
- Recompute the next collision(s) for the ball(s) involved
- Insert collision(s) into the queue, using the time of occurrence as the
key
- Find the next overall collision Ci+1 and repeat
|
|
23
|
- More natural to use Minimum() and ExtractMin()
- What if a player hits a ball?
- Need to code up a Delete() operation
- How? What will the running time
be?
|
|
24
|
- Sorts in place
- Sorts O(n lg n) in the average case
- Sorts O(n2) in the worst case
- So why would people use it instead of merge sort?
|