420 likes | 431 Views
Learn about partially ordered tree and its implementation as a heap data structure. Understand the complexities of insertion, finding the minimum, and removing the root. Explore the concepts of min and max heaps.
E N D
CS 2123 Data Structures Ch 16 – Heaps Static Array Implementation of Partially Ordered Binary Trees Turgay Korkmaz Office: NPB 3.330 Phone: (210) 458-7346 Fax: (210) 458-4437 e-mail: korkmaz@cs.utsa.edu web: www.cs.utsa.edu/~korkmaz Thanks to Eric S. Roberts, the author of our textbook, for providing some slides/figures/programs. I also used some materials kindly provided by Drs. Maynard and Quarles. Thanks to both.
Objectives • Understand heap data structures. • Implement a min (or max) heap. • Good programming practice: debugging complex code
Motivation • Recall Dijkstra’s algorithm • Its efficiency depends on how well the underlying priority queue package is implemented, right? • If we implement priority queues using link lists or array from Chapter 10, the PriorityEnqueue function will require O(N) time. • We can improve this to O(log N) by using a data structure called partially ordered tree! • So the overall complexity of Dijkstra’s algorithm will go from O(N2) to O(N log N + M)
An efficient implementation of Priority Queues • Partially ordered tree is a complete binary tree in which the following two properties hold: • The nodes of the tree are arranged in a pattern as close to that of a completelysymmetrical tree as possible. • Thus, the number of nodes along any path in the tree can never differ by more than one. • Moreover, the bottom level must be filled in a strictly left-to-right order. • Each node contains a key that is always less than or equal to the key in its children. Thus, the smallest key in the tree is always at the root Is this a BST? Why/Why not? Let’s add a node with the key 2193.
Partially ordered tree - Insertion • Because of the requirement that the lowest level of the tree be filled from left-to-right, the tree with the new node will be like… • But this violates the second property: a key in a node should be less than or equal to the keys in its children • To fix the problem, we can exchange the keys in those nodes, and get… • What will be the complexity?
Partially ordered tree – Insertion complexity • A newly inserted key may need to be exchanged with its parent in a cascading sequence of changes until the root! • Because of the structure of the tree, the total number of such exchanges will never be more than O(log N). • So the complexity of insertion is O(log N).
Partially ordered tree – Finding minimum • The smallest value in the tree is always at the root. • So the complexity of finding min is O(1). • But if we need to remove the minimum (the root), then we need to do some work to arrange the nodes in the tree!
Partially ordered tree – Removing the root (minimum key) • The standard approach is to • replace the key in the root with the key in the rightmost node from the lowest level • delete that rightmost node, • then swap keys down the tree until the ordering property is restored. • For example, let’s delete the root node from our tree example
Partially ordered tree – Removing the root (minimum key) • Replace the key in the root node with the one in the rightmost node and delete that rightmost node. • But this violates the second property: a key in a node should be less than or equal to the keys in its children • To fix the problem, we can exchange the keys and get… • What will be the complexity? Like insertion, deleting the root requires O(log N) time.
Partially ordered tree –IMPLEMENTATION • So far we just talked about its behavior • For implementation, one could use pointer-based structures as in BSTs. • But a better alternative here is to use an array-based structure called heap • Don’t be confused with the terminology! • this heap data structure has no relationship to the pool of unused memory available for dynamic allocation (which is also called heap).
HeapArray-based implementation of partially ordered tree (or complete binary tree) • The nodes in a partially ordered tree of size N can be stored in the first N elements of an array • Parent and child nodes always appear at an easily computed position. How? parent(r) = (r−1) / 2 leftchild(r) = 2 * r + 1 rightchild(r) = 2 * r + 2
Heap parent(r) = (r−1) / 2 if 0 < r && r < N leftchild(r) = 2 * r + 1 if 2*r + 1 < N rightchild(r) = 2 * r + 2 if 2*r + 2 < N leftsibling(r) = r − 1 if r is even && 0 < r < N rightsibling(r) = r + 1 if r is odd && 0 < r < N − 1 Else -1
5 12 Min vs. Max Heap 9 11 7 8 12 5 11 9 8 7 • The values in the heap are partially ordered and hence we can think of a heap as either • A min heap which has the property that the value of every node is less than or equal to the values of its children (as we considered in previous examples) • A max heap which has the property that the value of every node is greater than or equal to the values of its children • Since max heaps and min heaps are symmetrical structures, we can assume, without loss of generality, that the heap which we are considering is a max heap
Making a MAX heap by iteratively inserting each 1 7 7 7 10 7 1 3 5 3 10 3 7 3 5 1 10 1 5 1 5 O(N logN) ???
Making a MAX heap out of a given set of elements building a heap We will visualize the structure in terms of a tree structure as well as in the array. But this is not currently ordered and therefore is not a heap, yet!
Sifting down (also called heapifying) • In order to change a given array into a heap, we will start with the leaves and basically build upwards from each leaf making certain that we have a heap below each node. • If we assume that the two children of a node are heaps, then we can swap the node value with the largest son. • That new subtree may no longer be a heap since the new root might be smaller than one of its sons. • So we repeat the process until the value has moved into a position where it is larger than its two sons.
A simple example of sifting down We have 7 at the top node and the children are heaps with max values of 10 and 11. We would first swap 7 and 11. At this point, 7 is not larger than its children so we swap 7 with its largest child which is 9. At this point this entire subtree is a heap.
In the following figures the dashed portion of a tree will either represent a portion of the tree not yet under consideration or a node and children which do not satisfy the condition that the node value is larger than the children values and hence have to be corrected before we move up a level. Now return to our full tree example start at the first node which has children, that is ⌊n/2⌋ −1 Index r = 12/2-1=5 Start at level 3 with the leaves. The leaves are already heaps since they have no children and hence at level 3 and below we have all heaps. parent(r) = (r−1) / 2 leftchild(r) = 2 * r + 1 rightchild(r) = 2 * r + 2
Now consider the level two nodes start at the first node which has children, that is ⌊n/2⌋ −1 Index r =12/2-1=5 r=5, the 8 node is larger than its children. r=4, the 10 node is larger than its children. r=3, the 5 node is less than its 11 child so we swap the 5 and 11 nodes.
Now consider the level one nodes Swaps in previous slide yield the following partial heap 8 11 4 3 7 3 r=2, we swap the 3 node with the 8 node. But …..the 3 node is not larger than its children, so we continue swapping it with 4 node. r=1, the 7 node is swapped with the 11 node and after this swap the 7 sub-tree is a heap.
Now consider the level zero node Swaps in previous slide yields the following partial heap r=0, swap the 1 node with the 11 node.
Swaps in previous slide yields the following partial heap … but The 1 node is still smaller so we next swap it with its largest child, the 10 node.
Swaps in previous slide yields the following partial heap … but The 1 node is not yet the head of a heap so we swap it with the 9 node
Finally we get the heap… O(N logN) ??? or O(N) magic!
The size of the array will be reduced by one, so what we will do is simply switch the maximum element with the last element in the array and decrement the array size by 1, so the maximum element, though still in the array is not consider to be part of the heap. Remove the MAX element
Now let the new root percolate down Now let the new root percolate down by swapping with its maximum child until it is bigger than its children.
CODING parent(r) = (r−1) / 2 if 0 < r && r < N leftchild(r) = 2 * r + 1 if 2*r + 1 < N rightchild(r) = 2 * r + 2 if 2*r + 2 < N leftsibling(r) = r − 1 if r is even && 0 < r < N rightsibling(r) = r + 1 if r is odd && 0 < r < N − 1 Else -1
parent(r) = (r−1) / 2 if 0 < r && r < n leftchild(r) = 2 * r + 1 if 2*r + 1 < n rightchild(r) = 2 * r + 2 if 2*r + 2 < n The heap.h file. • Let r be the index of the node and n be the number of elements in the heap. • Implement parent, leftchild, rightchild as function… vs. as macros… #define PARENT(r,n) ( 0 < (r) && (r) < (n) ? ((r)-1)/2 : -1 ) #define LEFT(r,n) ( 2*(r)+1 < (n) ? 2*(r)+1 : -1 ) #define RIGHT(r,n) ( 2*(r)+2 < (n) ? 2*(r)+2 : -1 ) void print_heap(int heap[], int n); int valid_heap(int heap[], int n); void build_heap(int heap[], int n); void siftdown(int heap[], int r, int n);
print_heap(int heap[], int n) • As always we need functions to help us debug our functions… • Implement print_heap() void print_heap(int heap[], int n) { inti; for ( i=0; i<n; i++ ) { fprintf(stderr,"\t%d\t%d\n",i,heap[i]); } }
int valid_heap(int heap[], int n) • For larger examples we want a valid_heap() function. int valid_heap(int heap[], int n) { int r, left, right; for ( r = 0; r <= n/2-1; r++ ){ left = LEFT(r,n); right = RIGHT(r,n); if ( left != -1 && heap[r] < heap[left] ) return(0); if ( right != -1 && heap[r] < heap[right] ) return(0); } return(1); }
voidsiftdown(int heap[], int r, int n) • Implement a very basic operation called, the siftdown or heapify • Given a node whose children are heaps, it sifts down the tree trading the node at each level with its biggest child until it is larger than its children. • The only tricky part here is remembering to recognize when there is no child, the LEFT/RIGHT macros return -1.
4 4 4 4 if no change r 2 2 3 3 4 4 if heap[r] heap[left] 6 5 4 4 5 6 heap[r] heap[right] if 7 6
4 void SWAP(int heap[ ], int x, int y) { int tmp; tmp = heap[x]; heap[x]=heap[y]; heap[y]=tmp; } 4 4 4 voidsiftdown(int heap[ ], int r, int n); 2 2 3 3 voidsiftdown(int heap[], int r, int n) { int left, right; while(r >= 0){ left = LEFT(r,n); right = RIGHT(r,n); if ( (left!=-1) && (right==-1 || heap[left] > heap[right]) && (heap[r] < heap[left]) ){ SWAP(heap, r, left); r = left; } else if ( (right!=-1) && (left==-1 || heap[right] > heap[left]) && (heap[r] < heap[right]) ){ SWAP(heap, r, right); r = right; } else r=-1; } // end of while } 4 4 6 5 4 4 5 6 7 6
Now building a heap is simple • You simply start at the first node which has children, and it is easy to see that this is ⌊n/2⌋ −1 • So we start there and siftdown the node value and then move to the next one. • We end up with a heap and it can be shown that this is a linear operation, O(n) operations. void build_heap(int heap[], int n) { int r; for ( r = n/2-1; r >= 0; r-- ) { siftdown(heap,r,n); } }
Driver Program #include <stdio.h> #include <limits.h> #include "heap.h" int main(int argc, char *argv[]) { int heap[100000]; int i,n; i = 0; while ( scanf("%d",&heap[i]) == 1 ) { i++; } n = i; build_heap(heap, n); if ( valid_heap(heap, n) ) printf("Valid heap\n"); else printf("Invalid heap\n"); return(0); }
Add a new element • While we can efficiently build a heap from an array of data items, we will frequently encounter situations where we need to add additional elements to the heap. • The answer is simple enough: • add the new value to the end of the heap and • sift it up into a correct position so we need two new functions.
9 Exercise 6 3 4 Exercise: Write • void siftup(int heap[], int r, int n); It takes a heap (heap, n) and a value index. Then if the parent value is less than the new value, it swaps them. This continues until there is no parent or the new value is less than the parent value. • insert_heap(int heap[], int *n, int value); It will simply insert the new value after the end of the heap, increment the size of the heap (n), and then call siftup the new value: 7
void SWAP(int heap[], int x, int y) { int tmp; tmp = heap[x]; heap[x]=heap[y]; heap[y]=tmp; } Exercise voidsiftup(int heap[], int r, int n) { int parent; parent = PARENT(r,n); while(parent >= 0){ if (heap[parent] < heap[r]) ){ SWAP(heap, r, parent); r = parent; parent = PARENT(r,n); } else break; } // end of while } void insert_heap(int heap[], int *n, int value ) { heap[(*n)++] = value; siftup(heap,(*n)-1,*n); } 9 6 3 4 7
Exercise • Exercise: Write the int rm_max_heap(int heap[],int *n); It removes and returns the maximum element, maintaining max heap properties. intrm_max_heap(int heap[], int *n){ // if (*n<=0) exit(); int value = heap[0]; heap[0] = heap[ *n - 1 ]; (*n)-- ; siftdown(heap,0,*n); return value; }
24 17 18 14 12 8 16 9 10 3 Exercise n = 10 • Remove a given value (e.g., 18) from the heap and show how the values change on the tree structure to keep it as a heap. void remove_given_val(int heap[], int *n, int val) { int tmp, r; if (*n <=0 ) return; /* first search for the given value in heap array */ for(r=0; r < *n; r++ ) if (heap[r]== val) break; if(r < *n) { // found heap[r] = heap[*n-1]; (*n)--; siftdown(heap, r, *n); } } 3, 16 3, 10 3 n = 9