二叉查找树5

接上一篇,继续讲二叉查找树的操作,之前的博客都讲得差不多了,本篇就讲一下删除操作,以及求最矮公共父结点(LCA:lowest common ancestor)的操作吧。

  • 删除

  将一个结点从二叉查找树中删除之后,剩下的结点可能会不满足二叉查找树的性质,因此,在删除结点之后要对树进行调整,使其满足二叉查找树的性质。根据结点的孩子的数量,将删除操作分为三种情况,我们记要删除的结点为z,实际上删除的结点为y。

  1. z结点没有孩子。

  如下图a所示,我们要删除值为13的结点,因为结点没有孩子,所以删除之后不会影响到二叉树的整体性质,也就是说,直接将13这个结点删除即可,如图a所示,从左边的二叉树删除13这个点之后变到右边的二叉树。

   2. z结点有一个孩子。

  如下图b所示,要删除的值为16的结点有一个孩子,而且是右孩子,那么从图上来看,如果,我们将16去掉,然后把以20为结点的子树作为15的右子树,那么整棵树还是符合二叉查找树的性质的,因此,有一个孩子的结点的删除操作,就是要将其孩子作为其父结点的孩子即可。如图b所示。

   3. z结点有两个孩子。

  如下图c所示,要删除的值为5的结点,有两个孩子,删除之后肯定整棵树就不符合二叉查找树的性质了,因此要进行调整,我们发现,将5的后继,值为6的结点来放到5的位置,然后将6的孩子7作为6的父结点10的孩子,如下图c所示,我们要删除的是z结点,而我们实际要删除y结点,并替换z结点。这里需要注意的一点是,如果一个结点有右孩子,则该结点的后继,至多有一个子女,而且是右孩子。因为假如该结点的后继有左孩子和右孩子,那么其左孩子的值肯定是介于该结点和其后继之间的,那么按照二叉查找树的性质,这个左孩子就应该是该结点的后继,所以,这与原先的后继相互矛盾,因此,结论成立。

  好了,分析完了删除的三种情况,我们来完成我们的程序吧。

复制代码
 1 /**
 2      * 删除二叉查找树中的结点z
 3      * @author Alfred
 4      * @param z 要删除的结点
 5      * @return 删除或者替换的结点
 6      */
 7     public TreeNode treeDelete(TreeNode z){
 8         TreeNode x = null, y = null;
 9         if(z.getLeft() == null || z.getRight() == null){
10             //对应第1和第2种情况
11             y = z;
12         }else{
13             //对应第3种情况
14             y = treeSuccessor(z);
15         }
16         //将x置为y的非null子女,或者当y无子女时置为null
17         if(y.getLeft() != null){
18             x = y.getLeft();
19         }else{
20             x = y.getRight();
21         }
22         //通过修改x和y的父结点的引用,将y删除
23         if(x != null){
24             x.setParent(y.getParent());
25         }
26         if(y.getParent() == null){
27             //x成为树根
28             rootNode = x;
29         }else if(y == y.getParent().getLeft()){
30             //y是其父结点的左孩子
31             y.getParent().setLeft(x);
32         }else{
33             //y是其父结点的右孩子
34             y.getParent().setRight(x);
35         }
36         //如果y和z不是同一个结点,说明是第3种情况
37         if(y != z){
38             //内容替换
39             z.setKey(y.getKey());
40             z.setDataNum(y.getDataNum());
41         }
42         return y;
43     }
复制代码

  上面的程序完整的搞定了上面分析的三种情况。

  • LCA

  LCA问题对于二叉查找树来说是非常简单的。因为二叉查找树满足其特有的性质。给定树中的两个结点x和y,求其最低公共父结点。我们把这个算法分为三种情况。对于一棵二叉查找树来说:

  1. 如果x和y分别位于某结点z的左右子树上,那么结点z就是x和y的lca。

  2. 如果x和y位于某结点z的左子树或者右子树上,那么x和y的lca也必然位于其所处的左子树或者右子树上。

  3. 如果x或者y中,有一个结点是另外一个结点的父结点,那么lca就是它们中的父结点。即如果有一个点和某结点重合,则重合的点就是lca。

  那么,我们就从根结点开始,按照从上往下的顺序查找lca吧,比较根结点与两结点的关系,然后再进行下一步的操作。代码如下:

复制代码
 1 /**
 2      * 求两个结点的最低公共父结点
 3      * @author Alfred
 4      * @param x 结点
 5      * @param y 结点
 6      * @return 最低公共父结点
 7      */
 8     public TreeNode lca(TreeNode x, TreeNode y){
 9         TreeNode tmpNode = rootNode;
10         //获取两个key值
11         int xKey = x.getKey();
12         int yKey = y.getKey();
13         //给两个元素按从小到大排下序,使得x<y
14         if(xKey > yKey){
15             int tmpKey = xKey;
16             xKey = yKey;
17             yKey = tmpKey;
18         }
19         while(true){
20             if(tmpNode.getKey() < xKey){
21                 //这种情况下,lca在其右子树上
22                 tmpNode = tmpNode.getRight();
23             }else if(tmpNode.getKey() > yKey){
24                 //这种情况下,lca在其左子树上
25                 tmpNode = tmpNode.getLeft();
26             }else{
27                 return tmpNode;
28             }
29         }
30     }
复制代码

  代码比较简单,就是按照上面的三种情况按照从上到下的顺序来分类处理的。其他还有一些lca算法,如离线算法(Tarjan)和在线算法(RMQ)等,暂时先不讨论了,以后有时间了再补上吧。为了方便想学习的童鞋来测试,我把整个代码就贴到下面了(木有找到更好地方法来分享。。)。

TreeNode.java
BSTree.java
TestMain.java


  ps:关于二叉查找树的一些操作先写到这里吧,有不对的地方,请广大博友指正啊。

  pss:画图好累啊。。转载请注明。。。

 
 
标签: 算法
原文地址:https://www.cnblogs.com/Leo_wl/p/2497530.html