This section discusses abstraction of binary search trees.
This section assumes that you have already learned core concepts about binary search tress from the prerequisite. Thus, it focuses on the abstraction that can be applied to other types of binary search trees introduced in the following sections.
L1: defines two generic types, T for the type of the key and N is for the type of the binary node.
L8: calls the setKey() method to assign the value of key in L2.
Let us define boolean methods for the member fields:
publicbooleanhasParent() { return parent !=null; }publicbooleanhasLeftChild() { return left_child !=null; }publicbooleanhasRightChild() { return right_child !=null; }publicbooleanhasBothChildren() {returnhasLeftChild()&&hasRightChild();}/** @return true if the specific node is the left child of this node. */publicbooleanisLeftChild(N node) {return left_child == node;}/** @return true if the specific node is the right child of this node. */publicbooleanisRightChild(N node) {return right_child == node;}
What is the input parameter node is null for the isLeftChild() and isRightChild() methods?
Let us then define getters to access the member fields:
L9: this needs to be casted to N since the input parameter of isLeftChild() is N.
Is it safe to downcast this to N?
Finally, let us define setters and their helper methods:
publicvoidsetKey(T key) { this.key= key; }publicvoidsetParent(N node) { parent = node; }publicvoidsetLeftChild(N node) {replaceParent(node); left_child = node;}publicvoidsetRightChild(N node) {replaceParent(node); right_child = node;}/** * Replaces the parent of the specific node to be this node. * @param node the node whose parent to be replaced. */@SuppressWarnings("unchecked")protectedvoidreplaceParent(N node) {if (node !=null) {if (node.hasParent()) node.getParent().replaceChild(node,null);node.setParent((N)this); }}/** * Replaces the old child with the new child if exists. * @param oldChild the old child of this node to be replaced. * @param newChild the new child to be added to this node. */publicvoidreplaceChild(N oldChild,N newChild) {if (isLeftChild(oldChild)) setLeftChild(newChild);elseif (isRightChild(oldChild)) setRightChild(newChild);}
L5,10: sets node to be a child of this in two steps:
L6,11: replaces the parent of node with this.
L7,12: sets node to be the child.
L20: replaces the parent of node with this in two steps:
L22: node gets abandoned by its current parent.
L23: this becomes the new parent of node.
L32: replaces oldChild of this node with newChild.
Binary Node
Let us define the BinaryNode class inheriting AbstractBinaryNode:
publicabstractclassAbstractBinarySearchTree<TextendsComparable<T>,NextendsAbstractBinaryNode<T,N>> {protectedN root;publicAbstractBinarySearchTree() {setRoot(null); } /** @return a new node with the specific key. */abstractpublicNcreateNode(T key);publicbooleanisRoot(N node) { return root == node; }publicNgetRoot() { return root; }publicvoidsetRoot(N node) {if (node !=null) node.setParent(null); root = node; }}
L1: defines two generic types, T for the type of the key and N is for the type of the binary node.
L4: initializes the member field root to null.
L9: creates a binary node typed N, required for the add() method.
Why does Java not allow a generic type to be instantiated (e.g., node = new N())?
Let us define searching methods:
/** @return the node with the specific key if exists; otherwise, {@code null}. */publicNget(T key) {returnfindNode(root, key);}/** @return the node with the specific key if exists; otherwise, {@code null}. */protectedNfindNode(N node,T key) {if (node ==null) returnnull;int diff =key.compareTo(node.getKey());if (diff <0)returnfindNode(node.getLeftChild(), key);elseif (diff >0)returnfindNode(node.getRightChild(), key);elsereturn node;}/** @return the node with the minimum key under the subtree of {@code node}. */protectedNfindMinNode(N node) {returnnode.hasLeftChild() ?findMinNode(node.getLeftChild()): node;}/** @return the node with the maximum key under the subtree of {@code node}. */protectedNfindMaxNode(N node) {returnnode.hasRightChild() ?findMaxNode(node.getRightChild()): node;}
What are the worst-case complexities of findNode(), findMinNode(), and findMaxNode()?
L5: creates a node with the key to be the root if this tree does not include any node.
L7: finds the appropriate location for the key and creates the node.
What does the add() method above do when the input key already exists in the tree?
Let us define the remove() method:
/** @return the removed node with the specific key if exists; otherwise, {@code null}. */publicNremove(T key) {N node =findNode(root, key);if (node !=null) {if (node.hasBothChildren()) removeHibbard(node);elseremoveSelf(node); }return node;}
L6: removes a node with two children using the Hibbard algorithm.
L7: removes a node with no or one child
The removeSelf() method makes the node's only child as the child of its parent and removes it:
/** @return the lowest node whose subtree has been updatede. */protectedNremoveSelf(N node) {N parent =node.getParent();N child =null;if (node.hasLeftChild()) child =node.getLeftChild();elseif (node.hasRightChild()) child =node.getRightChild();replaceChild(node, child);return parent;}privatevoidreplaceChild(N oldNode,N newNode) {if (isRoot(oldNode))setRoot(newNode);elseoldNode.getParent().replaceChild(oldNode, newNode);}
L6: finds the child of node.
L7: replaces node with its child.
The removeHibbard() method finds a node that can be the parent of the left- and the right-children of node and makes it a child of its parent:
Which nodes are guaranteed to be the parent of those left- and right- children?
/** @return the lowest node whose subtree has been updatede. */protectedNremoveHibbard(N node) {N successor =node.getRightChild();N min =findMinNode(successor);N parent =min.getParent();min.setLeftChild(node.getLeftChild());if (min != successor) {parent.setLeftChild(min.getRightChild());min.setRightChild(successor); }replaceChild(node, min);return parent;}
The following demonstrates how the above removeHibbard() method works:
What is the worst-case complexity of the removeHibbard() method?
Binary Search Tree
Let us define the BinarySearchTree inheriting AbstractBinarySearchTree:
publicclassBinarySearchTree<TextendsComparable<T>> extendsAbstractBinarySearchTree<T,BinaryNode<T>> { /** * @param key the key of this node. * @return a binary node with the specific key. */ @OverridepublicBinaryNode<T> createNode(T key) {returnnewBinaryNode<T>(key); }}