首页 > 代码库 > 二叉平衡树之删除节点

二叉平衡树之删除节点

二叉平衡树之删除节点操作

更好的判断最小非平衡树类型的方法

在前一篇文章中,我们知道最小非平衡树可以分为四种类型,即:LL型、LR型、RR型和RL型。而且我也按照自己的理解,归纳了判断是哪种类型的方法。总结一下就是:设最小非平衡树的树根为unbalance,首先看unbalance的左右子树谁更高,如果左子树更高则为LX型、如果是右子树高则为RX型。再进一步,如果为LX型,将刚刚插入的节点的值value与unbalance左孩子进行比较,如果value大则为LR型,如果value小则为LL型。如果为RX型,将value与unbalance右孩子进行比较,如果value大则为RR型,如果value小则为RL型。

但是这种判断类型的方法实际上存在不足之处,就是它是适用于插入节点导致二叉树失衡的情形,因为第二步中要用到刚刚插入的节点的值。如果是删除节点导致的失衡就不能用这种方法判断了,因为根本没有value。

我在思考删除节点导致失衡的情形时,发现我上面总结的判断类型的方法实际上没有总结到根源上

其实我们调整树的结构根本目的是为了降低左右子树的高度差,也就是说调整的目的在于调整左右子树的高度(当然在调整高度的同时要保证依然是一颗二叉查找树)。实际上最小非平衡树的四种类型,实际上就是对左右子树高度的一个描述、一个分类

比如LL型

第一个L表示:unbalance的左子树比右子树高。第二个L表示unbalance的左孩子的左子树比右子树高。

 

LR型:

L表示:unbalance的左子树比右子树高。R表示unbalance的左孩子的右子树比左子树高。

 

RL型

R表示:unbalance的右子树比左子树高。L表示unbalance的右孩子的左子树比右子树高。

 

RR型:

第一个R表示:unbalance的右子树比左子树高。第二个R表示unbalance的右孩子的右子树比左子树高。

 

所以总结一下就是,第一个字母(L或R)描述的是unbalance(也就是根节点)的左右子树哪个更高,而第二个字母(L或R)描述的是unbalance的孩子(如果第一个是L则为左孩子,反之为右孩子)的左右子树哪个更高

这样,添加节点和删除节点导致的不平衡问题,对最小非平衡子树的类型判断就可以统一起来了!!!!!

另外需要指出,对于添加节点过程,并不存在,最小非平衡子树的左右子树等高的情况。比如下图:

 技术分享

 

最小非平衡子树的树根为12,但是8节点的左右子树高度相等,这才插入节点操作中是不可能产生的,因为刚刚插入的节点只可能是7或10,但是无论哪个是刚刚插入的节点,在它插入之前就已经不平衡了。比如10是刚刚插入的节点,可以看到,在插入10之前,12就已经不平衡了。

但是这种情况却可能在删除节点的过程中出现,比如下图:

 技术分享

 

树本来是平衡的,但是删除了14后,12就不平衡了,而这个时候8节点的左右子树等高。这种情形也是LL型。所以对上面类型的分类,还需要加入unbalance的孩子的左右子树等高的情况,添加之后就是:

LL型:

第一个L表示:unbalance的左子树比右子树高。第二个L表示unbalance的左孩子的左子树比右子树高,或者unbalance的左孩子的左右子树等高

 

LR型:

L表示:unbalance的左子树比右子树高。R表示unbalance的左孩子的右子树比左子树高。

 

RL型:

R表示:unbalance的右子树比左子树高。L表示unbalance的右孩子的左子树比右子树高。

 

RR型:

第一个R表示:unbalance的右子树比左子树高。第二个R表示unbalance的右孩子的右子树比左子树高,或者unbalance的右孩子的左右子树等高

 

理解了上面,下面开始讲删除节点操作

 

节点删除操作

对于二叉平衡树的删除节点操作,可以分成两步:

1、         删除节点,并且保证依然是一棵二叉排序树

2、         对二叉平衡树进行调整,保证是一棵平衡二叉树

 

对于第二步,在将删除节点和插入节点导致的非平衡问题中,最小非平衡子树类型的判断方法进行了统一后,实际上,对删除节点导致失衡问题的调整和对于添加节点导致的失衡问题的调整,方法也就统一了!

所以重点在第一步,即:删除节点,并且保证依然是一棵二叉排序树

 

当删除一个节点时,该节点的孩子们怎么分配十分关键,(父亲死之前要安排他的孩子,可怜天下父母心呀!)因为要保证依然是一棵二叉排序树。

 

根据待删除节点的孩子的分布特点,可以将待删除节点分成三种类型,我们设待删除的节点为todelete:

1、         todelete没有孩子,这种情况最简单了,直接把它删了就好,因为它没有孩子要分配。

2、         todelete只有一个孩子,这种情况也比较简单,用这个唯一的孩子将todelete取代了就好,所谓子承父业

