Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 53 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
53
Dung lượng
614,67 KB
Nội dung
- 425 - for(int m=0; m<currentSize; m++) if(heapArray[m] != null) System.out.print( heapArray[m].iData + " "); else System.out.print( " "); System.out.println(); // heap format int nBlanks = 32; int itemsPerRow = 1; int column = 0; int j = 0; // current item String dots = " "; System.out.println(dots+dots); // dotted top line while(currentSize > 0) // for each heap item { if(column == 0) // first item in row? for(int k=0; k<nBlanks; k++) // preceding blanks System.out.print(' '); // display item System.out.print(heapArray[j].iData); if(++j == currentSize) // done? break; if(++column==itemsPerRow) // end of row? { nBlanks /= 2; // half the blanks itemsPerRow *= 2; // twice the items column = 0; // start over on System.out.println(); // new row } else // next item on row for(int k=0; k<nBlanks*2-2; k++) System.out.print(' '); // interim blanks } // end for System.out.println("\n"+dots+dots); // dotted bottom line } // end displayHeap() // - } // end class Heap //////////////////////////////////////////////////////////////// class HeapApp { public static void main(String[] args) throws IOException { int value, value2; Heap theHeap = new Heap(31); // make a Heap; max size 31 boolean success; - 426 - theHeap.insert(70); // insert 10 items theHeap.insert(40); theHeap.insert(50); theHeap.insert(20); theHeap.insert(60); theHeap.insert(100); theHeap.insert(80); theHeap.insert(30); theHeap.insert(10); theHeap.insert(90); while(true) // until [Ctrl]-[C] { putText("Enter first letter of "); putText("show, insert, remove, change: "); int choice = getChar(); switch(choice) { case 's': // show theHeap.displayHeap(); break; case 'i': // insert putText("Enter value to insert: "); value = getInt(); success = theHeap.insert(value); if( !success ) putText("Can't insert; heap is full" + '\n'); break; case 'r': // remove if( !theHeap.isEmpty() ) theHeap.remove(); else putText("Can't remove; heap is empty" + '\n'); break; case 'c': // change putText("Enter index of item: "); value = getInt(); putText("Enter new priority: "); value2 = getInt(); success = theHeap.change(value, value2); if( !success ) putText("Can't change; invalid index" + '\n'); break; default: putText("Invalid entry\n"); } // end switch } // end while } // end main() // - - 427 - public static void putText(String s) { System.out.print(s); System.out.flush(); } // public static String getString() throws IOException { InputStreamReader isr = new InputStreamReader(System.in); BufferedReader br = new BufferedReader(isr); String s = br.readLine(); return s; } // public static char getChar() throws IOException { String s = getString(); return s.charAt(0); } // public static int getInt() throws IOException { String s = getString(); return Integer.parseInt(s); } // } // end class HeapApp The array places the heap's root at index 0. Some heap implementations start the array with the root at 1, using position 0 as a sentinel value with the largest possible key. This saves an instruction in some of the algorithms, but complicates things conceptually. The main() routine in HeapApp creates a heap with a maximum size of 31 (dictated by the limitations of the display routine) and inserts into it 10 nodes with random keys. Then it enters a loop in which the user can enter s, i, r, or c, for show, insert, remove, or change. Here's some sample interaction with the program: Enter first letter of show, insert, remove, change: s heapArray: 100 90 80 30 60 50 70 20 10 40 100 90 80 - 428 - 30 60 50 70 20 10 40 Enter first letter of show, insert, remove, change: i Enter value to insert: 53 Enter first letter of show, insert, remove, change: s heapArray: 100 90 80 30 60 50 70 20 10 40 53 100 90 80 30 60 50 70 20 10 40 53 Enter first letter of show, insert, remove, change: r Enter first letter of show, insert, remove, change: s heapArray: 90 60 80 30 53 50 70 20 10 40 90 60 80 30 53 50 70 20 10 40 Enter first letter of show, insert, remove, change: The user displays the heap, adds an item with a key of 53, shows the heap again, removes the item with the greatest key, and shows the heap a third time. The show() routine displays both the array and the tree versions of the heap. You'll need to use your imagination to fill in the connections between nodes. Expanding the Heap Array - 429 - What happens if, while a program is running, too many items are inserted for the size of the heap array? A new array can be created, and the data from the old array copied into it. (Unlike the situation with hash tables, changing the size of a heap doesn't require reordering the data.) The copying operation takes linear time, but enlarging the array size shouldn't be necessary very often, especially if the array size is increased substantially each time it's expanded (by doubling it, for example). In Java, a Vector class object could be used instead of an array; vectors can be expanded dynamically. Efficiency of Heap Operations For a heap with a substantial number of items, it's the trickle-up and trickle-down algorithms that are the most time-consuming parts of the operations we've seen. These algorithms spend time in a loop, repeatedly moving nodes up or down along a path. The number of copies necessary is bounded by the height of the heap; if there are five levels, four copies will carry the "hole" from the top to the bottom. (We'll ignore the two moves used to transfer the end node to and from temporary storage; they're always necessary so they require constant time.) The trickleUp() method has only one major operation in its loop: comparing the key of the new node with the node at the current location. The trickleDown() method needs two comparisons, one to find the largest child, and a second to compare this child with the "last" node. They must both copy a node from top to bottom or bottom to top to complete the operation. A heap is a special kind of binary tree, and as we saw in Chapter 8, the number of levels L in a binary tree equals log 2(N+1), where N is the number of nodes. The trickleUp() and trickleDown() routines cycle through their loops L–1 times, so the first takes time proportional to log 2N, and the second somewhat more because of the extra comparison. Thus the heap operations we've talked about here all operate in O(logN) time. Heapsort The efficiency of the heap data structure lends itself to a surprisingly simple and very efficient sorting algorithm called heapsort. The basic idea is to insert the unordered items into a heap using the normal insert() routine. Repeated application of the remove() routine will then remove the items in sorted order. Here's how that might look: for(j=0; j<size; j++) theHeap.insert( anArray[j] ); // from unsorted array for(j=0; j<size; j++) anArray[j] = theHeap.remove(); // to sorted array Because insert() and remove() operate in O(logN) time, and each must be applied N times, the entire sort requires O(N*logN) time, which is the same as quicksort. However, it's not quite as fast as quicksort. Partly this is because there are more operations in the inner while loop in trickleDown() than in the inner loop in quicksort. However, several tricks can make heapsort more efficient. The first saves time, and the second saves memory. Trickling Down in Place - 430 - If we insert N new items into a heap, we apply the trickleUp() method N times. However, if all the items are already in an array, they can be rearranged into a heap with only N/2 applications of trickleDown(). This offers a small speed advantage. Two Correct Subheaps Make a Correct Heap To see how this works, you should know that trickleDown() will create a correct heap if, when an out-of-order item is placed at the root, both the child subheaps of this root are correct heaps. (The root can itself be the root of a subheap as well as of the entire heap.) This is shown in Figure 12.8. Figure 12.8: Both subtrees must be correct This suggests a way to transform an unordered array into a heap. We can apply trickleDown() to the nodes on the bottom of the (potential) heap—that is, at the end of the array—and work our way upward to the root at index 0. At each step the subheaps below us will already be correct heaps because we already applied trickleDown() to them. After we apply trickleDown() to the root, the unordered array will have been transformed into a heap. Notice however that the nodes on the bottom row—those with no children—are already correct heaps, because they are trees with only one node; they have no relationships to be out of order. Therefore we don't need to apply trickleDown() to these nodes. We can start at node N/2–1, the rightmost node with children, instead of N–1, the last node. Thus we need only half as many trickle operations as we would using insert() N times. Figure 12.9 shows the order in which the trickle-down algorithm is applied, starting at node 6 in a 15-node heap. Figure 12.9: Order of applying trickleDown() The following code fragment applies trickleDown() to all nodes, except those on the bottom row, starting at N/2–1 and working back to the root: for(j=size/2-1; j >=0; j ) - 431 - theHeap.trickleDown(j); A Recursive Approach A recursive approach can also be used to form a heap from an array. A heapify() method is applied to the root. It calls itself for the root's two children, then for each of these children's two children, and so on. Eventually it works its way down to the bottom row, where it returns immediately whenever it finds a node with no children. Once it has called itself for two child subtrees, heapify() then applies trickleDo n() to the root of the subtree. This ensures that the subtree is a correct heap. Then heapify() returns and works on the subtree one level higher. heapify(int index) // transform array into heap { if(index > N/2-1) // if node has no children, return; // return heapify(index*2+2); // turn right subtree into heap heapify(index*2+1); // turn left subtree into heap trickleDown(index); // apply trickle-down to this node } This recursive approach is probably not quite as efficient as the simple loop. Using the Same Array Our initial code fragment showed unordered data in an array. This data was then inserted into a heap, and finally removed from the heap and written back to the array in sorted order. In this procedure two size-N arrays are required: the initial array and the array used by the heap. In fact, the same array can be used both for the heap and for the initial array. This cuts in half the amount of memory needed for heapsort; no memory beyond the initial array is necessary. We've already seen how trickleDown() can be applied to half the elements of an array to transform them into a heap. We transform the unordered array data into a heap in place; only one array is necessary for this. Thus the first step in heapsort requires only one array. However, things are more complicated when we apply remove() repeatedly to the heap. Where are we going to put the items that are removed? Each time an item is removed from the heap, an element at the end of the heap array becomes empty; the heap shrinks by one. We can put the recently removed item in this newly freed cell. As more items are removed, the heap array becomes smaller and smaller, while the array of ordered data becomes larger and larger. Thus with a little planning it's possible for the ordered array and the heap array to share the same space. This is shown in Figure 12.10. - 432 - FIGURE 12.10: Dual-purpose array The heapSort.java Program We can put these two tricks—applying trickleDown() without using insert(), and using the same array for the initial data and the heap—together in a program that performs heapsort. Listing 12.2 shows how this looks. Listing 12.2 The heapSort.java Program // heapSort.java // demonstrates heap sort // to run this program: C>java HeapSortApp import java.io.*; // for I/O import java.lang.Integer; // for parseInt() //////////////////////////////////////////////////////////////// class Node { public int iData; // data item (key) public Node(int key) // constructor { iData = key; } } // end class Node //////////////////////////////////////////////////////////////// class Heap { private Node[] heapArray; private int maxSize; // size of array private int currentSize; // number of items in array // - public Heap(int mx) // constructor - 433 - { maxSize = mx; currentSize = 0; heapArray = new Node[maxSize]; } // - public Node remove() // delete item with max key { // (assumes non-empty list) Node root = heapArray[0]; heapArray[0] = heapArray[ currentSize]; trickleDown(0); return root; } // end remove() // - public void trickleDown(int index) { int largerChild; Node top = heapArray[index]; // save root while(index < currentSize/2) // not on bottom row { int leftChild = 2*index+1; int rightChild = leftChild+1; // find larger child if(rightChild < currentSize && // right ch exists? heapArray[leftChild].iData < heapArray[rightChild].iData) largerChild = rightChild; else largerChild = leftChild; // top >= largerChild? if(top.iData >= heapArray[largerChild].iData) break; // shift child up heapArray[index] = heapArray[largerChild]; index = largerChild; // go down } // end while heapArray[index] = top; // root to index } // end trickleDown() // - public void displayHeap() { int nBlanks = 32; int itemsPerRow = 1; int column = 0; int j = 0; // current item String dots = " "; System.out.println(dots+dots); // dotted top line - 434 - while(currentSize > 0) // for each heap item { if(column == 0) // first item in row? for(int k=0; k<nBlanks; k++) // preceding blanks System.out.print(' '); // display item System.out.print(heapArray[j].iData); if(++j == currentSize) // done? break; if(++column==itemsPerRow) // end of row? { nBlanks /= 2; // half the blanks itemsPerRow *= 2; // twice the items column = 0; // start over on System.out.println(); // new row } else // next item on row for(int k=0; k<nBlanks*2-2; k++) System.out.print(' '); // interim blanks } // end for System.out.println("\n"+dots+dots); // dotted bottom line } // end displayHeap() // - public void displayArray() { for(int j=0; j<maxSize; j++) System.out.print(heapArray[j].iData + " "); System.out.println(""); } // - public void insertAt(int index, Node newNode) { heapArray[index] = newNode; } // - public void incrementSize() { currentSize++; } // - } // end class Heap //////////////////////////////////////////////////////////////// class HeapSortApp { public static void main(String[] args) throws IOException [...]... them In the next chapter we'll look at the more complicated algorithms associated with weighted graphs Introduction to Graphs Graphs are data structures rather like trees In fact, in a mathematical sense, a tree is a kind of graph In computer programming, however, graphs are used in different ways than trees The data structures examined previously in this book have an architecture dictated by the algorithms. .. display it insert it // reset flags // end bfs() Given the same graph as in dfs .java (shown earlier in Figure 13.8), the output from bfs .java is now Visits: ABDCE The bfs .java Program The bfs .java program, shown in Listing 13.2, is similar to dfs .java except for the inclusion of a Queue class (modified from the version in Chapter 5, "Linked Lists") instead of a StackX class, and a bfs() method instead... // -public static String getString() throws IOException { InputStreamReader isr = new InputStreamReader(System .in) ; BufferedReader br = new BufferedReader(isr); String s = br.readLine(); return s; } // public static int getInt() throws IOException { String s = getString(); return Integer.parseInt(s); } // - 435... of the heap The underlying array should be invisible, but insertAt() allows direct access to it In this situation we accept the violation of OOP principles because the array is so closely tied to the heap architecture An incrementSize() method is another addition to the heap class It might seem as though we could combine this with insertAt(), but when inserting into the array in its role as an ordered... your line Here's another situation in which you might need to find all the vertices reachable from a specified vertex Imagine that you're designing a printed circuit board, like the ones inside your computer (Open it up and take a look!) Various components—mostly integrated circuits (ICs)—are placed on the board, with pins from the ICs protruding through holes in the board The ICs are soldered in place,... starting point It visits all the vertices adjacent to the starting vertex, and only then goes further afield This kind of search is implemented using a queue instead of a stack An Example Figure 13 .9 shows the same graph as Figure 13.5, but here the breadth-first search is used Again, the numbers indicate the order in which the vertices are visited Figure 13 .9: Breadth-first search A is the starting... heapSort .java: Enter number of items: 10 Random: 81 6 23 38 95 71 72 39 34 53 Heap: 95 81 72 39 53 71 23 38 34 6 95 81 72 39 53 71 23 38 34 6 Sorted: 6 23 34 38 39 53 71 72 81 95 The Efficiency of Heapsort As we noted, heapsort runs in O(N*logN) time Although it may be slightly slower than quicksort, an advantage over quicksort is that it is less sensitive to the initial distribution of 2 data. .. Listing 13.2 The bfs .java Program // bfs .java // demonstrates breadth-first search // to run this program: C >java BFSApp import java. awt.*; //////////////////////////////////////////////////////////////// class Queue { private final int SIZE = 20; private int[] queArray; private int front; private int rear; public Queue() // constructor { queArray = new int[SIZE]; front = 0; rear = -1; } public void insert(int... 13.2 shows the adjacency lists for the graph of Figure 13.2-a Table 13.2: Adjacency Lists Vertex List Containing Adjacent Vertices A B C B A D C A D A D B In this table, the symbol indicates a link in a linked list Each link in the list is a vertex Here the vertices are arranged in alphabetical order in each list, although that's not really necessary Don't confuse the contents of adjacency lists with paths... from those we've dealt with thus far in this book If you're dealing with general kinds of data storage problems, you probably won't need a graph, but for some problems—and they tend to be interesting ones—a graph is indispensable Our discussion of graphs is divided into two chapters In this chapter we'll cover the algorithms associated with unweighted graphs, show some algorithms that these graphs can . putText("Can't remove; heap is empty" + '
'); break; case 'c': // change putText("Enter index of item: "); value = getInt();. System.out.print(heapArray[j].iData + " "); System.out.println(""); } // - public void insertAt(int index, Node newNode) { heapArray[index]. - for(int m=0; m<currentSize; m++) if(heapArray[m] != null) System.out.print( heapArray[m].iData + " "); else System.out.print( " ");