使二叉树成为二叉查找树的性质是,对于树中的每个节点X,它的左子树中所有项的值小于X中的项,而它的右子树中所有项的值大于X中的项。
二叉查找树要求所有的项都能够排序。要写出一个一般的类,我们需要提供一个接口来表示这个性质。这个接口就是Comparable,该接口告诉我们,树中的两项总可以使用CompareTo()方法比较。
接口Comparable和接口Comparator的区别:
public interface Comparable
此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法。
注意:已经被大多数的常用类型实现!
compareTo
int compareTo(T o)
比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
public interface Comparator
强行对某个对象 collection 进行整体排序 的比较函数。可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用 Comparator 来控制某些数据结构(如有序 set或有序映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序。
注意:仅有少数类实现了这个接口!
int compare(T o1,T o2)
比较用来排序的两个参数。根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。
1、以下是节点类BinaryNode类的实现:
private static class BinaryNode<Integer>
{
Integer element; //节点中的数据
BinaryNode<Integer> left; //节点的左孩子
BinaryNode<Integer> right; //节点的右孩子
BinaryNode(Integer theElement)
{
this(theElement,null,null);
}
BinaryNode(Integer theElement,BinaryNode<Integer> lt, BinaryNode<Integer> rt)
{
element = theElement;
left = lt;
right = rt;
}
}
2、contains方法
如果在树T中存在含有项X的节点,那么这个操作需要返回true,如果这样的节点不存在,则返回false。
如果T是空集,那么可以就返回false,否则如果存储在T处的项是X,那么可以返回true,否则,我们队树T的左子树或右子树进行一次递归调用,这依赖于X与存储在T中的项的关系。
private boolean contains(Integer x,BinaryNode t)
{
//t是根节点的引用
if(t == null)
return false;
int compareResult = x.compareTo(t.element);
//遍历左子树
if(compareResult<0)
{
return contains(x,t.left);
}
//遍历右子树
else if(compareResult>0)
{
return contains(x,t.right);
}
//找到节点
else
return true;
}
注意测试的顺序。关键的问题是首先要对是否空树进行测试,否则我们就会生成一个企图通过null引用访问数据域的NullPointerException异常。剩下的测试应该使得最不可能的情况安排在最后进行。还要注意,这里的两个递归调用事实上都是尾递归并且可以用一个while循环很容易的代替。
3、findMin方法和findMax方法
这两个private例程分别返回树中包含最小元和最大元的节点的引用。为执行findMin,从根开始并且只要有左儿子就向左进行。终止点就是最小的元素。findMax例程除了分支朝向右儿子外其余过程相同。
//使用递归实现查找
private BinaryNode findMin(BinaryNode t)
{
if(t==null)
return null;
else if(t.left==null)
return t;
return findMin(t.left);
}
//使用while循环查找
private BinaryNode findMax(BinaryNode t)
{
if(t!=null)
while(t.right!=null)
t=t.right;
return t;
}
4、insert方法
进行插入操作的例程在概念上是简单的。为了将X插入到树T中,你可以像用contains那样沿着树查找。如果找到X,则什么也不用做(或做一些更新)。否则,将X插入到遍历的路径上的最后一点上。
重复元的插入可以通过在节点记录中保留一个附加域以指示发生的频率来处理。
由于t引用该树的根,而根又在第一次插入时变化,因此insert被写成一个返回对新树根的引用的方法。
private BinaryNode<Integer> insert(Integer x,BinaryNode<Integer> t)
{
if(t==null)
return new BinaryNode<Integer>(x, null, null);
int compareResult = x.compareTo(t.element);
//在左子树进行插入,把小于某一节点的值插入到其左孩子的位置
if(compareResult<0)
t.left = insert(x,t.left);
//在右子树进行插入,把大于某一节点的值插入到其右孩子的位置
else if(compareResult>0)
t.right = insert(x,t.right);
else
; //否则什么都不做,因为要插入的节点在树中已经存在
return t;
}
5、remove方法
最困难的操作就是remove方法,因为有好几种需要考虑的情况。
1)如果节点是一片树叶,那么它可以立即被删除。
2)如果节点有一个儿子,则该节点可以在其父节点调整自己的链以绕过该节点后被删除。
2)如果节点有两个儿子,这是最复杂的情况。一般的删除策略是用其右子树的最小的数据代替该节点的数据并递归的删除那个节点。因为右子树中的最小的节点不可能有左儿子,所以第二次remove更容易。
private BinaryNode remove(Integer x,BinaryNode t)
{
if(t==null)
return t;
//先查找节点所在的位置
int compareResult = x.compareTo(t.element);
if(compareResult<0)
t.left = remove(x,t.left);
else if(compareResult>0)
t.right = remove(x,t.right);
else if(t.left!=null&&t.right!=null)
{
//找到待删除的节点右子树的最小数据节点替换它并删除
//findMin返回的是右子树最小节点的引用
t.element = findMin(t.right).element;
//替换之后将替换的节点删除
t.right = remove(t.element,t.right);
}
else
t = (t.left!=null)?t.left:t.right;
return t;
}
以下是整个程序的代码:
public class BinarySearchTree {
private BinaryNode root;
private static class BinaryNode
{
Integer element; //节点中的数据
BinaryNode left; //节点的左孩子
BinaryNode right; //节点的右孩子
BinaryNode(Integer theElement)
{
this(theElement,null,null);
}
BinaryNode(Integer theElement,BinaryNode lt, BinaryNode rt)
{
element = theElement;
left = lt;
right = rt;
}
}
public BinarySearchTree()
{
root = null;
}
public void makeEmpty()
{
root = null;
}
public boolean isEmpty()
{
return root == null;
}
public boolean contains(Integer x)
{
System.out.println(contains(x,root));
return contains(x,root);
}
public Integer findMin()
{
int x;
if(isEmpty())
System.out.println("it is a empty tree");
x = findMin(root).element;
System.out.println(x);
return x;
}
public Integer findMax()
{
int x;
if(isEmpty())
System.out.println("it is a empty tree");
x = findMax(root).element;
return x;
}
public void insert(Integer x)
{
root = insert(x,root);
}
public void remove(Integer x)
{
root = remove(x,root);
}
public void printTree()
{
if(isEmpty())
System.out.println("Empty tree");
else
printTree(root);
}
private boolean contains(Integer x,BinaryNode t)
{
if(t == null)
return false;
int compareResult = x.compareTo(t.element);
if(compareResult<0)
return contains(x,t.left);
else if(compareResult>0)
return contains(x,t.right);
else
return true; //找到节点
}
private BinaryNode findMin(BinaryNode t)
{
if(t==null)
return null;
else if(t.left==null)
return t;
return findMin(t.left);
}
private BinaryNode findMax(BinaryNode t)
{
if(t!=null)
while(t.right!=null)
t=t.right;
return t;
}
private BinaryNode insert(Integer x,BinaryNode t)
{
if(t==null)
return new BinaryNode(x, null, null);
int compareResult = x.compareTo(t.element);
if(compareResult<0)
t.left = insert(x,t.left);
else if(compareResult>0)
t.right = insert(x,t.right);
else
; //否则什么都不做,因为要插入的节点在树中已经存在
return t;
}
private BinaryNode remove(Integer x,BinaryNode t)
{
if(t==null)
return t;
int compareResult = x.compareTo(t.element);
if(compareResult<0)
t.left = remove(x,t.left);
else if(compareResult>0)
t.right = remove(x,t.right);
else if(t.left!=null&&t.right!=null)
{
t.element = findMin(t.right).element;
t.right = remove(t.element,t.right);
}
else
t = (t.left!=null)?t.left:t.right;
return t;
}
public void printTree(BinaryNode t)
{
if(t!=null)
{
printTree(t.left);
System.out.println(t.element);
printTree(t.right);
}
}
public static void main(String[] args) {
BinarySearchTree bst = new BinarySearchTree();
//插入数据
bst.insert(1);
bst.insert(4);
bst.insert(3);
bst.insert(6);
bst.insert(5);
bst.insert(8);
//打印树
bst.printTree();
//查看树是否包含9
bst.contains(9);
bst.findMin();//查找最小值
bst.findMax();//查找最大值
bst.remove(4);//删除4
bst.printTree();
bst.makeEmpty();//删除树
bst.printTree();
}
}