/** * An implemention of a linked binary tree This version is without complete * implementations of some methods (hence the WO) * * @author gtowell Written: Feb 2020 Updated: Mar 26, 2020 to fix right-left * inversion throughout code Updated: Nov 2020 switchd some "alt" code * to be the main code and updated documentation * * * Methods with an @override annotation are documented in TreeInterface * * Methods named xxxAltxxx are alternative implementations. The * alternatives have exactly the same effect, they just achieve it * slightly differently. * @param */ public class LinkedBinaryTreeWO> implements TreeInterface { /** * A class implementing the tree node Note that this inner class is declared as * protected so it is available and visible to extending classes. */ protected class Node { /** The data in the node */ F payload; /** The right child */ Node right; /** The left child */ Node left; /** * Node constructor. Just takes the data element. Sets the right and left to * null * * @param e the data element to be held in the node */ public Node(F e) { payload = e; right = null; left = null; } /** * A print representation of the node. This just relies on the print rep of the * payload */ public String toString() { return payload.toString(); } } /** The number of elements in the tree */ protected int size; /** The root of the tree */ protected Node root; /** * Create an empty LinnkedBinaryTree */ public LinkedBinaryTreeWO() { root = null; size = 0; } @Override public int size() { return size; } /** * Alternate implementation of size() that does not use the size instance * variable * * @return the number of nodes in the tree */ public int sizeAlt() { return sizeAltUtil(root); } /** * Private recursive helper method for size. Returns the size of the subtree * rooted at treepart * * @param treepart the root of a subtree * @return the size of the subtree */ private int sizeAltUtil(Node treepart) { if (treepart == null) return 0; return 1 + sizeAltUtil(treepart.left) + sizeAltUtil(treepart.right); } @Override public boolean isEmpty() { return size == 0; } /** * Alternate implementation of isEmpty that does not use the size instance * variable Without size just check is the root is null * * @return true iff the tree has no nodes. */ public boolean isEmptyAlt() { return root == null; } @Override public E contains(E element) { Node tmp = containsUtil(root, element); if (tmp != null) return tmp.payload; return null; } /** * Recursive helper function for determining if an element is in the tree. This * version follows the algorithm and pseudocode in class. This version is clear * because the base cases are at the top of the function. * * @param treepart the root of a subtree * @param toBeFound the value to be looked for * @return if found, the node containing the value, otherwise null. */ private Node containsUtil(Node treepart, E toBeFound) { if (treepart == null) return null; int cmp = treepart.payload.compareTo(toBeFound); if (cmp == 0) return treepart; if (cmp > 0) { // 3/26 return containsUtil(treepart.left, toBeFound); } else { return containsUtil(treepart.right, toBeFound); } } /** * Alternate version of contains. The major different in this version is that * you check for null BEFORE recursion rather than after. In trees with a lot of * leaves this can be quicker. * * @param element the element to search the tree for * @return the contained element or null */ public E containsAlt(E element) { if (root == null) return null; Node tmp = containsAltUtil(root, element); if (tmp == null) return null; return tmp.payload; } /** * Recursive helper function for determining if an element is in the tree. * Checks for null before recursion so quicker. OTOH, the alt version has base * cases for recursion throughtout the method. * * @param treepart the root of the current subtree to examine * @param toBeFound the element being searched for * @return true iff the element is in the tree. */ private Node containsAltUtil(Node treepart, E toBeFound) { int cmp = treepart.payload.compareTo(toBeFound); if (cmp == 0) return treepart; if (cmp > 0) { // 3/26 if (treepart.left == null) return null; else return containsAltUtil(treepart.left, toBeFound); } else { if (treepart.right == null) return null; else { return containsAltUtil(treepart.right, toBeFound); } } } /** * Alternate version of insert. IMHO, this version is far more comprehensible. * It does, however, require more lines of code. * * @param element the element to be added */ public void insertAlt(E element) { root = insertAltUtil(root, element); } /** * Version of insert that closely follows the pseudocode and algorithm from * lecture. This extends the algorithm from lecture in that it explicitly * handles duplicates. Note that this method returns the root of the subtree * investigated and always updates the subtree. * * @param treepart the root of the current subtree * @param element the element to insert * @return */ private Node insertAltUtil(Node treepart, E element) { // code intentionally missing return null; } @Override public void insert(E element) { if (root == null) { root = new Node(element); size = 1; } else insertUtil(root, element); } /** * Alterate recursive helper function for insertion of an element into a tree. * Again, watch out for base cases. * * @param treepart the root of the current subtree * @param toBeAdded the element to be added to the tree */ private void insertUtil(Node treepart, E toBeAdded) { // code intentionally missing return; } @Override public E remove(E element) { E tmp = contains(element); if (tmp == null) return null; root = removeUtil(root, element); return tmp; } /** * Find the value stored in the leftmost node of the tree * * @param sRoot the subtree root * @return the data leement in the left most node of the subtree */ private E minKey(Node sRoot) { if (sRoot.left == null) return sRoot.payload; else return minKey(sRoot.left); } /** * Find the maximum value in the tree rooted at the given node * * @param treepart the subtree root * @return the data element in the right most node of the subtree */ @SuppressWarnings("unused") private E maxKey(Node treepart) { if (treepart.right == null) return treepart.payload; else return maxKey(treepart.right); } /** * Find the maximum value in the tree rooted at the given node, and do it * without recursion. * * @param treepart the subtree root * @return the data element in the right most node of the subtree */ @SuppressWarnings("unused") private E maxKeyNR(Node treepart) { Node rightChild = treepart.right; while (rightChild != null) { treepart = rightChild; rightChild = treepart.right; } return treepart.payload; } /** * Recursive helper function for removing a node with the given element * * @param sRoot the root of the subtree being examined. * @param element the element to be removed. * @return the root of the subtree after element deletion */ private Node removeUtil(Node sRoot, E element) { // code intentionally missing return null; } /** * An alternate version of remove. Considerably longer, but more easily * understood. * * @param element the element to be removed * @return true iff the element is in the tree */ public boolean removeAlt(E element) { if (root == null) return false; return removeAltUtil(root, null, element); } /** * Internal, recursive implementation of remove * * @param treepart the root of the current subtree * @param parent the parent of the root of the current subtree * @param toBeRemoved the element to be removed. * @return true iff toBeRemoved is in the tree. */ private boolean removeAltUtil(Node treepart, Node parent, E toBeRemoved) { // code intentionally missing return false; } @Override public int height() { int tmp = maxDepthUtilAlt(root, 0) - 1; return tmp >= 0 ? tmp : 0; } /** * An internal recursive helper function to calculate the height of the tree * with the given root. * * @param node the root of the subtree for which the height is desired * @return */ private int maxDepthUtil(Node node) { if (node == null) return 0; int rd = maxDepthUtil(node.right) + 1; int ld = maxDepthUtil(node.left) + 1; if (rd > ld) return rd; else return ld; } private int maxDepthUtilAlt(Node node, int currDepth) { if (node == null) return currDepth; int rd = maxDepthUtilAlt(node.right, currDepth + 1); int ld = maxDepthUtilAlt(node.left, currDepth + 1); return rd > ld ? rd : ld; } /** * Print the tree in postorder The tree data will be printed on a single line * with each node appearing as [payload,depth] */ public void printPostOrder() { iPrintPostOrder(root, 0); System.out.println(); } /** * Recursive helper function for postorder printing. * * @param treePart the root of the current subtree * @param depth the depth of the root of the current subtree. */ private void iPrintPostOrder(Node treePart, int depth) { if (treePart == null) return; iPrintPostOrder(treePart.left, depth + 1); iPrintPostOrder(treePart.right, depth + 1); System.out.print("[" + treePart.payload + "," + depth + "]"); } @Override public String printNaturalOrder() { return ""; } /** * Return a breadth-first listing of the tree. * * @return A string containing the breadth first listing. */ public String breadthFirstListing() { StringBuffer collect = new StringBuffer(); // This works by descending to all nodes of level X, then X+1, etc // This means that to get the nodes at level X I need to go through level X-1 // Hence, nodes at level X-1 will actually be looked at (height-X-2) times which // which is kind of inefficient. So long at the tree is reasonably balanced, this // inefficiency is not horrible. // Faster solutions take more space. for (int targetLevel = 0; targetLevel <= height(); targetLevel++) { collect.append(targetLevel + " ["); bfUtil(root, targetLevel, 0, collect); collect.append("]\n"); } return collect.toString(); } /** * Recursive utility function for doing the breadth first listing * * @param node the current node to be considered * @param targetLevel the level at which you want to actually collect the nodes * @param currentLevel the current level of the node * @param buf a string buffer into which to write data */ private void bfUtil(Node node, int targetLevel, int currentLevel, StringBuffer buf) { if (node == null) return; if (targetLevel == currentLevel) { buf.append(" " + node.payload.toString()); return; } bfUtil(node.left, targetLevel, currentLevel + 1, buf); bfUtil(node.right, targetLevel, currentLevel + 1, buf); } public static void main(String[] args) { /* * This test will not work in the WO class because the implementations of both * insert and remove are empty */ LinkedBinaryTreeWO lbt = new LinkedBinaryTreeWO<>(); lbt.insert(10); lbt.insert(20); lbt.insert(15); lbt.insert(17); lbt.insert(18); lbt.insert(16); lbt.insert(40); lbt.insert(5); lbt.insert(8); lbt.insert(3); lbt.insert(60); lbt.insert(80); lbt.insert(100); System.out.println("height" + lbt.height()); System.out.println(lbt.breadthFirstListing()); lbt.remove(40); lbt.remove(100); lbt.remove(10); System.out.println(lbt.breadthFirstListing()); } }