Vous êtes sur la page 1sur 4

Binary Heaps and Priority Queues When discussing binary search trees we looked at examples like

where the items are placed in the tree so that (S) the value stored at each node of the tree is greater than the values stored in its left subtree, and less than the values stored in its right subtree. You can think of the items as being ordered from left to right. We obtain an interesting alternative structure if the items are ordered from bottom to top. Here is an example:

The defining property is now that (H) the value stored at each node of the tree is greater than or equal to the values stored in its left subtree, and greater than or equal to the values stored in its right subtree. Note that the value at a node is now greater than or equal to the values in both subtrees. (We are now also allowing duplicates, though there are none in this case). A simple local test for whether property (H) holds is that the value stored at every node should be no greater than the value stored at its parent (apart from the root which doesn't have a parent). It follows that every path from the root to a leaf forms a decreasing (non-increasing) sequence. Array Representation A complete binary tree has a simple array representation. Suppose we number the nodes from left to right, beginning at the top and ending at the bottom. Then we can store the various data items in the corresponding elements of an array. For example

can be represented by the array

This in fact corresponds to the level order enumeration of the tree. Note that we only use an initial segment of the array. Provided the array is long enough, and we know the number of tree nodes, it doesn't matter how many unused components there are at the end.

Complete Binary Trees Trees satisfying property (H) are particularly useful if they are well balanced, in the sense of having short path lengths from root to leaves. They don't have to be quite as well balanced as the tree above. All we require is that the path lengths are as short as possible. For example, we could have

or

or even

But we don't want are trees like

in which there are paths from some leaf to the root whose lengths differ by more than 1. We want trees in which every level is fully occupied except, possibly, for the bottom level. It is also very convenient, as you will see, if the bottom level is filled from left to right. (It could alternatively be filled from right to left, but we need to agree on one or the other.) Binary trees of this type are called complete. They are defined as being completely filled, except possibly for the bottom level, which is filled from left to right. A complete binary tree which also satisfies the ordering property (H) is called a binary heap.

Priority Queues So far we have not said anything about how we might use binary heaps. Two important applications are in priority queues and sorting. It is frequently useful to modify the queue concept by attaching a priority to items in the queue. For example, jobs waiting in a printer queue, or processes awaiting execution on a processing unit, may have different priorities. The allocation of tickets for a football match, or to standby passengers awaiting an airline flight, or patients needing medical attention, may also be prioritised. If an item of higher priority joins the queue, it can jump to the front or, at least, progress to some place closer to the front. In general the items in the queue will be ranked. We want the item of highest rank in the queue to be served first.

A simple PriorityQueue interface can be given as in Figure 9.1. public interface PriorityQueue { public boolean isEmpty(); public int size(); public void add(Comparable item); public Comparable remove(); } Figure 9.1: A simple interface for a priority queue. Items in a priority queue need to be comparable with respect to order, i.e. to implement the Comparable interface. The items themselves may be complex, e.g. records with several fields, but they must contain some information which enables them to be ordered, and the ordering must have been defined. If we use a binary heap to implement a priority queue, an item of highest priority will always be at the root of the tree. This makes it very easy to determine the next item to emerge from the queue. But we must then reorganise the heap so that an item of highest priority amongst the remainder moves into the root position. Removal of an item from the heap must leave the heap properly ordered, in other words it must still satisfy the heap property (H). Similarly, when an item is inserted into the heap, it must be placed in the correct position so that (H) remains satisfied. Strict Priority Queues So far we have ignored a subtle issue with priority queues. What should happen if two or more of the items in the queue have the same priority? Which should emerge first? An obvious answer is that it should be the first one to have arrived. But as things stand there is nothing to guarantee that result. There are several ways of dealing with this issue. First, it could be insisted that the compareTo method implemented by the objects in the queue should be consistent with equals. Effectively that would mean that no two distinct items could have the same priority. It would then be the responsibility of the client to tag items with priorities corresponding to the order in which they arrive, and this would form part of the definition of the compareTo method for objects of this type. For example, allocation of priorities to airline standby passengers might be determined by such factors as frequent-flyer status, fare paid, and check-in time. Assuming no two passengers checked in at the same time, no two passengers would ever have the same priority; or if they did check in at the same time, at different check-in desks for example, it should be a matter of indifference, on that basis alone, which had the higher priority. Alternatively, we might wish to provide the tagging automatically for the client. A priority queue would then be a genuine queue. Amongst items of the same priority, the first in would always be the first out. On that approach, the question of the most efficient implementation would depend largely on whether we expect items of the same priority to be the exception or the rule. If items mainly have the same priority, it would be best to implement a priority queue as a collection of ordinary queues. But these queues would have to be structured in such a way that, when adding to the priority queue, there was an efficient way of finding which sub-queue should be extended. Rather than pursue that line, we consider a simple implementation which retains a binary heap as the underlying data-structure, but which supplies an internal tag indicating the time of joining the queue. The order relation for the binary heap will then also take account of this time. We call such a binary heap aStrictBinaryHeap and code for its implementation is given in Figure 9.5. public class StrictBinaryHeap implements PriorityQueue { private BinaryHeap h; private static int ticks = 0; private class Item implements Comparable { Comparable item; int time; Item(Comparable item, int time) { this.item = item; this.time = time;

} public int compareTo(Object other) { int result = item.compareTo(((Item) other).item); if (result == 0) { result = ((Item) other).time - time; } return result; } } public StrictBinaryHeap() { h = new BinaryHeap(); } public boolean isEmpty() { return h.isEmpty(); } public int size() { return h.size(); } public void add(Comparable item) { h.add(new Item(item, ticks++)); } public Comparable remove() { return ((Item) h.remove()).item; } } Figure 9.5: A binary heap implementation of the PriorityQueue interface in which items of the same priority necessarily leave in the order in which they arrived. This is simpler than it looks. Essentially we make use of a simple BinaryHeap called h, but instead of inserting items immediately into h with their simple priority ordering, we tag each by the time of arrival. This is done using a private class Item which contains not just the original item to be inserted, but also the time of arrival. Members of this class are compared for order by using their original priorities, if they are different, and otherwise by their time of arrival. The clock is advanced by one tick each time a new item is added: public void add(Comparable item) { h.add(new Item(item, ticks++)); } The variable ticks is declared to be static so that it would be possible to merge two or more priority queues implemented in this way, using the common clock to compare their orders of arrival. On balance, applications may find it preferable to use a simple BinaryHeap, with responsibility being left to the client to ensure that any preferences for order of emergence from the queue are embodied in the original compareTo method of the items to be queued.