/*
* Nathaniel Lim
* CS 136 Williams College
* Lab: Binary-Multi-Tree
* Mon, April 14, 2008
*/
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
/*
* The BinaryMultiTree class supports a binary search tree structure, that
* sorts elements by a given Comparator<T> and places elements that are equivalent
* according that Comparator<T> into the same Node.
*
* The structure of the BMT has a reference to a root Node, which has the
* fields that resemble a tree structure. The reason for this choice over
* having the BMT class have a left and right BMT instance was for the
* integration of the remove method, which required Nodes to be
* manipulated.
*
* Keeping track of the size was a challenging part of the lab.
* The choice was to keep a size variable that kept track of elements
* within a Node and elements below it. This would make the size() constant
* time. This was easily maintained in the
* add method where at each level of recursion, it is given that the element
* x will be added somewhere on a certain subtree.
*
* However, with the structure of the remove operation, and having no reference
* to the parent Node, made the update of the size hard to implement. As solution
* a recursive method, called findSizes was written to traverse through the entire
* tree and set all the size variable of every Node in the tree to the correct value.
* This makes remove quite expensive when is does remove something. I assumed that
* size() would be called more often than remove, so having size maintained made size()
* constant time, which is better at the expense of having an expensive remove method.
*/
public class BinaryMultiTree<T> implements Iterable<T> {
private Comparator<T> comparator;
private Node<T> root;
public BinaryMultiTree(Comparator<T> comparator){
this.comparator = comparator;
root = new Node<T>(comparator);
}
/*
* Calls upon the field size contained in the Node of the structure
*/
public int size(){
return root.size();
}
public void add(T x){
root.add(x);
}
/*
* locateNode traverses through the tree structure starting at 'node'
* and returns the Node in which according to the comparator the value
* x may reside. This is useful for many methods that require a recursive
* traversal, such as contains, get, remove.
* It returns null when there is no such Node on the tree that
* could contain x
*
* O(log n)
*/
public Node<T> locateNode (Node<T> node, T x){
int c = comparator.compare(x, node.getValues().get(0));
Node<T> L = node.getLeft();
Node<T> R = node.getRight();
if (c == 0) {
return node;
} else if (c < 0) {
if (L != null){
return locateNode(L, x);
} else {
return null;
}
} else {
if (R != null){
return locateNode(R, x);
} else {
return null;
}
}
}
/*
* Finds the Node where x may reside, returns false if there is
* no such node, otherwise it returns whether or not x is contained in
* the values of the Node
*
* locating call: O(log n)
* Search within node: O(m), where m is
* the number of values within the node
*/
public boolean contains(T x){
Node<T> spot = locateNode(root, x);
if (spot == null){
return false;
} else {
return spot.getValues().contains(x);
}
}
/*
* Set the root reference to a new empty Node
*/
public void clear(){
root = new Node<T>(comparator);
}
/*
* Locates what node the removal should be from.
* If there are 2 or more things contained in the Node
* Simply remove if its there.
* Otherwise the Node will have one thing, which is removed
* and then the Node itself is removed from the structure.
*
* Remove takes O(log n) to find the node
* and O (n) to maintain the size field
*/
public T remove(T x){
Node<T> toGo = locateNode(root, x);
if (toGo == null){
return null;
} else {
ArrayList<T> y = toGo.getValues();
if (y.size() > 1){
int index = y.indexOf(x);
if (index == -1){
//Not Found
return null;
} else {
T out = y.remove(index);
findSizes(root);
return out;
}
} else {
T out = y.remove(0);
//Now with the Node toGo storing no values
//The Node must be removed from the tree structure
toGo = removeTopNode(toGo);
findSizes(root);
return out;
}
}
}
//This a method that can figure out all the sizes for all the nodes,
//if the remove or add method fails to update the size field
// O(n) where n is the number of nodes (not values)
private int findSizes(Node<T> n){
int s = 0;
s += n.getValues().size();
if (n.getLeft() != null){
s+= findSizes(n.getLeft());
}
if (n.getRight() != null){
s+= findSizes(n.getRight());
}
n.setSize(s);
return s;
}
/*
* Handles the four cases for removing a Node from a BinaryTree
* Method design from Java Structures by Duane A. Bailey
*/
private Node<T> removeTopNode(Node<T> topNode) {
//pre: topNode contains value wanted to remove
Node<T> left = topNode.getLeft();
Node<T> right = topNode.getRight();
//disconnect the top Node
topNode.setLeft(null);
topNode.setRight(null);
//Case 1
if (left == null){
return right;
}
//Case 2
if (right == null){
return left;
}
Node<T> predecessor = left.getRight();
//Case 3
if (predecessor == null){
left.setRight(right);
return left;
}
//Case 4: General
Node<T> parent = left;
while (!(predecessor.getRight() == null)){
parent = predecessor;
predecessor = predecessor.getRight();
}
// 'predecessor' is the predecessor of the root
parent.setRight(predecessor.getLeft());
predecessor.setLeft(left);
predecessor.setRight(right);
return predecessor;
}
/*
* Locates where the value x may be,
* and iterates through the value of the node
* until it finds an obj that equals x, returns it
* Returns null otherwise
*
* O(log n)
*/
public T get(T x){
Node<T> spot = locateNode(root, x);
if (spot != null){
ArrayList<T> vals = spot.getValues();
for (T obj: vals){
if (obj.equals(x)){
return obj;
}
}
return null;
} else {
return null;
}
}
public Iterator<T> iterator() {
return new InOrderIterator();
}
/*
* This inorder iterator simply does an inorder
* recursive traversal of the tree structure and
* adds all the values at each node to an ArrayList
* which is then iterated through.
*
* Instantiating and running the InOrderIterator class
* is O(n), which is necessary for any implementation of
* an iterator, however there is extra space allocated
* (an ArrayList of size n) that is not necessarily needed
* in other implementations. However this implementation is
* much easier to write because of having to deal with iterating
* through nodes and values within those nodes.
*/
private class InOrderIterator implements Iterator<T>{
ArrayList<T> allValues = new ArrayList<T>();
private int index = 0;
public InOrderIterator(){
buildHelper(root);
}
public void buildHelper(Node<T> current){
//Inorder recursion: L, Root, R
if (current.left != null){
buildHelper(current.left);
}
allValues.addAll(current.getValues());
if(current.right!=null){
buildHelper(current.right);
}
}
public boolean hasNext() {
return index < allValues.size();
}
public T next() {
T out = allValues.get(index);
index++;
return out;
}
//Implemented to do nothing.
public void remove() {
}
}
public class Name {
private String first, last;
public Name (String first, String last){
this.first = first;
this.last = last;
}
public String getFirst(){
return first;
}
public String getLast(){
return last;
}
public boolean equals (Object o){
Name other = (Name)o;
return last.equals(other.getLast()) &&
first.equals(other.getFirst());
}
public String toString(){
return first + " " + last;
}
}
public class LastNameComparator implements Comparator<Name>{
/*
* The compare method uses the fact that Strings are Comparable
* The Java Documentation says that the String compareTo method
* "compares two strings lexicographically". Which is how they need
* to be compared in this context.
*/
public int compare (Name a, Name b){
return a.getLast().compareTo(b.getLast());
}
// No one ever sees if two Comparators are equal
// Never truly implemented.
public boolean equals (Object obj) {
return false;
}
}
/*
* This helper class Node maintains the structure of the
* Binary Multi Tree. Each node has references to right and left
* and contains an ArrayList of values of type T.
*
* Size of a Node keeps track of how many values are in the current Node
* plus the number of values contained in both subTree Nodes
*
*/
private class Node<T> {
private ArrayList<T> values = new ArrayList<T>();
private Node<T> left;
private Node<T> right;
private int size = 0;
private Comparator<T> comparator;
public Node (Comparator<T> c){
comparator = c;
}
public ArrayList<T> getValues() {
return values;
}
public Node<T> getLeft() {
return left;
}
public void setLeft(Node<T> left) {
this.left = left;
}
public Node<T> getRight() {
return right;
}
public void setRight(Node<T> right) {
this.right = right;
}
public int size() {
return size;
}
public void setSize(int size) {
this.size = size;
}
/*
* Recursively adds a value to a Node tree
* Takes care of making new Nodes when needed,
* and adding to existing Nodes, while maintaining
* the Binary Search structure, and the size field
*
* O(log n)
*/
public void add(T x){
//This clause is only run when the this Node was
//recently instantiated.
if (values.size()==0){
values.add(x);
size++;
} else {
int c = comparator.compare(x, values.get(0));
if (c == 0){
values.add(x);
size++;
} else if (c < 0){
if (left == null){
left = new Node<T>(comparator);
}
left.add(x);
size++;
} else {
if (right == null){
right = new Node<T>(comparator);
}
right.add(x);
size++;
}
}
}
}
}