Heaps
Heaps
The binary heap
data structure is an array object that we can view as a nearly complete binary tree. Each node of the tree corresponds to an element of the array. The tree is completely filled on all levels except possibly the lowes, which is filled from the left up to a point that is the elements in the subarray \(A[\lfloor \frac{n}{2} \rfloor + 1 ... n]\) are all leaves.
An array \(A\) that represents a heap is an object with two attributes:
- \(A.length\): gives the number of elements in the array. \(A[1:A.length]\).
- \(A.heap\_size\): gives how many elements in the heap are stored within array \(A\). \(A[1:A.heap\_size:A.length]\)
Given the index \(i\) of a node, we can easily compute the indices of its parent, left child and right child by:
parent
: \(\lfloor \frac{i}{2} \rfloor\) (by shifting right 1 biti >> 1
)left child
: \(2i\) (by shifting left 1 biti << 1
)right child
: \(2i + 1\) (by shifting left 1 bit and add 1i << 1 + 1
)
There are two types of binary heap:
Max heap
: satisfies themax heap property
that for every node \(i\) other than the root \(A[parent(i)] \geq A[i]\), that is the maximum value in the array is stored in the root and the subtree rooted at a node contains values no larger than that contained at the node itself (heap sorts).Min heap
: satisfies themin heap property
that is organized in the opposite way, for every node \(i\) other than the root \(A[parent(i) \leq A[i]]\) (priority queues)
1 | class Heap: |
Maintaining the Heap Property
max_heapify(A, i)
, when it is called, this function assumes that the binary trees rooted at \(left(i)\) and \(right(i)\) are max heaps but that \(A[i]\) might be smaller than its children:
- At each step, the largest of the elements \(A[i], left(i), right(i)\) is determined, and its index is stored in
largest
. - If \(A[i]\) is largest, then the subtree rooted at node \(i\) is already a max-heap, we terminate the function.
- Otherwise, we swap \(A[i]\) with \(A[\arg\max(A[left(i)], A[right(i)])]\)
- Next, repeat on node \(A[\arg\max(A[left(i)], A[right(i)])]\) until the function terminates.
1 | def max_heapify(A, i): |
min_heapify
can be implemented in similar but opposite way.
Building a Heap
We can use the procedure max_heapify
in a bottom-up manner to convert an array \(A[1 ... A.heap\_size]\) into a max heap. Recall that the elements in the subarray \(A[\lfloor \frac{n}{2} \rfloor + 1 ... n]\) are all leaves of the trees, and so each is a 1-element heap to begin with. The function build_max_heap(A)
goes through the remaining nodes of the tree and runs max_heapify
on each one.
1 | def build_max_heap(A): |
A min heap can be build in similar way by replace max_heapify
with min_heapify
.
Heap Sort
Recall that, a root of the max heap is the maximum item in the array \(A[1 ... A.heap\_size]\). if we do a build_max_heap
on entire \(A\), swap \(A[1]\) with \(A[n]\) and then do a max_heapify(A[1 ... n - 1], i)
, we end up with a sorted array.
1 | def heap_sort(A): |
Priority Queues
A priority queue
is a data structure for maintaining a set \(S\) of elements, each with an associated value called a \(key\). A max priority queue
is based on max heap
and a min prioprity queue
is based on min heap
.
The max priority queue
(used in scheduler such as job scheduling) supports:
insert(S, x)
: inserts the elements \(x\) into \(S\).maximum(S)
: returns the element of S with the largest key.extract_max(S)
: removes and returns the element of \(S\) with the largest key.increase_key(S, i, k)
: increases the value of index \(i\)'s key to the new value \(k\), which is assumed to be at least as large as \(x\)'s current key value.
The min priority queue
(used in event-driven simulator) supports:
insert(S, x)
: inserts the elements \(x\) into \(S\).minimum(S)
: returns the element of S with the smallest key.extract_min(S)
: removes and returns the element of \(S\) with the smallest key.decrease_key(S, i, k)
: decreases the value with index \(i\)'s key to the new value \(k\), which is assumed to be at most as large as \(x\)'s current key value.
1 | def maximum(A): |
1 | def increase_key(A, i, k): |
1 | def insert(A, x): |
Complexity
max_heapify
: \(O(\log (n))\)build_max_heap
: \(O(n)\)heap_sort
: \(O(n \log (n))\)maximum
: \(O(1)\)extract_max
: \(O(\log n)\)insert
: \(O(\log n)\)increase_key
: \(O(\log n)\)