二叉查找树(二叉排序树)的链式实现
在二叉树里,提到过,并没有去实现插入(只有在指定位置插入),删除操作,因为对于树形结构,跟线性结构不一样,插入没有固定的位置,删除会导致树的形状发生改变。
二叉排序树是在二叉树的基础上,对结点元素增添了有序(Comparable)特性。递归的有: 左子节点 < 根节点 < 右子节点,这样插入就有了唯一的位置,删除导致树形结构的破坏也有规则来重新调整树的结构。
1,二叉查找树的接口ADT
一般来说,二叉排序树要实现下列操作:
package Tree;
public interface BinarySearchTreeADT extends BinaryTreeADT {
public void addElement(Comparable element);
public Comparable removeElement(Comparable target);
public Comparable findMin();
public Comparable findMax();
public Comparable removeMin();
public Comparable removeMax();
}
2,存储方式和构造方法
继承二叉树,为了便于实现,在二叉查找树里的结点类型里面,除left,right外再添加一个parent指针。
//private int count;
//private BinaryTreeNode root;
//只用到了两个构造函数,因为在二叉查找树里我们已经定义了顺序属性的addElement操作
public BinarySearchTree()
{
super();
}
public BinarySearchTree(Comparable element)
{
super(element);
}
继承部分参见上一篇二叉树内的实例变量和构造函数。
3,主要方法实现的分析
1)public void addElement(Comparable element)
根据插入元素的大小,从上往下,如果比当前节点小,往left走,如果大,往right走,知道element比某个结点大且这个节点的右子节点为空或者element比某个结点小,且这个结点的左子节点为空,插入即可
public void addElement(Comparable element) {//添加节点
BinaryTreeNode temp = new BinaryTreeNode(element);
if(root == null)
{
root = temp;
root.parent = null;
}
else
{
BinaryTreeNode current = root;//用current来找插入到的那个结点
boolean added = false;
while(!added)
{
if(element.compareTo(current.element) < 0)//比当前节点小
if(current.left == null)
{
current.left = temp;
current.left.parent = current;
added = true;
}else
current = current.left;
else //比当前节点大
if(current.right == null)
{
current.right = temp;
current.right.parent = current;
added = true;
}else
current = current.right;
}
}
count++;
}
2)public Comparable removeElement(Comparable target)
删除一个结点A要分3种情况
a, A既没有左子树也没有右子树,直接将A节点处置空即可,注意置空的方法:A.parent.left = null 或者 A.parent.right = null;
b, A有左子树没有右子树,或者相反,把左子树/右子树衔接到被删节点处即可
c A既有左子树也有右子树,那么需要在A的左子树上找到A的中序直接前驱(即左子树上的最右结点){或者在A的右子树上找到A的直接后继,即右子树上的最左节点,我实现的是找直接前驱},用直接前驱替代A,这时候直接前驱可能还有左子树(绝对没有右子树,它是最右结点),所以还要讨论有没有左子树的情况,有左子树的话,需要把左子树再衔接到直接前驱的位置。
另外实现上还有一些细节,下面是删除的实现:
public Comparable removeElement(Comparable target) {//删除节点
Comparable result = null;
if(!isEmpty())
{
if(root.element.equals(target))//如果要删的是根节点
{
result = (Comparable) root.element;
if(root.left == null && root.right == null)
root = null;
else if(root.left != null && root.right == null)
root = root.left;//注意这里可以直接引用赋值,因为没有右子树,root就是指向左节点了
else if(root.right != null && root.left == null)
root = root.right;
else
{
BinaryTreeNode temp = root.left;//找直接前驱
while(temp.right != null)
temp = temp.right;
root.element = temp.element;//直接前驱赋值到root
//root = temp;错 引用赋值,事实上这样root就指向了原来temp的位置
if(temp.left == null)
{
if(temp.parent != root)
temp.parent.right = null;
else root.left = null;
}
else{
temp.element = temp.left.element;
temp.left = null;
}
}
count--;
}
else//否则不是根节点,要先找到删除的节点
{
BinaryTreeNode current = root;//current来找删除的节点
boolean found = false;
if(target.compareTo(root.element) < 0)
current = root.left;
else current = root.right;
while(current != null && !found)
{
if(current.element.equals(target))//找到了要删的current
{
result = (Comparable) current.element;
found = true;//如果找到了就改变while循环的found条件,退出循环
count--;
//删除节点后的调整!!!
if(current == current.parent.left)
{
if(current.left == null && current.right == null)
current.parent.left = null;
else if(current.left !=null && current.right == null)
current.parent.left = current.left;
else if(current.right != null && current.left == null)
current.parent.left = current.right;
else
{
BinaryTreeNode temp = current.left;//找直接前驱
while(temp.right != null)
temp = temp.right;
current.element = temp.element;
if(temp.left == null)
{
if(temp.parent != current)
temp.parent.right = null;
else current.left = null;
}
else{
temp.element = temp.left.element;
temp.left = null;
}
}
}
else
{
if(current.left == null && current.right == null)
current.parent.right = null;
else if(current.left !=null && current.right == null)
current.parent.right = current.left;
else if(current.right != null && current.left == null)
current.parent.right = current.right;
else
{
BinaryTreeNode temp = current.left;//找直接前驱
while(temp.right != null)
temp = temp.right;
current.element = temp.element;
if(temp.left == null)
{
if(temp.parent != current)
temp.parent.right = null;
else current.left = null;
}
else{
temp.element = temp.left.element;
temp.left = null;
}
}
}
}
else//否则如果没有找到的话,继续往下找,在这改变while循环的current条件,继续循环
{
if(target.compareTo(current.element) < 0)
current = current.left;
else current = current.right;
}
}
if(!found)
{
System.out.println("没有找到要删除的元素");
return null;
}
}
}
return result;
}
这个实现的有点混乱,实际上把查找删除元素target和删除它写在了一个方法里,显得很臃肿,而且重复代码很多,复用性不好,不过就这样吧,也不想重写一个了,好麻烦
3)public Comparable removeMin()
实现起来思想不难,就是找到最左结点,但最左结点可能还有一个右子树,所以删除后要把右子树衔接起来,这里说一个语法问题,代码里面注释也写到了:
在删除二叉排序树上的最小值的时候,一直往左下搜寻,找到最小值的结点temp(temp肯定没有左子树了,往左到了尽头),这时temp可能有两种情况:1,没有右子树,没有右子树的时候删除temp,应该是temp.parent.left = null;而不能temp = null;. 2,还有一个右子树,应该是temp.parent.left = temp.right; 而不能是temp = temp.right;
这是因为temp在代码里写的是一个局部变量,在方法调用后就销毁了,对temp的操作,对树没有任何影响,但是可以在temp存在期间通过局部的temp来对它的父节点操作
public Comparable removeMin() {
if(isEmpty())
{
System.out.println("树为空!!!");
return null;
}
BinaryTreeNode temp = root;
while(temp.left != null)
temp = temp.left;
Comparable result = (Comparable) temp.element;
if(temp == root)
root = null;
else
{
if(temp.right == null)
temp.parent.left = null;
//temp = null;//错误 同下
else
temp.parent.left = temp.right;//不是赋值,是要把temp的右子树和temp的parent连起来
//temp = temp.right;//错误 temp是个局部变量!!!!
}
count--;
return result;
}
4,完整清单和测试
package Tree;
import java.util.Iterator;
//继承自二叉树,增添了parent指针,维护一个节点的父节点
public class BinarySearchTree extends BinaryTree implements BinarySearchTreeADT{
//private int count;
//private BinaryTreeNode root;
//只用到了两个构造函数,因为在二叉查找树里我们已经定义了顺序属性的addElement操作
public BinarySearchTree()
{
super();
}
public BinarySearchTree(Comparable element)
{
super(element);
}
//扩展的操作
public void addElement(Comparable element) {//添加节点
BinaryTreeNode temp = new BinaryTreeNode(element);
if(root == null)
{
root = temp;
root.parent = null;
}
else
{
BinaryTreeNode current = root;//用current来找插入到的那个结点
boolean added = false;
while(!added)
{
if(element.compareTo(current.element) < 0)//比当前节点小
if(current.left == null)
{
current.left = temp;
current.left.parent = current;
added = true;
}else
current = current.left;
else //比当前节点大
if(current.right == null)
{
current.right = temp;
current.right.parent = current;
added = true;
}else
current = current.right;
}
}
count++;
}
public Comparable removeElement(Comparable target) {//删除节点
Comparable result = null;
if(!isEmpty())
{
if(root.element.equals(target))//如果要删的是根节点
{
result = (Comparable) root.element;
if(root.left == null && root.right == null)
root = null;
else if(root.left != null && root.right == null)
root = root.left;//注意这里可以直接引用赋值,因为没有右子树,root就是指向左节点了
else if(root.right != null && root.left == null)
root = root.right;
else
{
BinaryTreeNode temp = root.left;//找直接前驱
while(temp.right != null)
temp = temp.right;
root.element = temp.element;//直接前驱赋值到root
//root = temp;错 引用赋值,事实上这样root就指向了原来temp的位置
if(temp.left == null)
{
if(temp.parent != root)
temp.parent.right = null;
else root.left = null;
}
else{
temp.element = temp.left.element;
temp.left = null;
}
}
count--;
}
else//否则不是根节点,要先找到删除的节点
{
BinaryTreeNode current = root;//current来找删除的节点
boolean found = false;
if(target.compareTo(root.element) < 0)
current = root.left;
else current = root.right;
while(current != null && !found)
{
if(current.element.equals(target))//找到了要删的current
{
result = (Comparable) current.element;
found = true;//如果找到了就改变while循环的found条件,退出循环
count--;
//删除节点后的调整!!!
if(current == current.parent.left)
{
if(current.left == null && current.right == null)
current.parent.left = null;
else if(current.left !=null && current.right == null)
current.parent.left = current.left;
else if(current.right != null && current.left == null)
current.parent.left = current.right;
else
{
BinaryTreeNode temp = current.left;//找直接前驱
while(temp.right != null)
temp = temp.right;
current.element = temp.element;
if(temp.left == null)
{
if(temp.parent != current)
temp.parent.right = null;
else current.left = null;
}
else{
temp.element = temp.left.element;
temp.left = null;
}
}
}
else
{
if(current.left == null && current.right == null)
current.parent.right = null;
else if(current.left !=null && current.right == null)
current.parent.right = current.left;
else if(current.right != null && current.left == null)
current.parent.right = current.right;
else
{
BinaryTreeNode temp = current.left;//找直接前驱
while(temp.right != null)
temp = temp.right;
current.element = temp.element;
if(temp.left == null)
{
if(temp.parent != current)
temp.parent.right = null;
else current.left = null;
}
else{
temp.element = temp.left.element;
temp.left = null;
}
}
}
}
else//否则如果没有找到的话,继续往下找,在这改变while循环的current条件,继续循环
{
if(target.compareTo(current.element) < 0)
current = current.left;
else current = current.right;
}
}
if(!found)
{
System.out.println("没有找到要删除的元素");
return null;
}
}
}
return result;
}
public Comparable findMax() {
if(isEmpty())
{
System.out.println("树为空!!!");
return null;
}
BinaryTreeNode temp = root;
while(temp.right != null)
temp = temp.right;
return (Comparable) temp.element;
}
public Comparable findMin() {
if(isEmpty())
{
System.out.println("树为空!!!");
return null;
}
BinaryTreeNode temp = root;
while(temp.left != null)
temp = temp.left;
return (Comparable) temp.element;
}
public Comparable removeMax() {
if(isEmpty())
{
System.out.println("树为空!!!");
return null;
}
BinaryTreeNode temp = root;
while(temp.right != null)
temp = temp.right;
Comparable result = (Comparable) temp.element;
if(temp == root)
root = null;
else
{
if(temp.left == null)
temp.parent.right = null;
else
temp.parent.right = temp.left;
//{
//temp.element = temp.left.element;
//temp.left = null;
//}
}
count--;
return result;
}
public Comparable removeMin() {
if(isEmpty())
{
System.out.println("树为空!!!");
return null;
}
BinaryTreeNode temp = root;
while(temp.left != null)
temp = temp.left;
Comparable result = (Comparable) temp.element;
if(temp == root)
root = null;
else
{
if(temp.right == null)
temp.parent.left = null;
//temp = null;//错误 同下
else
temp.parent.left = temp.right;//不是赋值,是要把temp的右子树和temp的parent连起来
//temp = temp.right;//错误 temp是个局部变量!!!!
}
count--;
return result;
}
public static void main(String[] args) {
BinarySearchTree tree = new BinarySearchTree();
//二叉排序树的形状跟插入顺序有关,中序序列总是不变(有序)
tree.addElement(10);
tree.addElement(5);
tree.addElement(3);
tree.addElement(7);
tree.addElement(6);
tree.addElement(9);
tree.addElement(8);
tree.addElement(13);
tree.addElement(11);
tree.addElement(20);
tree.addElement(25);
tree.addElement(16);
System.out.println("\n中序遍历结果为: ");
Iterator it = tree.iteratorInorder();
while(it.hasNext())
System.out.print(it.next() + " ");
System.out.println("\n前序遍历结果为: ");
it = tree.PreInorder();
while(it.hasNext())
System.out.print(it.next() + " ");
System.out.println("\n后序遍历结果为: ");
it = tree.PostInorder();
while(it.hasNext())
System.out.print(it.next() + " ");
System.out.println("\n\n" + "最小元素为: " + tree.findMin());
System.out.println("\n" + "最大元素为: " + tree.findMax());
tree.removeMin();
System.out.println("\n删除最小元素3后的前序序列: ");
it = tree.PreInorder();
while(it.hasNext())
System.out.print(it.next() + " ");
tree.removeMin();
System.out.println("\n\n接着删除最小元素5后的前序序列: ");
it = tree.PreInorder();
while(it.hasNext())
System.out.print(it.next() + " ");
//tree.removeElement(10);
//tree.removeElement(9);
//tree.removeElement(13);
//tree.removeElement(10);
//tree.removeElement(5);
//System.out.println("\n\n删除节点后前序遍历结果为: ");
//it = tree.PreInorder();
//while(it.hasNext())
//System.out.print(it.next() + " ");
}
}
在main函数里构造了如下二叉排序树:
旧金山大学计算机系弄了一个在线的可视化数据结构模拟,前几天google推荐给我的,感觉非常好,国内咋就没有这么好的东西呢,这个图是在那生成的,省去了我许多画图的麻烦,http://www.cs.usfca.edu/~galles/visualization/Algorithms.html
测试结果:
中序遍历结果为:
3 5 6 7 8 9 10 11 13 16 20 25
前序遍历结果为:
10 5 3 7 6 9 8 13 11 20 16 25
后序遍历结果为:
3 6 8 9 7 5 11 16 25 20 13 10
最小元素为: 3
最大元素为: 25
删除最小元素3后的前序序列:
10 5 7 6 9 8 13 11 20 16 25
接着删除最小元素5后的前序序列:
10 7 6 9 8 13 11 20 16 25