一、定义
二叉排序树或者是一颗空树,或者是具有以下性质的二叉树:
(1)若它的左子树不为空,则其左子树上所有结点的值均小于它的根结点的值
(2)若它的右子树不为空,则其右子树上所有结点的值均大于它的根结点的值
(3)它的左、右子树均为二叉排序树
由定义可以得出,中序遍历二叉树可以得到一个结点值递增的有序序列。
一个简单的二叉排序树:
这里使用二叉链表来作为存储结点,结点类的定义如下,与普通二叉树类似。
static final class BSNode{
int data; //值
BSNode left; //左孩子
BSNode right; //右孩子
BSNode(){
this.data = 0;
left = null;
right = null;
}
BSNode(int data){
this.data = data;
left = null;
right = null;
}
BSNode(int data,BSNode left,BSNode right){
this.data = data;
this.left = left;
this.right = right;
}
}
二、操作二叉排序树
1、二叉排序树的查找
二叉排序树的查找与折半查找类似,逐步缩小范围。
(1)若二叉树排序树为空,则查找失败,返回空
(2)若二叉排序树非空,将给定的值e与根节点的值T.data比较:
①如果e == T.data,返回T
②如果e < T.data,递归查询T的左子树
③如果e > T.data,递归查询T的右子树
代码如下:
//二叉排序树查找递归版,返回值等于e的结点
public BSNode searchBST(int e){
return searchBST(root,e); //root为树根,e为要查找的值
}
public BSNode searchBST(BSNode p,int e){
if(p!=null){
if(p.data == e)
return p;
if(e < p.data) //如果e小于
return searchBST(p.left,e);
else
return searchBST(p.right,e);
}
return null;
}
//二叉排序树查找非递归
public BSNode searchBSTN(int e){
BSNode p = root;
while(p!=null){
if(p.data == e)
return p;
else if(e < p.data)
p = p.left;
else
p = p.right;
}
return null;
}
2、二叉排序树的插入
二叉排序树的插入是以查找为基础的,假设要插入的值为e,那么从根结点往下查找,当树中不存在值等于e的结点时才插入。新插入的结点一定是一个新添加的叶子结点,并且是查找不成功时查找路径上访问的最后一个结点的左孩子或则右孩子结点。
(1)若二叉排序树为空,则待插入值e作为根的值插入到空树中。
(2)若二叉排序树非空,则将e与根节点的值T.data进行比较:
①若e < T.data,则查找其左子树
②若e > T.data,则查找其右子树
代码如下:
public void insertBST(int e){
if(root == null){ //如果树为空,则直接插入根节点
root = new Node(e,null,null);
return;
}
BSNode p = root;
while( p!= null){
if(e < p.data){ //查询左子树
if(p.left == null){ //说明p.left就是要插入的位置
p.left = new BSNode(e,null,null)
return;
}
else{
p = p.left;
}
}
else if(e > p.data){ //查询右子树
if(p.right == null){ //说明p.right就是要插入的位置
p.right = new Node(e,null,null);
return;
}
else{
p = p.right;
}
}
}
}
3、二叉排序树的创建
二叉排序树的创建是以插入为基础的,就是不断的插入。
代码如下:
public void creatBST(List<Integer> l){
for(int i=0;i < l.size(); i++){
insertBST(l.get(i));
}
}
4、二叉排序树的删除
删除要分情况讨论,假设被删除的结点是p,p的双结点为f,p的左右子树分别为pl和pr,同时假设p是f的左孩子(右孩子情况类似)。
(1)如果p是叶子结点,即pl和pr均为空树,由于直接删除p结点不会破坏整棵树的结构,则只需要修改其双亲结点。
f.left = null;
(2)如果p结点只有左子树或者只有右子树,那么只要令pl或者pr直接成为f的左子树即可(由于这里是假设的p是f的左子树,以p为根的树的所有结点的值都比f的值小)
f.left = p.left; 或f.left = p.right;
(3)如果p结点既有左子树又有右子树,有两种方法
①假设s是p的左子树中值最大的结点(即最右下的结点),可以知道s肯定没有右子树(如果有的话,那么s就不是左子树中最大的结点了),那么可以令p的右子树直接为s的右子树,令p的左子树为f的左子树。此方法容易增加树的深度。
f.left = p.left; s.right = p.right;
②使用p的直接前驱(或者直接后继)来替代p,p的直接前驱就是①中的s,由于s是p的左子树中最大的,而又比p的右子树中所有的值都小,所有用它替代p并不会破坏整棵树的结构(直接后继类似)。当以直接前驱s替代p时,由于s只有左子树sl,则在删除s之后,要令sl为s的双亲结点q的右子树即可。注意,如果s的双亲结点为p的话,那么令sl为p的左子树。
p.data = s.data; q.right = s.left;
代码如下:
public void deleteBST(int e){
BSNode p = root; //值等于e的结点
BSNode f = null; //p的双亲结点
BSNode s = null; //p的直接前驱(p的左子树中值最大的结点)
BSNode q = null; //s的双亲结点
while(p!=null){ //寻找值等于e的结点
if(p.data == e)
break;
f = p;
if(e < p.data)
p = p.left;
else
p = p.right;
}
if(p == null) return; //不存在值等于e的结点
q = p; //s的双亲结点一开始可能为p
//System.out.println("1");
//如果p的左右子树都不为空
if(p.left != null && p.right != null){
s = p.left;
while(s.right != null){ //寻找p左子树中值最大的结点,即最右下结点
q = s;
s = s.right;
}
p.data = s.data; //此时s指向被删结点的前驱
if(q != p)
q.right = s.left; //重接q的右子树
else
q.left = s.left; //重接q的左子树,如果s的双亲就为p的情况
return;
}
else if(p.left == null){ //被删结点左子树为空,只需处理右子树,这里涵盖了左右子树都为空的情况
p = p.right; //注意,如果左右子树都为空,那么p在这里会变成null
}
else if(p.right ==null){ //被删右子树为空的情况,只需处理左子树
p = p.left;
}
//System.out.println(f.data);
if(f == null) //被删结点为根
root = p;
else if( q == f.left ) //如果被删结点是其双亲的左结点
f.left = p;
else //如果被删结点是其双亲的右结点
f.right = p;
}
二叉排序树不经过优化时,查找的时间复杂度平均为O(log n),但是最坏的情况是O(n),例如插入时的序列是一个递增的序列时,二叉排序树的结构就是一个只有左子树的树,访问最大的结点需要移动n次。
完整代码:
package trees;
import java.util.*;
//二叉排序树(二叉查找树)
/*
* 定义
* 1、若它的左子树不为空,则左子树的所有结点小于根节点
* 2、若它的右子树不为空,则右子树的所有结点大于根节点
* 3、它的左、右子树均为二叉排序树
* 由其定义,中序遍历可以得到结点值的递增序列
*/
public class BSTree {
static final class BSNode{
int data;
BSNode left; //左孩子
BSNode right; //右孩子
BSNode(){
this.data = 0;
left = null;
right = null;
}
BSNode(int data){
this.data = data;
left = null;
right = null;
}
BSNode(int data,BSNode left,BSNode right){
this.data = data;
this.left = left;
this.right = right;
}
}
BSNode root;//树根
//二叉排序树查找递归版,返回值等于e的结点
public BSNode searchBST(int e){
return searchBST(root,e);
}
public BSNode searchBST(BSNode p,int e){
if(p!=null){
if(p.data == e)
return p;
if(e < p.data) //如果e小于
return searchBST(p.left,e);
else
return searchBST(p.right,e);
}
return null;
}
//二叉排序树查找非递归
public BSNode searchBSTN(int e){
BSNode p = root;
while(p!=null){
if(p.data == e)
return p;
else if(e < p.data)
p = p.left;
else
p = p.right;
}
return null;
}
public BSTree(){
root = null;
}
public void inOrder(){
inOrder(root);
}
public void inOrder(BSNode p){
if(p != null){
inOrder(p.left);
System.out.print(p.data+" ");
inOrder(p.right);
}
}
public BSTree(List<Integer> l){
createBST(l);
}
/*
* 二叉排序树插入
* 如果比根小则往左走,比根大则往右走,直到左子树或右子树为空时插入
* 这里要求树中不存在 data==e
*/
public void insertBST(BSNode p,int e){
if(root == null){
root = new BSNode(e,null,null);
return;
}
if(e > p.data){
if(p.right == null){
p.right = new BSNode(e,null,null);
return ;
}
else{
insertBST(p.right,e);
}
}
else if(e < p.data){
if(p.left == null){
p.left = new BSNode(e,null,null);
return;
}
else{
insertBST(p.left,e);
}
}
}
/*
* 二叉排序树的创建,就是不断的插入
*/
public void createBST(List<Integer> l){
for(int i=0;i<l.size();i++){
insertBST(root,l.get(i));
}
}
/*
* 二叉排序树的删除
* 假设p是要删除的结点,f是p的双亲结点,这里假设p是f的左孩子(右孩子情况类似)
* (1)如果p是叶子结点,只需要修改双亲结点即可
* f.left = null;
* (2)如果p只有左子树或只有右子树,那么只需要令它的左子树或右子树直接成为其双亲结点的左子树
* f.left = p.left;(或f.left = p.right)
* (3)如果p既有右子树又有左子树,有两种方法
* 1、令p的左子树为f的左子树,令p的右子树为p的左子树中值最大的结点的右子树,
* 假设p的左子树中值最大的结点为s,可以知道s一定没有右子树(有右子树就说明还有
* 比s大的,s就不是值最大的结点),该方法容易增加树的深度。
* f.left = p.left;s.right = p.right;
* 2、用p的直接前驱或者直接后继替代p,假设p的前驱是s,s的双亲是t(s是p的左子树中
* 值最大的结点,s替代p后依然会大于它的左子树,小于它的右子树,用后继替代同理)所
* 以只需用s替代p然后令s的左子树为t的右子树。这种方式不会增加树的高度。
* p.data = s.data;t.right = s.left;
*/
public void deleteBST(int e){
BSNode p = root; //值等于e的结点
BSNode f = null; //p的双亲结点
BSNode s = null; //p的直接前驱(p的左子树中值最大的结点)
BSNode q = null; //s的双亲结点
while(p!=null){ //寻找值等于e的结点
if(p.data == e)
break;
f = p;
if(e < p.data)
p = p.left;
else
p = p.right;
}
if(p == null) return; //不存在值等于e的结点
q = p; //s的双亲结点一开始可能为p
//System.out.println("1");
//如果p的左右子树都不为空
if(p.left != null && p.right != null){
System.out.println("2");
s = p.left;
while(s.right != null){ //寻找p左子树中值最大的结点,即最右下结点
q = s;
s = s.right;
}
p.data = s.data; //此时s指向被删结点的前驱
if(q != p)
q.right = s.left; //重接q的右子树
else
q.left = s.left; //重接q的左子树,如果s的双亲就为p的情况
return;
}
else if(p.left == null){ //被删结点左子树为空,只需处理右子树,这里涵盖了左右子树都为空的情况
//System.out.println("3");
p = p.right; //注意,如果左右子树都为空,那么p在这里会变成null
}
else if(p.right ==null){ //被删右子树为空的情况,只需处理左子树
//System.out.println("4");
p = p.left;
}
//System.out.println(f.data);
if(f == null) //被删结点为根
root = p;
else if( q == f.left ) //如果被删结点是其双亲的左结点
f.left = p;
else //如果被删结点是其双亲的右结点
f.right = p;
}
public static void main(String[] args) {
Integer[] a = new Integer[]{45,24,53,12,37,93};
ArrayList<Integer> l = new ArrayList<>(Arrays.asList(a));
BSTree bst = new BSTree(l);
/*bst.inOrder();
System.out.println();
int e = 37;
if(bst.searchBSTN(e)!=null){
System.out.println(bst.searchBSTN(e).data);
}
if(bst.searchBST(e)!=null){
System.out.println(bst.searchBST(e).data);
}
*/
bst.deleteBST(24);
bst.inOrder();
}
}