The Singleton Pattern II Recursive Linked Structures
BST Interface public interface BST<E extends Comparable<E>> extends Collection<E>{ // Traversals public List<E> preorder(); public List<E> inorder(); public List<E> postorder(); }
LinkedBST Class public class LinkedBST<E extends Comparable<E>> extends AbstractCollection<E> implements BST<E>{ private TreeNode<E> root = null; // Join the Collection family public boolean add(E item){return true;} public int size(){return 0;} public Iterator<E> iterator(){return this.preorder().iterator();} // Tree-like methods public boolean contains(Object item){return true;} public String toString(){return "";} // Traversals public List<E> preorder(){return new ArrayList<E>();} public List<E> inorder(){return new ArrayList<E>();} public List<E> postorder(){return new ArrayList<E>();} }
The TreeNode Class root “A” public class TreeNode<E extends Comparable<E>>{ public E data; public TreeNode<E> left, right; public TreeNode(E data){ this.data = data; this.left = null; this.right = null; } TreeNode<String> root = new TreeNode<>("A"); root “A”
Recursive Data Structures A recursive data structure is either a basic value, such as null, or contains a value of the same type of data structure The TreeNode class is a good example
Recursive Data Structures A linked structure is either empty (null) or contains a data value followed by other linked structures (left, right) of exactly the same form
Recursive Algorithms When processing a linked structure Handle the case of empty (null) Do something with the data value and then “recurse” with the linked structure (left, right) of exactly the same form
Some Recursive Patterns: Preorder Traversal if the linked structure is not empty process the value process the linked structure on the left process the linked structure on the right Note that there is an if statement rather than a while loop The process just quits when the base case (the end of the structure) is reached
Some Recursive Patterns: Preorder Traversal // In the LinkedBST class public List<E> preorder(){ List<E> list = new ArrayList<>(); preorderTraverse(this.root, list); return list; } private preorderTraverse(TreeNode<E> node, List<E> list){ if (node != null){ list.add(node.data); preorderTraverse(node.left, list); preorderTraverse(node.right, list);
Some Recursive Patterns: Search if the linked structure is empty return false else if the target equals the value return true else return the result of searching the rest of the linked structure
Some Recursive Patterns: Search public boolean contains(Object item){ return containsTraverse(this.root, (Comparable)item); } private boolean containsTraverse(TreeNode<E> node, Comparable<E> item){ if (node == null) return false; else{ int relation = item.compareTo(node.data); if (relation == 0) return true; else if (relation < 0) return containsTraverse(node.left, item); else return containsTraverse(node.right, item);
Some Recursive Patterns: size if the linked structure is empty return 0 else return 1 + the size of the rest of the linked structure
Some Recursive Patterns: size public int size(){ return sizeTraverse(this.root); } public int sizeTraverse(TreeNode<E> node){ if (node == null) return 0; else return 1 + sizeTraverse(node.left) + sizeTraverse(node.right);
Problems With Current Version Uses null for the case of an empty structure, so we must check for null with if statements in every method
Problems With Current Version A linked structure should be able to compute its own length But a recursive implementation of this would be pretty hard TreeNode n = new TreeNode("Ken"); int myLength = n.length();
Our View of the Current Version A linked structure is either empty (null) or contains a data value followed by another linked structure (next) of exactly the same form
A Better View A linked structure is either or empty (an empty node) compound (a compound node that contains a data value and another linked structure)
From Data to Algorithms Two concrete classes of nodes that implement the same interface The empty node’s methods handle the base cases The compound node’s methods handle the recursive ones Use polymorphism to make the choices (no explicit if statements in the code!)
From Data to Algorithms TreeNode CompoundNode EmptyNode
The TreeNode Interface public interface TreeNode<E extends Comparable<E>>{ public void preorder(List<E> list); public boolean contains(Comparable<E> item); public int size(); // etc. }
Node Classes, Etc. EmptyNode – The class of an empty node CompoundNode – The class of compound nodes EmptyNode.THE_EMPTY_NODE – The single instance used for any empty node
Using the New Implementation // In the class LinkedBST public List<E> preorder(){ List<E> list = new ArrayList<>(); this.root.preorderTraverse(list); return list; } public boolean contains(Object item){ return this.root.contains((Comparable)item); public int size(){ return this.root.size();
The EmptyNode Class The singleton instance is created just once public class EmptyNode<E> implements TreeNode<E>{ public void preorder(List<E> list){return;} public boolean contains(Comparable<E> item){return false} public int size(){return 0;} // etc. other instance methods go here // Constructor to prevent other instantiations private EmptyNode(){} static public final EmptyNode<E> THE_EMPTY_NODE = new EmptyNode<E>(); } The singleton instance is created just once
The CompoundNode Class public class CompoundNode<E> implements TreeNode<E>{ private E data; private CompoundNode<E> left, right; public CompoundNode(E data){ this.data = data; this.left = EmptyNode.THE_EMPTY_NODE; this.right = EmptyNode.THE_EMPTY_NODE; } public void preorder(List<E> list){ list.add(this.data) left.preorder(list); # Not recursion, but right.preorder(list); # polymorphism! public int size(){ return 1 + this.left.size() + this.right.size();
The CompoundNode Class public boolean contains(Comparable<E> item){ relation = item.compareTo(node.data); if (relation == 0) return true; else if (relation < 0) return this.left.contains(item); else return this.right.contains(item); }
Recursive Objects Instead of using explicit if statements to handle choices, try using polymorphism, which automatically handles them Divide the cases into different types of objects that obey the same interface (avoid using null for the simple case) Use simple objects for the base cases and recursive objects for the recursive ones