PROGRAMMING II – ABSTRACT DATA TYPES Unassessed Coursework 4: Binary Search Trees and Heaps The aims of this coursework is to practice: using Binary Search Trees (BST) by writing examples of higher level procedures and using them in small problems. using heaps by writing examples of access procedures, higher level procedures and small problem applications. 1. Assume the Java type declaration for the dynamic implementation of a BST given in Slide 3 of the lecture nodes on BST, with searchkey of type Integer and value of type String. Give the Java implementation of the following access procedure using recursion: public String retrieve(int searchKey) //post: Returns the item in the BST whose search key equals searchKey. //Returns null if no such item exists 2. Assume the availability of an ADT List of integers, with access procedures isEmpty, add, remove, get, and the availability of an ADT BinarySearchTree<String Integer> with its own access procedures, including the access procedures getLeftSubTree and getRightSubTree. Write the Java implementation of the following high-level procedures, using recursion: public List TreetoList(BinarySearchTree<String, Integer> myTree). //post: Returns a list containing the item values in the tree in descending //order. public List getOdd(BinarySearchTree<String, Integer> myTree) //post: Returns a list containing the odd (i.e. not divisible by 2) values in //the tree in ascending order. 3. The following Abstract Data Type QueueTrees consists of a queue of Binary Search Trees. The queue’s node includes a value of type char, and the tree’s node includes a search key of type Integer and a value of type String as illustrated in the picture below. QueueTrees last first 5 A 2 B . . T Name 11 10 Name Name 7 Name Name 20 Name (a) Give the Java type declarations for this ADT QueueTrees including the type declarations of all the data structures needed to implement it. (b) Assume now the existence of a class BinarySearchTree that implements an ADT BinarySearchTree. Give the new type declaration for QueueTrees. (c) Using your answer to part (b), give the Java implementation of the following access procedure. Include all additional access procedures needed to implement this access procedure. public void AddNewTNode(Integer SearchKey, String NewName){ //pre: Takes newName and a key value searchKey. //post: insert a new node with the given name and search key in the tree //referenced by the first node in the queue. 4. Consider the static implementation of an ADT Heap illustrated diagrammatically in slide 6 of the lecture notes on Heaps. Assume the elements included in the heap to be of type Integer. (a) Give the Java data structure declaration for such type of implementation, assuming the maximum size of the heap to be equal to 100. (b)Implement the constructor for creating an empty heap, and the access procedures isEmpty( ) and heapInsert(Integer newItem). Provide also an appropriate HeapException method. 5. In slide 9 of the lecture notes on Heaps, I gave the pseudocode of the procedure heapRebuild. Assume now this procedure to be an auxiliary procedure of a class Heap that implements a heap. Using the Java data structure declaration you have given in question 4(a), give the Java implementation of auxiliary procedure heapRebuild, declared as follows: protected void heapRebuild(Integer[ ] items, int root, size) //pre “root” is the index of the semi-heap’s root that has to be placed at //the proper position. 6. Assume the existence of a class Heap that implements an ADT heap of elements of type T. Implement an ADT PriorityQueue, of elements also of type T, that uses the class Heap as underlying data structure, and has the access procedures defined by the following interface. For simplicity, ignore the cases of exception. public interface PQInterface<T>{ } public boolean pqisEmpty(); //pre: none //post: Insert newItem in the priority queue at its proper position. public void pqInsert(T newItem) //pre: none //post: Retrieve and delete the item with highest priority. public T pqDelete( ) 7. Assume the existence of a class Heap that implements an ADT heap of elements of type Integer. Give the Java implementation of the following high-level procedure. If you use any auxiliary procedure, give also its implementation. public Heap ChangeItem(Heap myHeap, Integer Item, newItem) //pre: myHeap is not empty //post: returns a new heap equal to myHeap but with Item replaced by //newItem, if Item is in myHeap. 8. Assume an hash table of just integers, that uses the hash function h(x) = x mod 7 and a separate chaining strategy for resolving conflicts. What does the hash table loop like after the following insertions occur? 8, 10, 24, 15, 32, 17 9. Consider the following two hash functions: h1(key) = key div 250 h2(key) = key mod 250 where the possible key range is the set of integers 1, 2, 3,…, 1000. Assume the hash table to be an array of size 250, where each item is capable of holding 2 items, and to use an open addressing scheme to resolve collisions. For each of the functions h1 and h2, show the distribution of the keys among the table after the following insertions are performed: (a) 1,2,3,…,250 are added to an empty table (b) 5, 10, 15, 20, …, 980, 985, 990, 995, 1000 are added to an empty table. (c) What conclusion can you draw from this example about the selection of a hash function? PROGRAMMING II – ABSTRACT DATA TYPES Unassessed Coursework 4 ANSWERS: Binary Search Trees and Heaps 1. public String retrieve(int searchKey){ return retrieveItem(root, new Integer(searchKey)); }//end retrieve private String retrieveItem(TreeNode<Integer, String> tNode, Integer Key) { if (tNode == null) { return = null; } else{ if (Key.equals(tNode.getKey( )){ String nodeValue = tNode.getValue( );} else { if (Key.compareTo(tNode.getKey( )) < 0) { nodeValue = retrieveItem(tNode.getLeft( ), Key); } else{ nodeValue = retrieveItem(tNode.getRight( ), Key);\ }} return nodeValue; } }// end retrieveItem 2. public List TreetoList(BinarySearchTree<String, Integer> myTree) { List newlist = new List( ); return TreeListCons(myTree, newlist); } private List TreeListCons(BinarySearchTree<String, Integer> myTree, List mylist){ if (myTree.isEmpty( )){ return mylist; } else{ mylist = TreeListCons(myTree.getLeftSubTree( ), mylist); Integer item = myTree.getRoot( ); mylist.add(1, item.intValue( )); mylist = TreeListCons(myTree.getRightSubTree( ), mylist); return mylist; } } public List getOdd(BinarySearchTree<String, Integer> myTree){ List newlist = new List( ); return getOddCons(myTree, newlist);} public List getOddCons(BinarySearchTree<String, Integer> myTree, List mylist){ if (myTree.isEmpty( )){ return mylist; } else{ mylist = getOddCons(myTree.getLeftSubTree( ), mylist); if((myTree.getRoot( )).intValue( )%2 != 0) { mylist.add(mylist.size( )+1, (myTree.getRoot( )).intValue( ));} mylist = getOddCons(myTree.getRightSubTree( ), mylist); return mylist; } } 3. (a) (b) 1. class TreeNode<K,V>{ } private K key; private V value; private TreeNode<K,V> left; private TreeNode<K,V> right; … // methods definitions; 2. class BST<K extends } 1. class TreeNode<K,V>{ } class QueueNode{ private BinarySearchTree <Integer, String> tree; private char item; private QueueNode next; ….// methods definitions } 2. Comparable<K>,V>{ private TreeNode<K,V> root; … // methods definitions 3. class QueueNode{ private BST<Integer, String> tree; private char item; private QueueNode next; ….// methods definitions } 4. class Queue{ } private QueueNode first; private QueueNode last; ….// methods definitions 5. class QueueTrees } private Queue fullstructure; ….//methods definitions private K key; private V value; private TreeNode<K,V> left; private TreeNode<K,V> right; … // methods definitions; 3. class Queue{ } private QueueNode first; private QueueNode last; ….// methods definitions 4. class QueueTrees } private Queue fullstructure; ….//methods definitions (c) public void AddNewTNode(Integer searchKey, String newName){ //pre: Takes newName and a search key value SearchKey //post: insert a new node with the given name and search key in the tree //pointed by the first node in the queue QueueNode myqueueElement = fullstructure.getFrontNode( ); BinarySearchTree<Integer, String> mytree = myqueueElement.getTree( ); mytree.insert(searchKey, newName); } public QueueNode getFrontNode( ){ //This is an access procedure of the class Queue return first; } public BinarySearchTree getTree( ){ //This is an access procedure of the class QueueNode return tree; } Note: we don’t need to give the implementation of insert because we are assuming that there is already an implementation of a BinarySearchtre ADT with its access procedures. 4. (a) (b) public class Heap<Integer>{ private int max_heap = 100; private Integer[ ] items; private int size; // access procedures } //maximum size of the heap //array of heap items //number of items in the heap public Heap( ){ items = (T[ ]) new Object[max_heap]; size = 0; } public boolean isEmpty( ){ return size = = 0; } public void heapInsert(Integer newItem) throws HeapException{ if (size < Max_Heap){ item[size] = newItem; int place = size; int parent = (place –1)/2; while ( (parent >=0) && (items[place].compareTo(items[parent])>0){ Integer temp = item[place]; items[place] = items[parent]; items[parent] = temp; place = parent; parent = (place-1)/2; }//end while else { throw new HeapException(“HeapException: Heap full”); } }//end heapInsert public class HeapException extends RuntimeException{ public HeapException(String s){ super(s); }//end constructor } //end HeapException 5. protected void heapRebuild(Integer[ ] items, int root, int size){ int child = 2*root+1; if( child < size){//root is not a leaf, so it has a left child at position child int rightChild = child+1; if ((rightChild < size) && (items[rightChild].compareTo(items[child]) > 0)){ child = rightChild; } if (items[root].compareTo(items[child]) < 0){ Integer temp = items[root]; items[root] = items[child]; items[child] = temp; heapRebuild(items, child, size);} } } 6. public class PriorityQueue<T extends Comparable<T>> implements PQInterface{ private Heap<T> h; public PriorityQueue( ){ h = new Heap( );} public boolean pqisEmpty( ){ return h.isEmpty( );} public void pqInsert(T newItem){ h.heapInsert(newItem);} public T pqDelete( ){ return h.heapDelete( );} } NOTE: In the above implementation of the method “pqInsert”, we have ignored the fact that heapInsert may throw an exception when the heap is full. This is because I have asked in the question to not consider the cases of exception. A full implementation of the method “pqInsert” would have to use “try” and “catch” in order to try the operation “heapInsert” and catch a possible exception, and then itself throw an exception error. public Heap ChangeItem(Heap myHeap, Integer item, Integer newItem){ Heap auxheap = new Heap( ); boolean done = false; do{ Integer temp = myHeap.heapDelete( ); if (! temp.equals(item)){ auxheap.heapInsert(temp); } else { auxheap.heapInsert(newItem); done = true; } }while (! (done || myHeap.isEmpty( )) ); heapCopy(myHeap, auxheap); return auxheap; 7. } public void heapCopy(Heap fromHeap, Heap toHeap){ Integer temp; while(!fromHeap.isEmpty( )){ toHeap.heapInsert(fromHeap.heapDelete( )); } } 8. 0 1 2 3 4 5 6 15 8 17 32 24 10 9. (a) 1. Consider the function h1: 1 2 3 4 5 6 7 8 0 1 2 3 50 24 248 249 250 123 124 Only the first and second element will be at theirn own hash position, all the other will be included in the table by linearly probing along the array. In particular: 1, 2 are at their hash position, 3, 4 are at their probed position 1 5, 6 are at their pobed position 2, etc… 2. Consider the function h2: 0 1 2 3 1 2 3 4 24 248 249 250 24 248 249 In this case all the elements are inserted at their own proper hash positions. (b) 1. Consider the function h1: 5 10 15 20 25 30 35 40 …… 0 1 2 3 250 990 995 1000 24 98 99 248 249 Only 5 and 10 will be at their own position, all the other will be included in the table by linearly probing along the array. 2. Consider the function h2: 5 0 1 2 3 10 15 20 245 250 255 5 10 495 500 745 995 1000 245 The picture above shows only some of the allocations in the table. In particular: all the elements from 5 to 500 are allocated at their own hash positions (these are half of the keys values). The other elements are probed along the table. (c) Conclusion: 1) If the hash function uses some kind of division method, the “mod function is better than the div function. 2) Moreover, when using the mod function make sure that the number of positions in the table (i..e the size of the table) is a prime number. For instance in the above case, we were able only to assign half of the givens equence of numbers at their respective hash positions essentially because the sequence of numbers was given by the expression m* i*5, where i= 0,1,2,3,…..and the number 5 is a factor of the size of the table. 249