Problem Solving with Data Structures using Java: A Multimedia Approach Chapter 10: Generalizing Lists and Trees Chapter Objectives Story What we were just doing with LLNode What linked lists are good for. • Using linked lists to represent structure and behavior What trees are good for. • Using trees to represent structure and behavior • Examples • Equations • Matching • Unification Removing Redundant Code The DisplayableNode (for scene graphs) and CollectableNode (for sound trees) class hierarchies have lots of the exact same code. Can we put all this code in one place (an abstract superclass), make it general, but make it so that all the old code still works? Abstracting LLNode abstract LLNode Knows next Knows how to do all basic list operations CollectableNode Knows just knows the part that specializes LLNode for collectable (sound-returning) nodes. DrawableNode Just specializes LLNode to represent things that can draw with a turtle and draw down their linked list with a turtle (drawOn). LLNode: The abstract definition of what a linked list node is Given LLNode, we can make anything we want into a linked list. Want a linked list of students? • Subclass LLNode into StudentNode • StudentNodes know names and ID numbers, • • and know how to access and return these. The linked list part is inherited from LLNode. Every StudentNode knows next and knows how to getNext, add, etc. /** * Class that represents a node in a linked list * @author Mark Guzdial * @author Barb Ericson */ public abstract class LLNode { /** The next node in the list */ private LLNode next; /** * Constructor for LLNode that just sets * next to null */ public LLNode() { next = null; } /** * Method to set the next element * @param nextOne the element to set as next */ public void setNext(LLNode nextOne) { this.next = nextOne; } /** * Method to get the next element * @return the next element in the linked list */ public LLNode getNext() { return this.next; } /** Method to remove a node from the list, fixing * the next links appropriately. * @param node the element to remove from the list. */ public void remove(LLNode node) { if (node==this) { System.out.println("I can't remove myself from " + "the head of the list"); return; } LLNode current = this; // While there are more nodes to consider while (current.getNext() != null) { if (current.getNext() == node) { // Simply make node's next be this next current.setNext(node.getNext()); // Make this node point to nothing node.setNext(null); return; } current = current.getNext(); } } A node can’t remove itself /** * Insert the input node after this node. * @param node element to insert after this. */ public void insertAfter(LLNode node) { // Save what "this" currently points at LLNode oldNext = this.getNext(); this.setNext(node); node.setNext(oldNext); } /** * Return the last element in the list * @return the last element in the list */ public LLNode last() { LLNode current; current = this; while (current.getNext() != null) { current = current.getNext(); } return current; } /** * Return the number of the elements in the list * @return the number of elements in the list */ public int count() { LLNode current; int count = 1; current = this; while (current.getNext() != null) { count++; current = current.getNext(); } return count; } /** * Add the passed node after the last node in this list. * @param node the element to insert after this. */ public void add(LLNode node) { this.last().insertAfter(node); } /** * Reverse the list starting at this, * and return the last element of the list. * The last element becomes the FIRST element * of the list, and THIS goes to null. * @return the new head of the list */ public LLNode reverse() { LLNode reversed, temp; while (this.getNext() != null) { temp = this.last(); this.remove(temp); reversed.add(temp); } // Now put the head of the old list on the end of // the reversed list. reversed.add(this); // At this point, reversed // is the head of the list return reversed; // Handle the first node outside the loop reversed = this.last(); this.remove(reversed); } } Next step: Change DrawableNode to extend LLNode /** * Stuff that all nodes and branches in the * scene tree know. * @author Mark Guzdial * @author Barb Ericson */ public abstract class DrawableNode extends LLNode { /** * Constructor for DrawableNode */ public DrawableNode() { super(); // call to parent constructor } /** * Use the given turtle to draw oneself * @param t the Turtle to draw with */ public abstract void drawWith(Turtle t); // no body in an abstract method /** * Draw on the given picture * @param bg the background picture to draw on */ public void drawOn(Picture bg) { Turtle t = new Turtle(bg); t.setPenDown(false); this.drawWith(t); } } Have to fix branches /** * Ask all our children to draw, * then tell the next element to draw * @param turtle the Turtle to draw with */ public void drawWith(Turtle turtle) { // start with the first child DrawableNode current = this.getFirstChild(); // Have my children draw while (current != null) { getNext() returns an LLNode, but we need it to be a DrawableNode so that we can drawWith(). current.drawWith(turtle); turtle.moveTo(turtle.getXPos()+gap,turtle.getYPos()); current = (DrawableNode) current.getNext(); } // Have my next draw if (this.getNext() != null) { current = (DrawableNode) this.getNext(); current.drawWith(turtle); } } Rewriting CollectableNode to extend LLNode /** * Node in a sound tree. * @author Mark Guzdial * @author Barb Ericson */ public abstract class CollectableNode extends LLNode { /** * No argument constructor */ public CollectableNode() { super(); // call to parent class constructor } /** * Play the list of sound elements * after me */ public void playFromMeOn() { this.collect().play(); } /** * Collect all the sounds from me on * @return the collected sound */ public abstract Sound collect(); } All the linked list methods are now factored out. Have to cast /** * Collect all the sound from our firstChild, * then collect from next. * @return the combined sound */ public Sound collect() { Sound childSound; CollectableNode node; if (firstChild != null) { childSound = firstChild.collect(); } else { childSound = new Sound(1); } This error doesn’t show up at compile time, but it does at runtime. // Collect from my next if (this.getNext() != null) { Same error really, similar fix. node=(CollectableNode) this.getNext(); childSound=childSound.append(node.collect()); } return childSound; } Now we have a generic Linked List Node! We can generate a new linked list easily, by subclassing LLNode. Let’s create a linked list of students. /** * Class that represents a student node * in a linked list * @author Mark Guzdial * @author Barb Ericson */ public class StudentNode extends LLNode { /** the student this node is keeping track off */ private Student myStudent; /** * Constructor that takes the student * @param someStudent the student to store at this node */ public StudentNode(Student someStudent) { super(); myStudent = someStudent; } /** * Method to get the student stored at this node * @return the student stored at this node */ public Student getStudent() {return myStudent;} /** * Method to get information about this node * @return an information string */ public String toString() { if (this.getNext() == null) { return "StudentNode with student: " + myStudent; } else { return "StudentNode with student: " + myStudent + " and next: " + this.getNext(); } } /** * Main method for testing */ public static void main(String[] args) { Student student1 = new Student("Tanya Clark",1); Student student2 = new Student("Tim O'Reilly",2); Student student3 = new Student("Tesheika Mosely",3); StudentNode node1 = new StudentNode(student1); StudentNode node2 = new StudentNode(student2); StudentNode node3 = new StudentNode(student3); node1.setNext(node2); node2.setNext(node3); StudentNode node = (StudentNode) node1.getNext().getNext(); System.out.println(node.getStudent()); } } Why do people use linked lists? Whenever you want dynamic size to the list, You may want the ordering to represent something, You want insertion and deletion to be cheap and easy, You are willing to make finding a particular item slower. Examples of Linked Lists Order of layers in Visio or PowerPoint Notes in a Phrase in JMusic Video segments in non-linear video editing. Items in a toolbar. Slides in a PowerPoint presentation. But what are trees good for? Trees represent hierarchical structure. • When just representing ordering (linearity) isn’t enough. Trees can store operations (behavior), as well as data. • Linked lists can, too, but not as useful. Examples of Trees Representing how parts of music assemble to form a whole. Representing the elements of a scene. • Representing the inheritance relationships among classes. • Scene graph Class hierarchy Files and directories on your hard disk. Elements in an HTML page. Organization chart Political affiliations Example Tree: Equation It’s fairly easy to turn an equation into a tree. • • 3+4*5 If you see an operation, make a branch. Otherwise, make a node. The structure here represents order of operation. Evaluating this tree is like collecting() the sounds, but collection involves computation (behavior) at the branches. + 3 * 4 5 Example Tree: Taxonomies (Keeping track of meaning) Let’s say that you want to compare prices at various websites for the same item. • • • At one site, they call it the “retail price” At another, they call it the “customer’s cost” How do you track that these are similar meanings? It’s the same as knowing that a MoveBranch is a kind of Branch is a kind of DrawableNode! Tree of Meanings Here, the arrows mean the same thing as in a class hierarchy. The thing below is a specialization of the thing above. Artificial Intelligence (AI) and Semantic Web researchers really do represent taxonomies in just this way. Price Retail price Customer price Customer cost Wholesale price Business-tobusiness price Algorithm for matching 1. 2. 3. Traverse the tree to find phrase1. Traverse the tree to find phrase2. Do both have a common ancestor (parent)? That’s the meaning in common! 4. If not, need to extend the taxonomy. Example tree: Unification My cat ate a fat worm. Sentence Diagrams are trees. Arrows don’t mean the same things here. • • Some arrows are saying instance-of a category (like “noun”) Other arrows are saying has-pieceswithin (like links from “subject” and “object”) ate verb subject object noun article cat noun adjective adjective a worm My fat How do we search a collection of sentence diagrams? Imagine: You’re an expert with the National Security Agency. You have megabytes of captured terrorist messages. Using specialized Natural Language Understanding (NLU) technology, you have trees of all this text. Now what do you do with it? Compare trees We can create a tree describing the kind of sentence that we want to find. We leave variables that can be bound in the process of the query. • Matching complex trees like this is sometimes called unification. Not exactly the same word? How do we determine if it’s close enough? See previous slides… attack verb subject noun object noun <suspect> <location in United States> Trees in User Interfaces Any user interface is actually composed of a tree! Windows hold panes hold buttons and text areas. window pane pane button text area button button Binary Trees Binary trees have at most two children per branch. • Any node can be a branch. Binary search trees are binary trees that are wellstructured. Binary Trees Binary trees can represent any kind of tree. Lots of interesting properties. • The minimum number of levels of n nodes in a binary tree is log2(n)+1 Binary Search Trees Binary search trees are particularly fast to search. • • Lists are always O(n) to search Well-structured trees are O(log2 n) to search. Rule: For each data node • • bear Items to left are “less than” data in this node. Items to right are “greater than” data in this node. apple ant cash ark card cat Implementing Binary Trees /** * Class that represents a binary tree node * @author Mark Guzdial * @author Barb Ericson */ public class TreeNode { /** the data stored at this node */ private String data; /** the left child */ private TreeNode left; /** the right child */ private TreeNode right; Constructing a new node /** * Constructor that takes the string * to store * @param something the string to store */ public TreeNode(String something) { data = something; left = null; right = null; } Any node can be the root of a tree Printing a tree, recursively // Skipping getters and setters – you know what those look like /** * Method to return a string of information * @return the information string */ public String toString() { return "This: " + this.getData()+ " Left: " + this.getLeft() + " Right: " + this.getRight(); } } Testing our TreeNode > TreeNode node1 = new TreeNode("George"); > node1 // with no ending ';' it is like a System.out.println(node); This: George Left: null Right: null > TreeNode node1b = new TreeNode("Alicia"); > node1.setLeft(node1b); > node1 This: George Left: This: Alicia Left: null Right: null Right: null Implementing insert for binary search trees We’ll use strings as data. To compare them, use compareTo /** * Method to add a new tree node in the tree * @param newOne the node to add */ public void insert(TreeNode newOne) { /* if the data at this node is greater than the * data in the passed node */ if (this.data.compareTo(newOne.data) > 0) { // and no left child then add this as the left child if (this.getLeft() == null) { this.setLeft(newOne); } // else insert it into the left subtree else { this.getLeft().insert(newOne); } } // must be great than or equal else { // if no right child use this as the right child if (this.getRight() == null) { this.setRight(newOne); } // else insert into the right subtree else { this.getRight().insert(newOne); } } Testing: > TreeNode node1 = new TreeNode("Shilpa"); > TreeNode node2 = new TreeNode("Sam"); > TreeNode node3 = new TreeNode("Tina"); > TreeNode node4 = new TreeNode("Zach"); > System.out.println(node1); This: Shilpa Left: null Right: null > node1.insert(node2); > System.out.println(node1); This: Shilpa Left: This: Sam Left: null Right: null Right: null > node1.insert(node3); > System.out.println(node1); This: Shilpa Left: This: Sam Left: null Right: null Right: This: Tina Left: null Right: null > node1.insert(node4); > System.out.println(node1); This: Shilpa Left: This: Sam Left: null Right: null Right: This: Tina Left: null Right: This: Zach Left: null Right: null Finding in a binary search tree /** * Method to find the passed someValue in * the tree and return the node or return null * if it isn't found in the tree * @param someValue the value to find */ public TreeNode find(String someValue) { // if we found the value return the node if (this.getData().compareTo(someValue) == 0) { return this; } /* if the data in the current node is greater than * the value */ if (this.data.compareTo(someValue) > 0) { // if no left child return null (not found) if (this.getLeft() == null) { return null; } // else look in the left subtree else { return this.getLeft().find(someValue); } } /* the data in the current node is less than the value */ else { // if no right child then not found if (this.getRight() == null) { return null; } // look in the right subtree else { return this.getRight().find(someValue); } } } Testing the search on example tree Balancing a tree Both these trees are well-ordered as search trees. But balanced trees lead to O(log2n) search times. We balance by rotating subtrees Original, unbalanced Rotating right branch of Sam Printing out the elements in alphabetical order Our default toString() goes as left as possible before doing any right branches. How do we print out everything in the binary search tree in order? /** * Method to do an inorder traversal of the tree * @return a string with the data values in it */ public String traverse() { String returnValue = ""; // Visit left if (this.getLeft() != null) { returnValue += " " + this.getLeft().traverse(); } // Visit me returnValue += " " + this.getData(); // Visit right if (this.getRight() != null) { returnValue += " " + this.getRight().traverse(); } return returnValue; } In-Order Traversal of our example tree > node1.traverse() Sam Shilpa Tina Zach Pre-order vs. In-Order If equations were trees toString() does a pre-order traversal. • +*34*xy traverse() does in-order • 3*4+x*y Post-order gives the RPN version • yx*43*+ Using trees as lists /** * Method to add the newOne node as * the first node in the tree (treating * the tree like a list * @param newOne the new node to add */ public void addFirst(TreeNode newOne) { if (this.getLeft() == null) { this.setLeft(newOne); } else { this.getLeft().addFirst(newOne); } } /** * Method to add the newNode as the last * node in a list (treating the tree like a list) * @param newOne the node to add */ public void addLast(TreeNode newOne) { if (this.getRight() == null) { this.setRight(newOne); } else { this.getRight().addLast(newOne); } Example use: } > TreeNode node1 = new TreeNode("the"); > node1.addFirst(new TreeNode("George of")); > node1.addLast(new TreeNode("jungle")); > node1.traverse() " George of the jungle"