/** Implementation of a binary search tree. * We need to modify this implementation so that it represents an AVL * tree as well. We need to preserve this property: when a node has * 2 children, their heights differ by at most 1. * This will affect the way we do inserts and deletes. */ import java.util.NoSuchElementException; public class Tree { private Node root; public Tree() { root = null; } /** Need to find the appropriate place in the tree to add this value. * But when we find it, need to be aware of its parent, and whether it * should be a left or right child. */ public void add(Node newNode) { if (root == null) { root = newNode; } else { Node current = root; boolean added = false; while (! added) { // should we go left? if (newNode.compare(current) < 0) { if (current.left == null) { current.left = newNode; current.left.parent = current; // I'm my child's parent. added = true; } else current = current.left; } // otherwise we go right: else { if (current.right == null) { current.right = newNode; current.right.parent = current; // I'm my child's parent. added = true; } else current = current.right; } } // Need to handle AVL tree property. // ADD CODE HERE // Copied from class notes: // From w (current node we add) up to the root, // look for a node z that is unbalanced. // Unbalanced means its 2 children have heights differing by 2+, // or the case that z's height is 2+ but has only 1 child. // Let y be z's taller child. // Let x be y's taller child. If a tie, choose x to be w's ancestor. // // Refer to x,y,z as a,b,c -- where abc is an inorder traversal // (ascending values). Refer to the subtrees of x,y,z as T1-T4 // where these are also in an inorder/ascending relationship. // Replace the subtree rooted at z with a new subtree rooted at b. // It's children are a and c; its grandchildren are T1-T4. } // We're done inserting. See what we have so far. System.out.printf("%s\n", this.toString()); } /** find - You might want to implement this method too: * Return the node (if any) having the specified key value, * Begin the search at the specified (Node) starting point. */ /** findHeight - needed by AVL restructuring */ /** findTreeHeight - use findHeight() to find height of the root node; * i.e. height of the tree. */ /** For removing an element, there are 3 cases. If the victim is a * leaf node, we can just get rid of it. But if it has one child, * that child gets promoted to the victim's position. If the victim has * 2 children, then we have to promote the inorder predecessor (we could * have done successor alternatively) to the victim's position. */ public void remove(Node victim) { Node position = find(victim); System.out.println("remove(): The victim is at position " + position); if (position == null) throw new NoSuchElementException("value " + victim.data); // First case - leaf node if (position.left == null && position.right == null) { // Tell parent that its child is gone. if (position.equals(position.parent.left)) position.parent.left = null; else position.parent.right = null; // Because of garbage collection, nothing left pointing at position. // So probably unnecessary to explicitly kill it. // position = null; } // Second case - one child. // Don't forget to set the victim's child's parent to be victim's parent. else if (position.left == null) { position.right.parent = position.parent; if (position.equals(position.parent.left)) position.parent.left = position.right; else position.parent.right = position.right; } else if (position.right == null) { position.left.parent = position.parent; if (position.equals(position.parent.left)) position.parent.left = position.left; else position.parent.right = position.left; } // Third case - 2 children. Need to find the inorder predecessor // node and move its value to this position. The inorder predecessor // can be found by going left once, then right as far as necessary. else { Node pred = position.left; while (pred.right != null) pred = pred.right; position.data = pred.data; // Now we need to delete the pred node since its value was just copied. // This should take care of the case where pred has a left child that // shouldn't be lost. // Need to make the pred's parent believe the truth. // And need to set the parent of the pred's child = pred's parent. if (pred.left != null) { pred.parent.right = pred.left; pred.left.parent = pred.parent; } else pred.parent.right = null; } } // Find an element (to remove). public Node find(Node target) { Node current = root; while (true) { if (current == null || target.equals(current)) break; else if (target.compare(current) < 0) current = current.left; else current = current.right; } return current; } public Node findMin() { Node node; for (node = root; node != null && node.left != null; node = node.left) ; return node; } public Node findMax() { Node node; for (node = root; node != null && node.right != null; node = node.right) ; return node; } // Normally, toString takes no parameter, but we want one that recursively // traverses the tree, so we should have a version that takes a parameter, // starting with the root. public String toStringVerbose() { return toStringVerbose(root); } // Print out all the elements of the BST in preorder. public String toStringVerbose(Node n) { String build = ""; if (n == null) return build; build = "node = " + n.data + ", left = "; if (n.left == null) build += "null "; else build += "" + n.left.data + " "; build += ", right = "; if (n.right == null) build += "null"; else build += "" + n.right.data + " "; build += ", parent = "; if (n.parent == null) build += "null\n"; else build += "" + n.parent.data + "\n"; build += toStringVerbose(n.left); build += toStringVerbose(n.right); return build; } /** toString - Let's make the tree look more like a tree. 1111111111222222222233333333334444444444555555555566666666667777777777 1234567890123456789012345678901234567890123456789012345678901234567890123456789 nn nn nn nn nn nn nn nn nn nn nn nn nn nn nn nn nn nn nn nn nn nn nn nn nn nn nn nn nn nn nn */ public String toString() { int [] a = new int[32]; setArray(root, a, 1); StringBuilder sb = new StringBuilder(); sb.append("The first 5 levels of the tree:\n"); sb.append(String.format("%41s\n", makePretty(a[1]))); sb.append(String.format("%21s%40s\n", makePretty(a[2]), makePretty(a[3]))); sb.append(String.format("%11s", makePretty(a[4]))); for (int i = 5; i <= 7; ++i) sb.append(String.format("%20s", makePretty(a[i]))); sb.append("\n"); sb.append(String.format("%6s", makePretty(a[8]))); for (int i = 9; i <= 15; ++i) sb.append(String.format("%10s", makePretty(a[i]))); sb.append("\n"); sb.append(String.format("%3s", makePretty(a[16]))); for (int i = 17; i <= 31; ++i) sb.append(String.format("%5s", makePretty(a[i]))); sb.append("\n"); return sb.toString(); } // Allow us to print nothing in case of 0. // Allocate 2 digits for the number. public String makePretty(int n) { if (n == 0) return " "; else return String.format("%2d", n); } /** setArray - Recursive approach. Let's put the values from the AVL * tree into an array, with a maximum of 5 levels. I want to be able * to index the tree locations with 1 at the root, 2 and 3 at the next * level, etc., so that the children are always at 2i and 2i+1. * So, we need to allocate space for 32 elements. [0] will not be * used, and 5 levels of the tree will need to go up to [31]. * The 5th level is [16] thru [31]. The max # levels ensures that we will * have enough room to print them within 80 columns. * Let's assume that the entire array is set to 0 representing null. */ public void setArray(Node n, int [] a, int i) { if (n == null) return; a[i] = n.data; // No more recursion if deep enough if (i >= 16) return; setArray(n.left, a, 2*i); setArray(n.right, a, 2*i+1); } }