/* * Author: Anmol Sethi */ public class BinarySearchTree> { @SuppressWarnings("unused") private static final long serialVersionUID = 29384723897L; class Node { E value; Node left = null; Node right = null; Node(E value) { this.value = value; } @Override public String toString() { return "( " + value + " )"; } } protected Node root = null; protected int size = 0; /** * Adds value to BST and returns true only if value is non-null and does not * already exist in the BST. * * @param val Value to be added to BST * @return True only if element was added to BST */ public boolean add(E val) { if (val == null) return false; int size = this.size; this.root = add(this.root, val); return size != this.size; } /** * Adds a value to the tree starting at Node n, returning the node occupying the * same position as was given, which may be a new node containing the given * value if the given node is null, or it may be an existing node if that node * already contains the given value. * * @param n The subtree to which to add the value * @param val The value to add * @return The updated subtree root (possibly null) after adding the value */ private Node add(Node n, E val) { if (n == null) { this.size++; return new Node(val); } int cmp = val.compareTo(n.value); if (cmp < 0) n.left = add(n.left, val); else if (cmp > 0) n.right = add(n.right, val); return n; } /** * Removes value from BST and returns true only if value is non-null and existed * in BST. * * @param val The value to remove * @return True only if the value was removed from the BST */ public boolean remove(E val) { if (val == null) return false; int size = this.size; this.root = remove(this.root, val); return size != this.size; } /** * Removes a value from the tree starting at Node n, returning the node * occupying the same position as was given (which can be null if the given node * gets removed). * * The below code follows the BST node deletion algorithm described in Data * Structures and Algorithms in Java, Section 11.1.2, page 464. * * @param n The subtree from which to remove the value * @param val The value to remove * @return The updated subtree root (possibly null) after removing the value */ private Node remove(Node n, E val) { if (n == null) return null; int cmp = val.compareTo(n.value); if (cmp < 0) n.left = remove(n.left, val); else if (cmp > 0) n.right = remove(n.right, val); else if (n.left == null) { /* * The value of n.right is the correct return value for this method whether it * is null or a Node. Thus there is no need for a (n.left == null && n.right == * null) branch. */ this.size--; return n.right; } else if (n.right == null) { this.size--; return n.left; } else { E pred = maxValue(n.left); n.value = pred; n.left = remove(n.left, pred); } return n; } /** * Returns the maximum value contained in the subtree rooted at the given Node, * or null if the given subtree is empty. * * @param n The subtree from which to search for the maximum value * @return The value of the node at the rightmost position, which holds the * maximum value of the represented sorted set */ protected E maxValue(Node n) { if (n == null) return null; if (n.right == null) return n.value; return maxValue(n.right); } /* * ASSIGNMENT METHODS: Implement the methods below. */ /** * Given a value that is stored in this BST, this method returns the * corresponding Node that holds it, or null if the value is null or does * not exist in this BST. * * @param val The value to find in the BST * @return The Node containing the value if it exists in this BST, else null */ // protected Node findNode(E val) { // return null; // } protected Node findNode(E val) { return findNode(root, val); } private Node findNode(Node current, E val) { if (current == null || val == null) return null; int cmp = val.compareTo(current.value); if (cmp == 0) return current; else if (cmp < 0) return findNode(current.left, val); else return findNode(current.right, val); } /** * Given a value, this method returns the depth of its Node, or -1 if the * value is null or does not exist in this BST. The depth is defined as the * number of edges from that node to the root. * * @param val The value of the node from which to calculate depth * @return The number of edges from the node containing the given value in * this BST to the root of the BST, or -1 if value does not exist * in the BST */ // protected int depth(E val) { // return -1; // } protected int depth(E val) { Node node = findNode(val); if (node == null) return -1; return depth(root, node); } private int depth(Node current, Node target) { if (current == null) return -1; if (current == target) return 0; int leftDepth = depth(current.left, target); if (leftDepth >= 0) return leftDepth + 1; int rightDepth = depth(current.right, target); if (rightDepth >= 0) return rightDepth + 1; return -1; } /** * For the subtree rooted at the given Node, this method returns the height * of the subtree. The height of a subtree is defined as the maximum number * of edges between the root of the subtree and any descendant leaf. The * height of a leaf is 0. The height of an empty subtree, represented as a * null Node, is defined to be -1. * * @param n Node representing a subtree rooted at that node * @return The height of the given subtree */ // protected static int height(BinarySearchTree.Node n) { // return -1; // } protected static int height(BinarySearchTree.Node n) { if (n == null) return -1; return Math.max(height(n.left), height(n.right)) + 1; } /** * Given null, returns true. Given an instance of Node, returns true only if * the absolute value of the difference in heights of its left and right * children is less than 2. * * @param n The node to test for balance * @return True only if node is null or is non-null and the absolute value * of the difference in heights of its left and right children is * less than 2 */ // protected static boolean isBalancedNode(BinarySearchTree.Node n) { // return true; // } protected static boolean isBalancedNode(BinarySearchTree.Node n) { if (n == null) return true; int leftHeight = height(n.left); int rightHeight = height(n.right); return Math.abs(leftHeight - rightHeight) < 2; } /** * This method returns false if and only if there exists any node in the * tree for which isBalancedNode(n) returns false; otherwise it returns * true. * * @return True only if the entire tree is balanced */ // public boolean isBalanced() { // return true; // } public boolean isBalanced() { return isBalanced(root); } private boolean isBalanced(Node node) { if (node == null) return true; boolean leftBalanced = isBalanced(node.left); boolean rightBalanced = isBalanced(node.right); boolean nodeBalanced = isBalancedNode(node); return leftBalanced && rightBalanced && nodeBalanced; } /** * OPTIONAL CHALLENGE: Produces the same result as isBalanced() but does so * in O(n) time. * * Ungraded. Do not attempt until after you have a working isBalanced() for * grading. * * @return True only if the entire tree is balanced */ // public boolean isBalancedFast() { // return true; // } public boolean isBalancedFast() { return isBalancedFast(root) != -1; } private int isBalancedFast(Node node) { if (node == null) return 0; int leftHeight = isBalancedFast(node.left); if (leftHeight == -1) return -1; int rightHeight = isBalancedFast(node.right); if (rightHeight == -1) return -1; int heightDiff = Math.abs(leftHeight - rightHeight); if (heightDiff > 1) return -1; return Math.max(leftHeight, rightHeight) + 1; } }