






Study with the several resources on Docsity
Earn points by helping other students or get them with a premium plan
Prepare for your exams
Study with the several resources on Docsity
Earn points to download
Earn points by helping other students or get them with a premium plan
Material Type: Paper; Class: DATA STRUCTURES; Subject: Computer Science; University: Oregon State University; Term: Fall 2006;
Typology: Papers
1 / 12
This page cannot be seen from the preview
Don't miss anything!







In this chapter we examine yet another variation on the simple Bag data structure. A priority queue maintains values in order of importance. A metaphor for a priority queue is a to-do list of tasks waiting to be performed, or a list of patients waiting for an operating room in a hospital. The key feature is that you want to be able to quickly find the most important item, the value with highest priority. Like a bag, you can add new elements into the priority queue. However, the only element that can be accessed or removed is the one value with highest priority. In this sense the container is like the stack or queue, where it was only the element at the top or the front of the collection that could be removed.
The traditional definition of the Priority Queue abstraction includes the following operations: Add (newelement) Add a new value to queue First () Return first element in queue removeFirst () Remove first element in queue isEmpty() Return true if collection is empty Note that a priority queue is not, in the technical sense, a true queue. To be a queue, elements would need to satisfy the FIFO property. This is clearly not the case for the priority queue. However, the name is now firmly attached to this abstraction, so it is unlikely to change.
To be written. Simulations are an important application.
We will explore three different queue implementation techniques. The first is to examine how any sort of ordered bag (such as the SortedBag, SkipList, or AVLtree) can be used to implement the priority queue. The second approach introduces a new type of binary tree, termed the heap. The classic heap (known simply as the heap) provides a very memory efficient representation for a priority queue. Our third technique, the skew heap, uses an interesting variation on the heap technique.
A note regarding the name heap. The term heap is used for two very different concepts in computer science. The heap data structure is an abstract data type used to implement priority queues. The terms heap , heap memory. heap allocation , and so on are used to describe memory that is allocated directly by the user, using the malloc function. You should not confuse the two uses of the same term.
In an ordered collection the smallest element is always the first value. While the bag does not support direct access to the first element (as does, say, the queue), we can nevertheless obtain access to this value by means of an iterator. This makes it very easy to implement a Heap using an ordered bag as a storage mechanism for the underlying data values, as the following example illustrates: struct AVLtree * data = newAVLtree() ; /* already written */ void heapAdd (EleType newElement, struct AVLtree data) { avlTreeAdd(data, newElement); } EleType heapFirst (struct AVLtree * data) { AVLtreeIterator itr = data.iterator(); if (AVLtreeIteratorHasNext(itr)) { return AVLtreeIteratorNext(itr); } else assert(0); / trying to return value from empty queue */ } int heapIsEmpty (struct AVLtree * data) { return AVLtreeSize(data) == 0; } void removeFirst (struct AVLtree * data) { EleType f = heapFirst(data); AVLtreeRemove(f); } We have seen three ordered collections, the SortedArray, the SkipList, and the AVLtree. Based on your knowledge of the algorithmic execution speeds for operations in those data structures, fill in the following table with the execution times for the BagHeap constructed in a fashion similar to the able. Operation SortedArray SkipList AVLtree Add(newElement) First() removeFirst() Notice that in the above scheme we have exposed the underlying AVL tree by forcing the user to explicitly allocate and create the tree in order to use the heap. This minimizes the
Notice that a heap is partially ordered, but not completely. In particular, the smallest element is always at the root. Although we will continue to think of the heap as a tree, we will make use of the fact that a complete binary tree can be very efficiently represented as an array. To root of the tree will be stored as the first element in the array. The children of node i are found at positions 2i+1 and 2i+2, the parent at (i-1)/2. You should examine the tree above, and verify that the transformation given will always lead you to the children of any node. To reverse the process, to move from a node back to the parent, simply subtract 1 and divide by 2. You should also verify that this process works as you would expect. We will construct our heap by defining functions that will use an underlying dynamic array as the data container. This means that users will first need to create a new dynamic array before they can use our heap functions: struct dyArray * heap = newDyArray(10); /* create a new array */ … To insert a new value into a heap the value is first added to the end. (This operation has actually already been written in the function dyArrayAdd). Adding an element to the end preserves the complete binary tree property, but not the heap ordering. To fix the ordering, the new value is percolated up into position. It is compared to its parent node. If smaller, the node and the parent are exchanged. This continues until the root is reached, or the new value finds its correct position. Because this process follows a path in a complete binary tree, it is O(log n). The following illustrates adding the value 4 into a heap, then percolating it up until it reaches its final position. When the value 4 is compared to the 2, the parent node containing the 2 is smaller, and the percolation process halts. Because the process of percolating up traverses a complete binary tree from leaf to root, it is O(log n), where n represents the number of nodes in the tree. Percolating up takes care of insertion into the heap. What about the other operations? The smallest value is always found at the root. This makes accessing the smallest element easy. But what about the removal operation? When the root node is removed it leaves a “hole.” Filling this hole with the last element in the heap restores the complete binary tree
property, but not the heap order property. To restore the heap order the new value must percolate down into position. To percolate down a node is compared to its children. If there are no children, the process halts. Otherwise, the value of the node is compared to the value of the smallest child. If the node is larger, it is swapped with the smallest child, and the process continues with the child. Again, the process is traversing a path from root to leaf in a complete binary tree. It is therefore O(log n). Using the existing functions to manipulate a dynamic array, the process of removing the first element can be described as follows: void removeFirst(struct dyArray heap) { int last = dyArraySize(heap); assert(last != 0); / make sure there is at least one value / / Copy the last element to the first position/ dyArraySet(heap->data, 0, dyArrayrGet(heap, last-1)); dyArrayRemoveAt(heap, last-1); / Remove last element./ adjustHeap(heap, dyArraySize(heap), 0); / Rebuild heap property./ } AdjustHeap can easily be written as a recursive routine: void adjustHeap (struct dyArray hdata, int max, int pos) int leftChild = 2pos + 1; int rightChild = 2 * pos + 2; if (rightChild < max) { / we have two children / get index of smallest child if value at pos < value of smallest child swap with smallest child, call adjustHeap (max, index of smallest child) else if (leftchild < max) { / we have one child / if value at pos < value of child swap with smallest child, call adjustHeap (max, index of child) / else no children, done */
void heapAdd (struct dyArray heap, EleType newValue) { dyArrayAdd(heap, newValue); / add to end of array / / then percolate down into position */ } }
When a value is removed from a heap the size of the heap is reduced. If the heap is being represented in an array, this means that the bottom-most element of the array is no longer being used. (Using the terminology of the dynamic array we examined earlier, the size of the collection is smaller, but the capacity remains unchanged). pictures What if we were to store the removed value in the now unused section of the array? In fact, since the last element in the array is always moved into the hole made by the removal of the root, it is a trivial matter to simply swap this value with the root.
picture Suppose we repeat this process, always swapping the root with the currently last element, then reheaping by percolating the new root down into position. The result would be a sorting algorithm, termed heap sort. The following is a snapshot illustrating heap sort in the middle of execution. Notice that the smallest elements have been moved to the right. The current size of the dynamic array is indicated by the sharp drop in values. The elements to the left of this point are organized in a heap. Notice that the heap is not completely ordered, but has a tendency towards being ordered. To determine the algorithmic execution time for this algorithm, recall that adjustHeap requires O(log n) steps. There are n executions of adjustHeap to produce the initial heap. Afterwards, there are n further executions to reheap values during the process of sorting. Altogether the running time is O(n log n). This matches that of merge sort, quick sort, and tree sort. Better yet, heap sort requires no additional storage. Simulate execution of the Heap sort algorithm on the following values: 9 3 2 4 5 7 8 6 1 0 First make the values into a heap (the graphical representation is probably easier to work with than the vector form). Then repeatedly remove the smallest value, and rebuild the heap.
1 (^1) This section can be safely omitted at the discretion of the instructor.
The merge algorithm for a skew heap can be described as follows: Node merge (Node left, Node right) if (left is null) return right if (right is null) return left if (left child value < right child value) { Node temp = left.left; left.left = merge(left.right, right) left.right = temp return left; } else { Node temp = right.right right.right = merge(right.left, left) right.left = temp return right } Complete the implementation of the SkewHeap based on these ideas: class SkewHeap
public void removeFirst () { root = merge(root.leftChid, root.rightChild); } private Node