/* ************************
* Name : Eugene Krapivin *
* ID : 306255084 *
* ***********************/
package trees;
import exceptions.*;
import java.io.Serializable;
import java.util.Stack;
import nodes.Node;
/**
* Data structure implements a Binary search tree.
* <p>
* In a binary search tree, every sub tree is a binary search tree as well. Left
* son of the root is smaller then the value of the root, the right side is
* larger then the root.
* <p>
* Class has some basic IO function to the structure. For more information see
* {@code http://en.wikipedia.org/wiki/Binary_search_tree}
* <p>
* Since it is a search tree, having duplicate values seem to be wrong because
* only one of the values will be reached, hence I do not allow duplicate values
* in the tree.
*
* @see Node
* @version 1.1 12/3/2012
* @author Eugene Krapivin
*/
public class BinarySearchTree implements Serializable
{
private static final long serialVersionUID = -4148557239778403932L;
protected Node root;
/* Class constructors */
/**
* Class constructor.
* <p>
* This is the default constructor for the class {@code BinarySearchTree}.
* Since the data in the nodes is immutable for obvious reasons, the node
* will be automatically initialized to 0.
*
* @see Node
* @deprecated
*/
public BinarySearchTree()
{
root = new Node();
}
/**
* Class constructor.
* <p>
* This constructor receives the first value of the root (in a B search tree
* the root must always have a value) The sons of the first node a NULL and
* the data of the first node (root) is set to the data value.
*
* @see Node
* @param data
* the data to set in the root node
*/
public BinarySearchTree(int data)
{
root = new Node(data);
}
/**
* Class constructor.
* <p>
* This is a copy constructor for the BinarySearchTree structure. It will
* create a distinct duplicate from the original.
*
* @param other
* the other binary search tree to copy from
* @throws NotBinarySearchTree
*/
public BinarySearchTree(BinarySearchTree other) throws NotBinarySearchTree
{
if (checkIfBST(other.getRoot()) == true)
{
root = other.root.clone(); // using the cloning method of class node
}
else
{
throw new NotBinarySearchTree(
"Tree can not be set, the argument is not a legal binary search tree");
}
}
/**
* Class constructor.
* <p>
* This is a "soft" copy constructor. It receives a {@code Node} to which
* the first node of the new tree will be initialized. The references of the
* {@code Node} will be copied as well.
*
* @param root
* the Node to which the constructor will initialize the new
* tree.
* @throws NotBinarySearchTree
*/
public BinarySearchTree(Node root) throws NotBinarySearchTree
{
if (checkIfBST(root) == true)
{
setRoot(root);
}
else
{
throw new NotBinarySearchTree();
}
}
/* getter/setter */
/**
* getter method for root field.
*
* @return root the root of the tree, first node.
*/
public Node getRoot()
{
return root;
}
/**
* Setter method for field root.
*
* @param root
* a Node to which I will initialize the root
*/
public void setRoot(Node root)
{
this.root = root;
}
/* helper methods */
/**
* The methods checks whether a Node is a binary search tree
* <p>
* for more information about the rules of a binary search tree {code http
* ://en.wikipedia.org/wiki/Binary_search_tree}
*
* @param root
* the node which will be checked
*
* @return true if the tree is in correspondence to the rules of a BST
* otherwise false
*/
private boolean checkIfBST(Node root)
{
if (root == null)
{
return true;
}
// return false if the left branch is bigger then root value
if (root.getLeft() != null)
{
if (root.getData().compareTo(root.getLeft().getData()) <= 0)
{
return false;
}
}
// return false if the right branch is smaller then root value
if (root.getRight() != null)
{
if (root.getData().compareTo(root.getRight().getData()) == 1)
{
return false;
}
}
// if neither of the previous checks didn't yield false, check the sons
return true && checkIfBST(root.getLeft())
&& checkIfBST(root.getRight());
}
/**
* Find minimal value of the tree or sub-tree.
* <p>
* By the structure of binary search trees the smallest value will be the
* left most value of the tree. the method is private and made for inner use
* of method remove.
* <p>
* The method you uses recursion to traverse the tree.
*
* @see #remove(Node root, Comparable value)
* @param root
* the root in which the method checks at the moment
* @return the minimal value of the sub-tree
*/
private Node minValue(Node root)
{
if (root.getLeft() == null)
{
return root;
}
else
{
return minValue(root.getLeft());
}
}
/**
* The method removes the left most node of the tree, used by method remove
*
* @see #remove(Comparable)
* @param root
* the node in which the method checks at the moment
*/
private void removeMin(Node root)
{
Node current = root;
if (current.getRight().getLeft() == null)
{
current.setRight(current.getRight().getRight());
}
else
{
current = current.getRight();
do
{
if (current.getLeft().getLeft() != null)
{
current = current.getLeft();
}
else
{
current.setLeft(current.getLeft().getLeft());
break;
}
}
while (current.getLeft() != null);
}
}
/**
* The method find the ancestor of the node it receives.
* <p>
* Throws exception {@link ItemNotFoundException} if the Node is not found.
*
* @param root
* the root for which the ancestor will be searched
* @return returns null if the input node is the root. If not found throws
* exception, else returns reference to the ancestor
* @throws ItemNotFoundException
*/
private Node findPredecessor(Node root) throws ItemNotFoundException
{
if (this.root.getData() == root.getData())
{
return null;
}
Node current = this.root;
while (current != null)
{
if (current.getLeft() != null)
{
if (current.getLeft() == root)
{
return current;
}
}
if (current.getRight() == root)
{
return current;
}
if (root.getData().compareTo(current.getData()) <= 0)
{
current = current.getLeft();
}
else
{
current = current.getRight();
}
}
throw new ItemNotFoundException("The item doesn't exists in the tree.");
}
/**
* Remove method.
* <p>
* The method uses recursion to traverse the tree, find the node to delete
* it.
* <p>
* If the item is not found a {@link ItemNotFoundException} will be thrown.
* The method has a "wrapper" for the user use.
*
* @param root
* the current root to search in
* @param value
* the value to delete from the tree
* @throws ItemNotFoundException
*/
private void remove(Node root, Comparable value)
throws ItemNotFoundException
{
if (root != null)
{
// looking for the value to delete
if (root.getData().compareTo(value) > 0)
{
remove(root.getLeft(), value);
}
else if (root.getData().compareTo(value) < 0)
{
remove(root.getRight(), value);
}
// value found - check if has 2 branches
else if (root.getRight() != null && root.getLeft() != null)
{
Node min = minValue(root.getRight());
removeMin(root);
min.setLeft(root.getLeft());
min.setRight(root.getRight());
Node ancesstor = findPredecessor(root);
if (ancesstor == null)
{
this.root = min;
}
else if (root == ancesstor.getLeft())
{
ancesstor.setLeft(min);
}
else
{
ancesstor.setRight(min);
}
}
else
// if has 1 branch or less
{
// find the father of the node to delete
Node ancesstor = findPredecessor(root);
// if the value to delete is in the root
if (ancesstor == null)
{
if (root.getRight() != null || root.getLeft() != null)
{
this.setRoot(root.getLeft() == null ? root.getRight()
: root.getLeft());
}
else
{
throw new ItemNotFoundException(
"Can not remove the only node in the tree. "
+ "A binary search tree must have at least 1 value.");
}
} // if not root check if the father's left is not null
else if (ancesstor.getLeft() != null)
{ // if not null, check if to be deleted
if (ancesstor.getLeft().getData().compareTo(value) == 0)
{ // if to be deleted replace the target by the son
ancesstor.setLeft(root.getRight() == null ? root
.getLeft() : root.getRight());
}
else
{
ancesstor.setRight(root.getRight() == null ? root
.getLeft() : root.getRight());
}
}
else
{
if (ancesstor.getRight().getData().compareTo(value) == 0)
{ // if to be deleted replace the target by the son
ancesstor.setRight(root.getRight() == null ? root
.getLeft() : root.getRight());
}
else
{
ancesstor.setLeft(root.getRight() == null ? root
.getLeft() : root.getRight());
}
}
}
}
else
{
throw new ItemNotFoundException(value.toString()
+ " not found in the tree.");
}
}
/**
* In-order traversal of the tree, using recursion algorithm.
* <p>
* The method has "wrapper" for the user use
*
* @param root
* the current root to work on
*/
private void inorderRecursive(Node root)
{
if (root != null)
{
inorderRecursive(root.getLeft());
System.out.print(root.getData() + " ");
inorderRecursive(root.getRight());
}
}
private Node search(Node root, Comparable value)
{
if (root == null || root.getData().compareTo(value) == 0)
{
return root;
}
else if (root.getData().compareTo(value) == 1)
{
return search(root.getLeft(), value);
}
else
{
return search(root.getRight(), value);
}
}
/**
* Insertion method.
* <p>
*
* This method inserts a value to the current tree, keeping the laws of the
* binary search tree structure. For more information on binary search trees
* {code http://en.wikipedia.org/wiki/Binary_search_tree}.
* <p>
* The method does not allow insertion of duplicate values, and will throw a
* DuplicateItemException if one is added. The method uses recursion to
* traverse the tree until it finds the place to put the new item in.
* <p>
* This method is private, and has a "wrapper" for the user to use.
*
* @param root
* the root which is checked for insertion
* @param value
* the value to insert to the tree
* @throws DuplicateItemException
*/
protected void insert(Node root, Node value)
{
if (root.compareTo(value) >= 0)
{
if (root.getLeft() == null) // if the son to the left is null
{
root.setLeft(value); // create new one with the value
}
else
{
insert(root.getLeft(), value); // otherwise call insert to left
}
}
else if (root.compareTo(value) <= -1)
{
if (root.getRight() == null) // if the son to the left is null
root.setRight(value); // create new one with value
else
insert(root.getRight(), value); // call insert to right
}
else
{
throw new DuplicateItemException(value.getData().toString());
}
}
/* Public methods */
/**
* This method is a wrapper for the inner insert method.
*
* @see #insert(Node, Node)
* @param value
* the value to insert to the tree.
* @throws DuplicateItemException
*/
public void insert(Comparable value) throws DuplicateItemException
{
insert(root, new Node(value));
}
/**
* this method is a wrapper for the remove method.
*
* @see #remove(Node, Comparable)
* @param value
* the value to remove from the tree
* @throws ItemNotFoundException
*/
public void remove(Comparable value) throws ItemNotFoundException
{
remove(root, value);
}
/**
* this is a same method that runs the remove method in a try-catch block
* and deals with errors.
*
* @param value
* the value to delete from the tree.
*/
public void tryRemove(Comparable value)
{
try
{
remove(this.root, value);
}
catch (ItemNotFoundException ex)
{
System.out.println(ex.getMessage());
}
catch (NullPointerException ex)
{
System.out.println(ex.getStackTrace().toString());
}
catch (Exception e)
{
System.out.println(e.getStackTrace().toString());
}
}
/**
* This is the wrapper method for the search method.
*
* @see #search(Node, Comparable)
* @param value
* @return true if value is found, else false.
*/
public boolean search(Comparable value)
{
return search(root, value) == null ? false : true;
}
/**
* this is a wrapper method for the recursive method inorderSearch
*
* @see #inorderRecursive(Node)
*/
public void inorderRecursive()
{
inorderRecursive(root);
}
/**
* This is an iterative version to the in-order traversal.
*/
public void inorderIterative()
{
Node current = this.root;
Stack<Node> stack = new Stack<Node>();
while (stack.isEmpty() == false || current != null)
{
if (current != null)
{
stack.push(current);
current = current.getLeft();
}
else
{
current = stack.pop();
System.out.print(current.getData() + " ");
current = current.getRight();
}
}
}
/**
* toString method
* <p>
* Prints the whole tree by printing the nodes in a in-order manner
*/
public String toString()
{
Node current = this.root;
Stack<Node> stack = new Stack<Node>();
String resultString = "";
while (stack.isEmpty() == false || current != null)
{
if (current != null)
{
stack.push(current);
current = current.getLeft();
}
else
{
current = stack.pop();
resultString += current.toString();
current = current.getRight();
}
}
return resultString;
}
/**
* Equality method
* <p>
* Method checks if 2 trees are equal, using the toString methods of each of
* the trees
*
* @param other
* @return true if the trees are equal, false otherwise
*/
public boolean equals(BinarySearchTree other)
{
return this.toString().equals(other.toString());
}
}