3、         todelete有两个孩子,这种情况就比较复杂了。俗话说一碗水端不平,究竟将自己的皇位传给哪个孩子呢?这可是个严肃的问题!中国古代讲究宗法制,血统很重要,按照宗法制,皇位最合法的继承者就是嫡长子!那在二叉排序树中谁是todelete的嫡长子呢?答案是todelete的后继节点,也就是比todelete大的节点中的最小的那一个!看到了吧,为什么当初起名字的时候要叫它后继节点呢。人家是要继承皇位哒!
太子继承了皇位,那太子的位置就空了,这个时候就需要有人来继承太子的位置,那个人就是原太子的右孩子。好把,我们形式化一下,设todelete的后继节点为y,y的右孩子为x(注意y是没有左孩子的,因为如果存在左孩子那他的左孩子就是后继节点,继承皇位就没他什么事儿啦!)。这个时候,如果x为NULL那么,将todelete用y的替换,然后释放原来的y空间即可。如果x存在,那么用y替换了todelete后,还要用x替换y,然后释放原来x的空间,也就是太子成为皇帝,太子的儿子成为太子,然后太子的儿子的位置就空了,空了就清理了

两张图,说明一下:

 技术分享

技术分享

 


这里要多说一句,对于单链表来说,如果用一个节点替换另一个节点,比如:用C替换B,我们可以先断开AB之间的链,然后后将A链接到C,然后断开BC之间的链,最后把B回收。当然我们也可以不断开链,而是用C的内容替换B的内容,然后回收C。对于树来说,也有这两种方法,但是采用值替换的方法更简单一下,原因嘛,是因为,单链表就一条链,而树是会分叉的,会有多条链的

 

当然还有一点需要注意,就是当后继节点y是todelete的儿子的时候,和y是todelete的孙子(或者更后的位置)时,且x为NULL的时候,编写代码时会有一点出入。看代码的时候就明白了(可能你用其他方法,可以避免这种差异,也说不定)

 

删除部分的代码:

/** * 删除指定的节点 * */void delete_node(PBNode root,int value){         //找到该节点         PBNode node  = get_node(root,value);         if(node == NULL)                  return;         //找到副节点         PBNode p_node = get_father(root,value);                 if(node->l_tree == NULL)         {                  PBNode r = node->r_tree;                  if(r == NULL)//为叶子节点的情况                  {                          //直接删除该节点即可                          if(p_node != root)//反之,说明整个二叉树就一个根节点                          {                                   if(p_node->l_tree == node)                                   {                                            p_node->l_tree = NULL;                                    }                                    if(p_node->r_tree == node)                                   {                                            p_node->r_tree = NULL;                                   }                           }                          free_node(&node);                  }                  else//只有右子树的情况,采用值替换的方法比较科学                  {                          node->l_tree = r->l_tree;                          node->r_tree = r->r_tree;                          node->value = http://www.mamicode.com/r->value;                          free_node(&r);                   }         }         else if(node->r_tree == NULL)//只有左子树的情况,采用值替换的方法比较科学         {                  PBNode l = node->l_tree;                  node->l_tree = l->l_tree;                  node->r_tree = l->r_tree;                  node->value = http://www.mamicode.com/l->value;                  free_node(&l);          }         else//既有左孩子又有右孩子,这种情况下我采用的是值交换的方式,这样比较简单,如果采用断链再重新指向的方法代码量就会加大         {                  //找到后继节点                  PBNode y = node->r_tree;                  PBNode y_p = y;//用于保存后继节点的父亲节点                  while(y->l_tree != NULL)                  {                          y_p = y;                          y = y->l_tree;                  }                   PBNode x = y->r_tree; //后继节点的右孩子                  //替换后继节点和待删除的节点                  node->value = http://www.mamicode.com/y->value;                           if(x == NULL)//后继节点没有右孩子(根据后继节点的定义可以知道,后继节点一定没有左孩子)                  {                          //后继节点没有右孩子的情况,在交换了后记节点和待删除节点的值后,直接删除后记节点即可                          if(y_p == y)//后继节点就是待删除节点的右孩子                          {                                   node->r_tree = NULL;                           }                          else                          {                                   y_p->l_tree = NULL;//后继节点是待删除节点的右子树的最左节点                          }                          free_node(&y);                  }                  else                  {                          //替换后继节点的右孩子和后继节点                          y->l_tree = x->l_tree;                          y->r_tree = x->r_tree;                          y->value = http://www.mamicode.com/x->value;                          free_node(&x);                  }         }        }

 

 

对于第二步,使二叉树平衡,就再调用一下平衡的函数就好,我已经将删除节点和添加节点后引起的不平衡问题的处理方法进行了统一。

 

最后附上源文件的链接和密码(那个bst2.c是包含删除节点操作的,而且将调整的方法进行了统一)

 链接:http://pan.baidu.com/s/1slyjF6t 密码:k752

 

 

二叉平衡树之删除节点