二叉树是一种常见并且实用的数据结构,它结合了有序数组和链表的优点,查找数据时和数组一样快,添加和删除数据时像链表一样高效。
二叉排序树又称二叉查找树,它要么是一棵空树,要么是具有以下特征的树:
- 若它的左子树不空,那么它的左子树的所有结点的值均小于根结点的值;
- 若它的右子树不空,那么它的右子树的所有结点的值均大于跟结点的值;
- 左子树与右子树也分别为二叉排序树。
这就满足,一个结点的左孩子以及这个结点的右孩子都是比该结点的双亲结点的数值小,一个结点的左孩子以及该结点的右孩子都比该结点的双亲结点大。之所以能实现这样的存储结构是因为每插入一个结点都是从根结点开始遍历比较的。
1.二叉排序树的结点实现
二叉排序树的结点由三部分构成,结点本身存在的数据值,结点的左孩子以及右孩子,很显然,结点的孩子也应该为结点类型。结点的实现代码如下:
class BiNode{ int data; BiNode lchild; BiNode rchild; public BiNode(int data) { this.data = data; } @Override public String toString() { return "BiNode [data=" + data + ", lchild=" + lchild + ", rchild=" + rchild + "]"; } }
2.向初始化的二叉排序树中插入结点
初始化的二叉排序树中只有一个BiNode类型的根结点root,且初始化的根结点为空结点,通过insert方法向其中插入结点,insert(BiTree bt,int data)方法实现的思路为:
- 实例化一个要插入的结点newNode;
- 判断根结点是否为空,若为空,则将根结点指向要插入的结点,即要插入的结点作为该树的根结点;
- 若根结点不为空,判断data和根结点值的大小,data值小于根结点值,新结点作为根结点的左孩子插入,否则作为根结点的右孩子插入;
- 每插入一个结点,都是从根结点开始遍历比较的,按上述比较方式进行结点的插入。
代码实现:
public static void insert(BiTree bt,int data){ BiNode newNode=new BiNode(data); if(bt.root==null){ bt.root=newNode; }else{ BiNode current=bt.root; BiNode parent; while(true){ parent=current; if(data<current.data){ current=current.lchild; if(current==null){ parent.lchild=newNode; return; } }else{ current=current.rchild; if(current==null){ parent.rchild=newNode; return; } } } } }
代码中在插入非根结点时,首先声明了一个结点类型的current引用,每进行一次结点的比较,current引用指向当前结点的左孩子或右孩子,下一次循环开始,将current指向的结点作为子树的根结点。
3.二叉排序树的查找结点操作
3.1 普通方法实现的查找
查找与插入的实现思路相似,关键要理解不管是查找还是插入,每进行一个结点的查找或插入,都是从根结点开始遍历的。
实现代码:
public static void search(BiTree bt,int key){ if(bt.root==null){ System.out.println("先创建一颗二叉树"); }else{ BiNode current=bt.root; while(true){ if(key<current.data){ if(current.lchild!=null){ current=current.lchild; }else{ System.out.println("找不到"); return; } }else if(key>current.data){ if(current.rchild!=null){ current=current.rchild; }else{ System.out.println("找不到"); return; } //等于情况 }else{ System.out.println("查找到数据:"+current.data); return; } } }
3.2 递归实现的查找
递归函数就是直接调用自己或通过一系列的调用语句间接地调用自己的函数,使用递归能使代码更清晰简洁容易读懂。但是注意递归跟循环一样,必须有一个终止条件,否则会无休止地执行下去。下面代码中递归结束的条件就是判断当前结点的左孩子或右孩子是否是空结点,若为空结点,查找过程中data值和结点值的比较过程结束。
public static void recursionSearch(BiNode bt,int key){ if(bt==null){ System.out.println("先创建一棵排序二叉树"); }else{ BiNode current=bt; if(key<current.data){ if(current.lchild!=null){ current=current.lchild; recursionSearch(current,key); }else{ System.out.println("找不到"); } }else if(key>current.data){ if(current.rchild!=null){ current=current.rchild; recursionSearch(current,key); }else{ System.out.println("找不到"); } }else{ System.out.println("查找成功:"+current.data); } } }
4.二叉排序树查找某一个结点双亲的操作
查找一个结点的双亲结点的操作与查找一个结点的实现方式相似,只不过在遍历的时候每次current=current.lchild;或(current=current.lchild;)之前先判断该结点的左孩子或右孩子的值是否等于该结点的值,若等于则为该结点的双亲结点。
public static BiNode searchParent(BiTree bt,int key){ BiNode current = null; BiNode parent=null; if(bt.root==null){ System.out.println("先创建一棵二叉树"); }else{ current=bt.root; while(true){ if(key<current.data){ if(current.lchild!=null){ if(current.lchild.data==key){ parent=current; //System.out.println("双亲结点为:"+parent); return parent; }else{ current=current.lchild; } }else{ return null; } }else if(key>current.data){ if(current.rchild!=null){ if(current.rchild.data==key){ parent=current; //System.out.println("双亲结点为:"+parent); return parent; }else{ current=current.rchild; } }else{ return null; } }else{ System.out.println("没有双亲结点"); return null; } } } return parent; }
5.二叉排序树删除结点并返回删除结点之后的树操作
删除结点操作比查找,插入都麻烦,因为删除了一个结点,这棵树可能不满足二叉排序树的特点了,所以删除之前首先应该判断该结点的类型。
- 如果删除的是叶子结点,删就删了,它后边也没有其它结点了,很容易实现。
- 如果删除的结点只有左子树或只有右子树,那么就需要将该结点删除之后左子树或右子树整个移到删除结点的位置。这个很容易想通,如果待删除结点是其双亲结点的右孩子,那么该删除结点的左孩子和右孩子的值都是比它的双亲结点大的,直接将该删除结点的左子树或右子树移到删除结点的位置,依旧满足二叉排序树的特点。如果待删除结点是其双亲结点的左孩子,同理,该删除结点的左孩子和右孩子的值都是比它的双亲结点小的。
- 假设删除的结点既有左子树又有右子树,此时待删除结点的位置就需要用中序遍历后它的前驱结点来替换,然后在前驱结点续接待删除结点的左子树和右子树。那么前驱结点原来所在的位置如何处理呢?先来看如何寻找前驱结点:先找到待删除结点的左孩子p,再找p结点的右孩子的右孩子的右孩子...,直到为叶子结点,可以看出前驱结点没有右孩子。所以如果前驱结点有左子树,左子树直接上移到前驱结点的位置,如果没有,直接将前驱结点所在的位置结点赋值为null。
下面代码中待删除结点是叶子结点或仅有左子树或仅有右子树的情况,如果想要在删除结点操作后返回删除结点之后的树,直接修改完待删除结点及待删除结点的左右子树是不能返回删除结点之后的树的,这就需要先找到待删除结点的双亲,判断待删除结点是左孩子还是右孩子,然后在双亲结点的左孩子或右孩子位置续接修改之后的结点。这个实现使用currentParent(BiTree bt,BiNode current,int key,BiNode childBitree)方法,参数中current是查找到的待删除结点,childBitree是待删除结点的左孩子或右孩子(如果有的话),没有传null。
public static void deleteSearchNode(BiTree bt,int key){ //一.查找到要删除的结点 BiNode current=search(bt,key); BiNode deleteNode=current; System.out.println("查找到的要删除的结点为:"+current); BiNode pre=null; //二.判断删除的结点是:叶子结点 if((current.lchild==null)&&(current.rchild==null)){ currentParent(bt,current,key,null); //仅有右子树 }else if((current.lchild==null)&&(current.rchild!=null)){ BiNode rBitree=current.rchild; currentParent(bt,current,key,rBitree); //仅有左子树 }else if((current.lchild!=null)&&(current.rchild==null)){ BiNode lBitree=current.lchild; currentParent(bt,current,key,lBitree); //既有左子树又有右子树 }else if((current.lchild!=null)&&(current.rchild!=null)){ //1.找到current的直接前驱,方法为:找到current的左孩子p,再找p结点的右孩子的右孩子...直到叶子结点 BiNode p=current.lchild; while(p.rchild!=null){ p=p.rchild; } //当p.rchild==null时,p为node的直接前驱;前驱结点pre指向p pre=p; System.out.println("直接前驱是:"+p); //2.如果直接前驱有左孩子,左孩子移至直接前驱的位置,并且释放左孩子,否则释放掉直接前驱 if(p.lchild!=null){ p=p.lchild; p.lchild=null; }else{ p=null; } //3.返回current找到直接前驱修改之后的树:下面用C表示-->先找到直接前驱的双亲结点 //这一步用了很长时间,要在树上改,直接改要删除的结点是无效的,所以要先找到要释放的结点的双亲结点,给双亲结点的左孩子或右孩子赋值为null。 if(current.lchild==pre){ current.lchild=null; }else{ BiNode node=current.lchild.rchild; BiNode parent; if(node.rchild!=null){ while((node.rchild.data)!=(pre.data)){ node=node.rchild; } parent=node; parent.rchild=null; }else{ parent=current.lchild; parent.rchild=null; } } System.out.println("C:"+current); //4.删除结点deleteNode,将前驱结点的值赋值给deleteNode的data,并在deleteNode上续接current的左子树和右子树 deleteNode.data=pre.data; deleteNode.lchild=current.lchild; deleteNode.rchild=current.rchild; System.out.println("删除结点之后的树:"+bt); } } public static BiTree currentParent(BiTree bt,BiNode current,int key,BiNode childBitree){ //右子树整个移动到删除结点的位置 current=bt.root; BiNode parent=null; while(true){ if(key<current.data){ if(current.lchild!=null){ if(current.lchild.data==key){ parent=current; parent.lchild=childBitree; System.out.println("删除结点之后的树:"+bt); //System.out.println("双亲结点为:"+parent); return bt; }else{ current=current.lchild; } }else{ return null; } }else if(key>current.data){ if(current.rchild!=null){ if(current.rchild.data==key){ parent=current; parent.rchild=childBitree; System.out.println("删除结点之后的树:"+bt); //System.out.println("双亲结点为:"+parent); return bt; }else{ current=current.rchild; } }else{ return null; } }else{ System.out.println("没有双亲结点"); return null; } } }
测试一下。
待删除结点是叶子结点:
未删除之前的树:BiTree [root=BiNode [data=62, lchild=BiNode [data=58, lchild=BiNode [data=47, lchild=BiNode [data=35, lchild=null, rchild=BiNode [data=37, lchild=null, rchild=null]], rchild=BiNode [data=51, lchild=null, rchild=null]], rchild=null], rchild=BiNode [data=88, lchild=BiNode [data=73, lchild=null, rchild=null], rchild=BiNode [data=99, lchild=BiNode [data=93, lchild=null, rchild=null], rchild=null]]]] 查找到的要删除的结点为:BiNode [data=37, lchild=null, rchild=null] 删除结点之后的树:BiTree [root=BiNode [data=62, lchild=BiNode [data=58, lchild=BiNode [data=47, lchild=BiNode [data=35, lchild=null, rchild=null], rchild=BiNode [data=51, lchild=null, rchild=null]], rchild=null], rchild=BiNode [data=88, lchild=BiNode [data=73, lchild=null, rchild=null], rchild=BiNode [data=99, lchild=BiNode [data=93, lchild=null, rchild=null], rchild=null]]]]
待删除结点仅有左子树:
未删除之前的树:BiTree [root=BiNode [data=62, lchild=BiNode [data=58, lchild=BiNode [data=47, lchild=BiNode [data=35, lchild=null, rchild=BiNode [data=37, lchild=null, rchild=null]], rchild=BiNode [data=51, lchild=null, rchild=null]], rchild=null], rchild=BiNode [data=88, lchild=BiNode [data=73, lchild=null, rchild=null], rchild=BiNode [data=99, lchild=BiNode [data=93, lchild=null, rchild=null], rchild=null]]]] 查找到的要删除的结点为:BiNode [data=99, lchild=BiNode [data=93, lchild=null, rchild=null], rchild=null] 删除结点之后的树:BiTree [root=BiNode [data=62, lchild=BiNode [data=58, lchild=BiNode [data=47, lchild=BiNode [data=35, lchild=null, rchild=BiNode [data=37, lchild=null, rchild=null]], rchild=BiNode [data=51, lchild=null, rchild=null]], rchild=null], rchild=BiNode [data=88, lchild=BiNode [data=73, lchild=null, rchild=null], rchild=BiNode [data=93, lchild=null, rchild=null]]]]
待删除结点仅有右子树:
未删除之前的树:BiTree [root=BiNode [data=62, lchild=BiNode [data=58, lchild=BiNode [data=47, lchild=BiNode [data=35, lchild=null, rchild=BiNode [data=37, lchild=null, rchild=null]], rchild=BiNode [data=51, lchild=null, rchild=null]], rchild=null], rchild=BiNode [data=88, lchild=BiNode [data=73, lchild=null, rchild=null], rchild=BiNode [data=99, lchild=BiNode [data=93, lchild=null, rchild=null], rchild=null]]]] 查找到的要删除的结点为:BiNode [data=35, lchild=null, rchild=BiNode [data=37, lchild=null, rchild=null]] 删除结点之后的树:BiTree [root=BiNode [data=62, lchild=BiNode [data=58, lchild=BiNode [data=47, lchild=BiNode [data=37, lchild=null, rchild=null], rchild=BiNode [data=51, lchild=null, rchild=null]], rchild=null], rchild=BiNode [data=88, lchild=BiNode [data=73, lchild=null, rchild=null], rchild=BiNode [data=99, lchild=BiNode [data=93, lchild=null, rchild=null], rchild=null]]]]
待删除结点既有左子树又有右子树:
未删除之前的树:BiTree [root=BiNode [data=62, lchild=BiNode [data=58, lchild=BiNode [data=47, lchild=BiNode [data=35, lchild=null, rchild=BiNode [data=37, lchild=null, rchild=null]], rchild=BiNode [data=51, lchild=null, rchild=null]], rchild=null], rchild=BiNode [data=88, lchild=BiNode [data=73, lchild=null, rchild=null], rchild=BiNode [data=99, lchild=BiNode [data=93, lchild=null, rchild=null], rchild=null]]]] 查找到的要删除的结点为:BiNode [data=47, lchild=BiNode [data=35, lchild=null, rchild=BiNode [data=37, lchild=null, rchild=null]], rchild=BiNode [data=51, lchild=null, rchild=null]] 直接前驱是:BiNode [data=37, lchild=null, rchild=null] C:BiNode [data=47, lchild=BiNode [data=35, lchild=null, rchild=null], rchild=BiNode [data=51, lchild=null, rchild=null]] 删除结点之后的树:BiTree [root=BiNode [data=62, lchild=BiNode [data=58, lchild=BiNode [data=37, lchild=BiNode [data=35, lchild=null, rchild=null], rchild=BiNode [data=51, lchild=null, rchild=null]], rchild=null], rchild=BiNode [data=88, lchild=BiNode [data=73, lchild=null, rchild=null], rchild=BiNode [data=99, lchild=BiNode [data=93, lchild=null, rchild=null], rchild=null]]]]
6.完整的代码实现
package com.java.Search; public class SearchBSTTest{ public static void main(String[] args){ BiTree bt=new BiTree(); BiTree.insert(bt,62); BiTree.insert(bt,88); BiTree.insert(bt,58); BiTree.insert(bt,47); BiTree.insert(bt,35); BiTree.insert(bt,73); BiTree.insert(bt,51); BiTree.insert(bt,99); BiTree.insert(bt,37); BiTree.insert(bt,93); System.out.println("未删除之前的树:"+bt); SearchBST.deleteSearchNode(bt,47); } } class SearchBST { /* * 1.删除的结点是叶子结点: * 2.该结点仅有左子树或仅有右子树: * 3.该结点既有左子树又有右子树: * */ public static void deleteSearchNode(BiTree bt,int key){ //一.查找到要删除的结点 BiNode current=search(bt,key); BiNode deleteNode=current; System.out.println("查找到的要删除的结点为:"+current); BiNode pre=null; //二.判断删除的结点是:叶子结点 if((current.lchild==null)&&(current.rchild==null)){ //在树上查找到该结点并将该结点赋值为null currentParent(bt,current,key,null); //仅有右子树 }else if((current.lchild==null)&&(current.rchild!=null)){ BiNode rBitree=current.rchild; currentParent(bt,current,key,rBitree); //仅有左子树 }else if((current.lchild!=null)&&(current.rchild==null)){ //左子树整个移动到删除结点的位置 BiNode lBitree=current.lchild; currentParent(bt,current,key,lBitree); //既有左子树又有右子树 }else if((current.lchild!=null)&&(current.rchild!=null)){ //1.找到current的直接前驱,方法为:找到node的左孩子p,再找p结点的右孩子的右孩子...直到叶子结点 BiNode p=current.lchild; while(p.rchild!=null){ p=p.rchild; } //当p.rchild==null时,p为node的直接前驱;前驱结点pre指向p pre=p; System.out.println("直接前驱是:"+p); //2.如果直接前驱有左孩子,左孩子移至直接前驱的位置,并且释放左孩子,否则释放掉直接前驱 if(p.lchild!=null){ p=p.lchild; p.lchild=null; }else{ p=null; } //3.返回current找到直接前驱修改之后的树:下面用C表示-->先找到直接前驱的双亲结点 //这一步用了很长时间,要在树上改,直接改要删除的结点是无效的,所以要先找到要释放的结点的双亲结点,给双亲结点的左孩子或右孩子赋值为null。 if(current.lchild==pre){ current.lchild=null; }else{ BiNode node=current.lchild.rchild; BiNode parent; if(node.rchild!=null){ while((node.rchild.data)!=(pre.data)){ node=node.rchild; } parent=node; parent.rchild=null; }else{ parent=current.lchild; parent.rchild=null; } } System.out.println("C:"+current); //4.删除deleteNode,将前驱结点的值赋值给deleteNode的data,并在deleteNode上续接current的左子树和右子树 deleteNode.data=pre.data; deleteNode.lchild=current.lchild; deleteNode.rchild=current.rchild; System.out.println("删除结点之后的树:"+bt); } } //在删除结点是叶子结点,仅有左子树,仅有右子树的结点时,在树上修改删除返回修改后的树 public static BiTree currentParent(BiTree bt,BiNode current,int key,BiNode childBitree){ //右子树整个移动到删除结点的位置 current=bt.root; BiNode parent=null; while(true){ if(key<current.data){ if(current.lchild!=null){ if(current.lchild.data==key){ parent=current; parent.lchild=childBitree; System.out.println("删除结点之后的树:"+bt); //System.out.println("双亲结点为:"+parent); return bt; }else{ current=current.lchild; } }else{ return null; } }else if(key>current.data){ if(current.rchild!=null){ if(current.rchild.data==key){ parent=current; parent.rchild=childBitree; System.out.println("删除结点之后的树:"+bt); //System.out.println("双亲结点为:"+parent); return bt; }else{ current=current.rchild; } }else{ return null; } }else{ System.out.println("没有双亲结点"); return null; } } } //查找一个结点的双亲结点 public static BiNode searchParent(BiTree bt,int key){ BiNode current = null; BiNode parent=null; if(bt.root==null){ System.out.println("先创建一棵二叉树"); }else{ current=bt.root; while(true){ if(key<current.data){ if(current.lchild!=null){ if(current.lchild.data==key){ parent=current; //System.out.println("双亲结点为:"+parent); return parent; }else{ current=current.lchild; } }else{ return null; } }else if(key>current.data){ if(current.rchild!=null){ if(current.rchild.data==key){ parent=current; //System.out.println("双亲结点为:"+parent); return parent; }else{ current=current.rchild; } }else{ return null; } }else{ System.out.println("没有双亲结点"); return null; } } } return parent; } //二叉排序树查找,返回该结点 public static BiNode search(BiTree bt,int key){ BiNode current = null; if(bt.root==null){ System.out.println("先创建一棵二叉树"); }else{ current=bt.root; while(true){ if(key<current.data){ if(current.lchild!=null){ current=current.lchild; }else{ System.out.println("找不到"); return null; } }else if(key>current.data){ if(current.rchild!=null){ current=current.rchild; }else{ System.out.println("找不到"); return null; } //等于情况 }else{ //System.out.println("查找到数据:"+current.data); return current; } } } return current; } //二叉排序树递归查找 public static void recursionSearch(BiNode bt,int key){ if(bt==null){ System.out.println("先创建一棵排序二叉树"); }else{ BiNode current=bt; if(key<current.data){ if(current.lchild!=null){ current=current.lchild; recursionSearch(current,key); }else{ System.out.println("找不到"); } }else if(key>current.data){ if(current.rchild!=null){ current=current.rchild; recursionSearch(current,key); }else{ System.out.println("找不到"); } }else{ System.out.println("查找成功:"+current.data); } } } } //二叉排序树实现 class BiTree{ BiNode root; public BiTree(){ root=null; } @Override public String toString() { return "BiTree [root=" + root + "]"; } //向BiTree中插入数据 public static void insert(BiTree bt,int data){ BiNode newNode=new BiNode(data); if(bt.root==null){ bt.root=newNode; }else{ BiNode current=bt.root; BiNode parent; while(true){ parent=current; if(data<current.data){ current=current.lchild; if(current==null){ parent.lchild=newNode; return; } }else{ current=current.rchild; if(current==null){ parent.rchild=newNode; return; } } } } } } //二叉排序树结点实现 class BiNode{ int data; BiNode lchild; BiNode rchild; public BiNode(int data) { this.data = data; } @Override public String toString() { return "BiNode [data=" + data + ", lchild=" + lchild + ", rchild=" + rchild + "]"; } }