By using a suitable comparator, we can make easily change the notion of "smallest" by "largest". Ultimately the notion of the priority queue is quite general in that a FIFO queue can be seen a priority queue with comparison of elements "by time stamp on entering the queue."
package prioqueuedemo;
import java.util.*;
//import heap.*;
public class Main {
public static void main(String[] args) {
mainBasic(args);
}
public static void mainBasic(String[] args) {
Comparator<Integer> rev_cmp = new Comparator<Integer>() {
@Override
public int compare(Integer lhs, Integer rhs) {
return rhs.compareTo(lhs);
}
};
Queue<Integer> queue = new PriorityQueue<Integer>();
Queue<Integer> queue_rev = new PriorityQueue<Integer>(20, rev_cmp);
//Queue<Integer> queue = new BinaryHeap<Integer>();
//Queue<Integer> queue_rev = new BinaryHeap<Integer>(20, rev_cmp);
Random r = new Random();
for (int i = 0; i < 10; ++i) {
int x = r.nextInt(100);
System.out.println("---- add(" + x + ")");
queue.add(x);
queue_rev.add(x);
}
System.out.println("queue: " + queue);
System.out.println("queue_rev: " + queue_rev);
System.out.println("removing from queue:");
while (!queue.isEmpty()) {
System.out.println("---- remove = " + queue.remove());
}
System.out.println("removing from rev_queue:");
while (!queue_rev.isEmpty()) {
System.out.println("---- remove = " + queue_rev.remove());
}
}
}
Note the output of these statements:
System.out.println("queue: " + queue);
System.out.println("queue_rev: " + queue_rev);
It may not make sense what this output reflects, but it actually reflects
the underlying structure which we'll soon see.
Class Name: QueueAdapter package: queueThen insert the following content ( click to show )
parent = n/2Saying that a complete binary tree satisfies the heap property means that at each node n,
data[n/2] ≤ data[n] (n != 1)or, looking at it the other way:
data[n] ≤ data[2*n] (when data[2*n] exists) data[n] ≤ data[2*n+1] (when data[2*n+1] exists)Here is an example:
hole = ++size;The operation we do is called "percolate up" in which the hole is moved to the correct position in the tree so that we can do the insertion:
data[hole] = eltand know that the heap property is maintained:
data[hole/2] ≤ data[hole]And so, if necessary, the hole moves up the tree from the leaf while the data in the parent is shifted down with an operation like this:
data[hole] = data[hole/2]; hole = hole/2;For example, the steps to do the insertion of the value 17 in this tree would be this:
elt = data[size--]; hole = 1;The hole must move down the tree (percolate down) with an operation like:
hole = 2 * hole; or hole = 2 * hole + 1;
until we can set
data[hole] = eltsatisfied that the heap property is maintained:
elt ≤ data[2*hole] elt ≤ data[2*hole+1]When a hole goes down from parent to child, always choose a child with a smaller value. Programmatically, percolate down is a more complicated operation due to having to choose between two children and determining how many children do deal with. If we do a removal on the tree above (after the insert of 17 is complete) these are the steps;
Class Name: BinaryHeap package: heapThen insert the following content ( click to show )
//import heap.*;and replace the commented and uncommented sections:
Queue<Integer> queue = new PriorityQueue<Integer>(); Queue<Integer> queue_rev = new PriorityQueueThe class code contains non-standard features which illustrate the underlying tree structure. A second test program is better for showing this structure:(20, rev_cmp); //Queue<Integer> queue = new BinaryHeap<Integer>(); //Queue<Integer> queue_rev = new BinaryHeap<Integer>(20, rev_cmp);
public static void mainTestHeap(String[] args) {
Queue<Integer> queue = new BinaryHeap<Integer>();
Random r = new Random();
for (int i = 0; i < 10; ++i) {
int x = r.nextInt(100);
System.out.println("------------ add(" + x + ")");
queue.add(x);
((BinaryHeap)queue).reverseInorderOutput();
}
while (!queue.isEmpty()) {
System.out.println("------------ remove = " + queue.remove());
((BinaryHeap)queue).reverseInorderOutput();
}
}
[13, 11, 12, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6]Putting these directly in order into the tree gives:
public void buildHeap(E[] a) {
// this part sets the size correctly
int old_capacity = capacity;
size = capacity = a.length;
if (capacity > old_capacity) {
data = (E[]) new Object[capacity + 1];
}
for (int i = 0; i < a.length; ++i) {
data[i+1] = a[i];
}
// here is the actual code:
for (int i = size/2; i > 0; --i) {
int hole = i;
E elt = data[hole];
hole = percolateDown(hole, elt);
data[hole] = elt;
}
}
public static void mainTestBuildHeap(String[] args) {
int NUM = 10;
Integer[] A = new Integer[NUM];
Random r = new Random();
for (int i = 0; i < A.length; ++i) {
A[i] = r.nextInt(NUM * 3);
}
System.out.println( Arrays.toString(A) );
BinaryHeap<Integer> heap = new BinaryHeap<Integer>();
heap.buildHeap(A);
heap.reverseInorderOutput();
}
n = 2h+1 1then log(n+1) = h + 1 and and the sum of the heights would be
n log(n+1) < nIf the tree is complete, but not necessarily perfect, we can see, from the first point above that
|
the number of nodes in a perfect tree of height h+1 |
< 2 * |
the number of nodes in a perfect tree of height h |
| the sum of the heights in a complete tree with n nodes |
≤ | the sum of the heights of the next perfect tree (with fewer than 2*n nodes) |
≤ | 2 * n |
public static void mainHeapSort(String[] args) {
int NUM = 20;
Comparator<Integer> rev_cmp = new Comparator<Integer>() {
@Override
public int compare(Integer lhs, Integer rhs) {
return rhs.compareTo(lhs);
}
};
BinaryHeap<Integer> maxheap = new BinaryHeap<Integer>(NUM, rev_cmp);
Integer[] A = new Integer[NUM];
Random r = new Random();
for (int i = 0; i < A.length; ++i) {
A[i] = r.nextInt(NUM * 3);
}
System.out.println( Arrays.toString(A) );
maxheap.buildHeap(A);
System.out.println( "---------------------------" );
maxheap.reverseInorderOutput();
System.out.println( "---------------------------" );
int i = A.length;
while (! maxheap.isEmpty()) {
A[--i] = maxheap.remove();
}
System.out.println( Arrays.toString(A) );
}
The actual heapsort algorithm uses the array itself as the Binary Heap
and thus does not need the heap separate from the array as is the case in our version.
The difficulty in writing the actual heapsort algorithm is translating the
array index range "fromIndex to toIndex" into
the range "1 to size+1" which is natural for a complete binary tree.
V put(K key, V value); V get(K key); Map.Entry<K,V> remove();It combines the get/put of Map operations, with a revised remove function as might occur in a PriorityQueue in which the "least" element is removed. The put function will either add a new (key,value) pair if the key does not exist, or will modify an existing key's value.
private Map<K,Integer> indexOf = new HashMap<K,Integer>();This data member maps keys to array positions, i.e., indexOf.get(key) is always the index of the array element containing the pair with that key. This must be continually reset whenever a pair move up or down in the heap. The important added private member function is:
private V setValue(K key, V value);which changes the value of an existing key, thereby causing the pair to either:
Class Name: HeapMap package: heapThen insert the following content ( click to show ) Here is a sample program which uses type Double as the value, a common usage:
public static void mainTestHeapMap(String[] args) {
HeapMap<String, Double> heap = new HeapMap<String, Double>();
String[] keys = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"};
int x = 10;
for (String key : keys) {
heap.put(key, (double) x);
x += 10;
}
heap.reverseInorderOutput();
System.out.println( "\n---------------- heap.put(\"I\",2.0)" );
heap.put("I", 2.0);
heap.reverseInorderOutput();
System.out.println( "\n---------------- heap.put(\"C\",5.0)" );
heap.put("C", 5.0);
heap.reverseInorderOutput();
System.out.println( "\n---------------- heap.put(\"I\",300.0)" );
heap.put("I", 300D);
heap.reverseInorderOutput();
System.out.println( "\n---------------- heap.put(\"D\",-Infinity)" );
heap.put("D", Double.NEGATIVE_INFINITY);
heap.reverseInorderOutput();
System.out.println( "\n---------------- heap.remove()" );
heap.remove();
heap.reverseInorderOutput();
}
}
As suggested by the last example in the test program,
if all heap elements have finite values
we can effect removal of a key from a HeapMap object
(implemented as a min heap), using this code:
V remove(HeapMap heapmap, K key) {
V val = heapmap.get(key);
heapmap.put(key, Double.NEGATIVE_INFINITY);
heapmap.remove();
return val;
}