数据结构(十五)二叉树 —— 平衡二叉树

在前面的章节已经有总结过,在某些特定的情况下,二叉排序树是有可能退化成单链表的。也就是说,如果一个二叉排序树对已经有序的序列进行排序,那么其中的结构就会退化成单链表,并且元素的查找效率也会明显的下降,那么此时我们需要一些特别的手段保证这个二叉排序树的“平衡”,进而保证查询元素的效率。

一、二叉树绝对平衡的定义

   平衡二叉树(AVL树)本身也是一种二叉排序树,其内部的元素之间同样遵从左孩子 < 根节点 <右孩子或者左孩子 > 根节点 > 右孩子的特性的。但是,在创建平衡二叉树的时候,我们要求树结构中的任何一个节点,左右子树的层数深度之间的深度之差,绝对值不超过1。举个例子:如果在某一个二叉树结构中,某一个节点的左子树的深度是5,则其右子树的深度取值范围在4­-6之间,如果超出或者小于这个范围,就不算是平衡二叉树了。那么在向这个二叉树结构中插入新节点,或者删除原有节点的时候,很可能出现导致某节点左右子树不平衡的状况发生,那么此时就需要对指定的节点进行旋转,最终达到让这个节点,乃至整个二叉树结构平衡的目的。

二、节点的旋转

   在我们向AVL树中添加节点,或者删除掉AVL树中节点的时候,难免会造成AVL树原先整体平衡的结构,在某些节点上变得不平衡。那么此时,我们就需要通过对AVL树中节点的旋转,对AVL树的节点结构进行重新调整,最终使得AVL树重新回归平衡状态。
值得庆幸的是,在添加节点或者删除节点之后,我们最多能够在4代以内的节点之间造成不平衡的关系。所以,我们可以将不平衡的状态按照不平衡节点和其叶子节点之间的深度关系,分为3代不平衡和4代不平衡两种。其中:通过添加和删除节点导致的3代不平衡的情况都比较容易理解和处理,但是一旦涉及到在4代节点之间,因为添加或者删除元素导致的不平衡的出现,就需要我们分析一些比较复杂的情况了,但是不管情况多么复杂,我们最终都是通过对节点的旋转,来完成对AVL树结构平衡的调整的而节点的旋转,根据旋转的方向和次数,又可以分为:左旋、右旋、左右旋、右左旋四种。下面,我们就来根据上面说到的这些情况,逐一讨论如何调整AVL树结构的平衡性

1、平衡二叉树中的相关概念

  • 插入节点:指的是通过添加操作,新加入AVL树结构中的节点,在向AVL树中添加阶段的时候不一定会引起结构的不平衡变化,但是我们依然会在讨论添加节点的时候,使用这个概念。
  • 最低不平衡节点:在向AVL树中添加节点操作,或者删除节点操作的时候,可能会引起AVL树整体结构的不平衡,但是在这个整体不平衡的AVL树中,又以其中层数最接近叶子节点的一个不平衡节点更为重要,我们称最接近叶子节点的不平衡点为最低不平衡点

2.1、三代以内的添加节点操作

三代以内的情况,一般来讲都指的是祖父节点因为元素的插入导致不平衡,三代以内的情况我们分为4中情况:

(1)左左下来右旋

如果插入节点在加入二叉树中的时候,是挂载在了其祖父节点的左孩子下,并且是其父节点的左孩子,并导致整个二叉树的不平衡,那么此时我们使用右旋的方式,对二叉排序树进行调整,即:左左下来右旋

扫描二维码关注公众号,回复: 8986447 查看本文章

右旋的步骤:
步骤1:父节点取代祖父节点的位置,即向上提升一层
步骤2:祖父节点变为父节点的右孩子,此时的父节点不可能存在右孩子节点,因为如果存在,则在上一轮就应该已经通过旋转调整平衡了
步骤3:其余节点之间的关系保持不变

(2)右右下来左旋

如果插入节点在加入二叉树中的时候,是挂载在了其祖父节点的右孩子下,并且是其父节点的右孩子,并导致整个二叉树的不平衡,那么此时我们使用左旋的方式,对二叉排序树进行调整,即:右右下来左旋

左旋的步骤:
步骤1:父节点取代祖父节点的位置,即向上提升一层
步骤2:祖父节点变为父节点的左孩子,此时的父节点不可能存在左孩子节点,因为如果存在,则在上一轮就应该已经通过旋转调整平衡了
步骤3:其余节点之间的关系保持不变

(3)左右下来左右旋

左右旋的步骤:
步骤1:插入节点取代父节点的位置
步骤2:父节点成为替换后插入节点的左孩子节点
步骤3:替换后的插入节点替换祖父节点
步骤4:祖父节点成为替换后的插入节点的右孩子节点
步骤5:其余节点之间的关系保持不变

(4)右左下来右左旋

如果插入节点在加入二叉树中的时候,是挂载在了其祖父节点的右孩子下,并且是其父节点的左孩子,并导致整个二叉树的不平衡,那么此时我们使用右左旋的方式,对二叉排序树进行调整,即:右左下来右左旋

右左旋的步骤:
步骤1:插入节点取代父节点的位置
步骤2:父节点成为替换后插入节点的右孩子节点
步骤3:替换后的插入节点替换祖父节点
步骤4:祖父节点成为替换后的插入节点的左孩子节点
步骤5:其余节点之间的关系保持不变

2.2、四代以内添加节点的情况

四代以内的情况,一般来讲都指的是曾祖节点因为元素的插入导致不平衡四代以内的情况我们可以分为如下4种:

(1)左左型右旋

第1个左:曾祖节点的左子树深度高于右子树深度,那么说明问题出现在曾祖节点的左子树中
第2个左:曾祖节点左孩子的左子树深度高于曾祖节点左孩子的右子树深度,说明问题出在曾祖节点左孩子的左子树中,此时我们通过右旋解决这一问题。

右旋步骤:
步骤1:使用插入节点的祖父节点替代插入节点的曾祖节点
步骤2:曾祖节点连同其右孩子一起成为祖父节点的右孩子
步骤3:祖父节点原先的右孩子成为曾祖节点的左孩子
步骤4:其余节点之间的关系保持不变

(2)右右型左旋

第1个右:曾祖节点的右子树深度高于左子树深度,那么说明问题出现在曾祖节点的右子树中
第2个右:曾祖节点右孩子的右子树深度高于曾祖节点右孩子的左子树深度,说明问题出在曾祖节点右孩子的右子树中
此时我们通过左旋解决这一问题。

左旋步骤:
步骤1:使用插入节点的祖父节点替代插入节点的曾祖节点
步骤2:曾祖节点连同其左孩子一起成为祖父节点的左孩子
步骤3:祖父节点原先的左孩子成为曾祖节点的右孩子
步骤4:其余节点之间的关系保持不变

(3)左右型左右旋

第1个左:曾祖节点的左子树深度高于右子树深度,那么说明问题出现在曾祖节点的左子树中

第2个右:曾祖节点左孩子的右子树深度高于曾祖节点左孩子的左子树深度,说明问题出在曾祖节点左孩子的右子树中
此时我们通过左右旋解决这一问题

左右旋的步骤:
步骤1:使用父节点替代祖父节点
步骤2:祖父节点成为父节点的左孩子;
             如果父节点原先只有左孩子,则父节点的左孩子变成祖父节点的右孩子;
             如果父节点原先只有右孩子,则右孩子位置不变;祖父节点左旋完成
             注意:父节点不可能同时具有左孩子和右孩子
步骤3:祖父节点左旋完毕后,使用新的祖父节点替代曾祖节点,曾祖节点及其右孩子成为祖父节
点的右孩子,曾祖节点右旋完成
步骤4:其余节点之间的关系保持不变

(4)右左型右左旋

第1个右:曾祖节点的右子树深度高于左子树深度,那么说明问题出现在曾祖节点的右子树中
第2个左:曾祖节点右孩子的左子树深度高于曾祖节点右孩子的右子树深度,说明问题出在曾祖节点右孩子的左子树中此时我们通过右左旋解决这一问题

右左旋的步骤:
步骤1:使用父节点替代祖父节点
步骤2:祖父节点成为父节点的右孩子;
             如果父节点原先只有左孩子,则左孩子位置不变;
             如果父节点原先只有右孩子,则父节点的右孩子变成祖父节点的左孩子;祖父节点右旋完成
             注意:父节点不可能同时具有左孩子和右孩子
步骤3:祖父节点右旋完毕后,使用新的祖父节点替代曾祖节,曾祖节点及其左孩子成为祖父节点的左孩子,曾祖节点左旋完成
步骤4:其余节点之间的关系保持不变

3.1、不影响平衡性的的删除

假设我们在删除AVL树的一个节点之后,整个AVL树依然是平衡的,那么我们可以根据删除节点的子节点数量,更加详细的分出如下3种情况:
(1)删除的是叶子节点

如果删除的是一个叶子节点,并且在删除之后整个AVL树依然处于平衡状态,那么次数没有必要对AVL树结构做出任何调整,直接删除这个节点即可。

(2)删除带有一个子节点的节点
如果我们删除的节点带有一个子节点,并且在删除这个节点之后,整个AVL树结构依然保持平衡,那么不论这个节点的子节点是其左孩子还是其右孩子,只要使用这个节点的子节点替换这个节点原有的位置即可,此时依然不需要对整个AVL树结构做出任何调整。

(3)删除带有两个子节点的节点
在这种情况下,我们选择使用当前AVL树中,比删除节点大的节点中,取值最小的节点,来替换删除节点,而这种节点,我们称之为后继节点,如下图中的节点7,就是删除节点6的后继节点:

如果我们删除节点2,那么节点3就是删除节点2的后继节点,并使用节点3来替换节点2,可是如果后继节点同样具有左右孩子怎么办?那么此时我们就如同对待删除节点一样,对待这个后继节点,就好像后继节点被删除了一样。上面我们讨论的都是在删除节点后,AVL树依然能够保持平衡的情况,下面我们一起来讨论下,在删除节点之后,AVL如果不能够继续保持平衡,应该如何进行调整。同样,我们需要分别讨论不平衡节点在3代以内和4代以内的两种大情况

3.2、三代以内删除节点的操作

三代以内的情况,一般来讲都指的是祖父节点因为元素的删除导致不平衡,三代以内的情况我们可以分为如下4种:

(1)左左高的右旋

第1个左:删除节点之后,如果祖父节点的左子树深度大于右子树深度,说明问题出在祖父节点的左子树上
第2个左:祖父节点左孩子的左子树深度大于等于祖父节点左孩子右子树的深度,那么说明只要对祖父节点进行右旋就能够保证AVL树的平衡,此时我们通过右旋解决这一问题

右旋的步骤:
步骤1:使用父节点替换祖父节点
步骤2:如果父节点原先就具有右孩子,那么父节点的右孩子变成祖父节点的左孩子
步骤3:其余节点之间的关系保持不变

(2)右右高的左旋

第1个右:删除节点之后,如果祖父节点的右子树深度大于左子树深度,说明问题出在祖父节点的右子树上
第2个左:祖父节点右孩子的右子树深度大于等于祖父节点右孩子左子树的深度,那么说明只要对祖父节点进行左旋就能够保证AVL树的平衡,此时我们通过左旋解决这一问题

左旋的步骤:
步骤1:使用父节点替换祖父节点
步骤2:如果父节点原先就具有左孩子,那么父节点的左孩子变成祖父节点的右孩子
步骤3:其余节点之间的关系保持不变
(3)左右高的左右旋
第1个左:删除节点之后,如果祖父节点的左子树深度大于右子树深度,说明问题出在祖父节点的左子树上
第2个右:祖父节点左孩子的右子树深度大于祖父节点左孩子左子树的深度,那么说明只要对祖父节点进行左右旋就能够保证AVL树的平衡,此时我们通过左右旋解决这一问题

左右旋的步骤:
步骤1:使用叶子节点替换父节点,父节点完成左旋
步骤2:使用替换后的父节点替换祖父节点,祖父节点成为父节点的右孩子,祖父节点右旋完成
步骤3:其余节点之间的关系保持不变
(4) 右左高的右左旋
第1个右:删除节点之后,如果祖父节点的右子树深度大于左子树深度,说明问题出在祖父节点的右子树上
第2个左:祖父节点右孩子的左子树深度大于祖父节点右孩子右子树的深度,那么说明只要对祖父节点进行右左旋就能够保证AVL树的平衡,此时我们通过右左旋解决这一问题

右左旋的步骤:
步骤1:使用叶子节点替换父节点,父节点完成右旋
步骤2:使用替换后的父节点替换祖父节点,祖父节点成为父节点的左孩子,祖父节点左旋完成
步骤3:其余节点之间的关系保持不变

3.3、四代以内删除节点的情况

四代以内的情况,一般来讲都指的是曾祖节点因为元素的删除导致不平衡。四代以内的情况我们可以分为如下4种:

(1)左左型右旋

第1个左:在删除节点后,曾祖节点的左子树深度大于右子树深度,说明问题出在曾祖节点的左子树上
第2个左:曾祖节点左孩子的左子树深度大于等于曾祖节点左孩子右子树的深度,说明问题出在曾祖节点左孩子的左子树上
此时我们通过右旋解决这一问题

右旋的步骤:
步骤1:使用祖父节点替换曾祖节点
步骤2:祖父节点原来的右孩子变成祖父节点的左孩子
步骤3:其余节点之间的关系保持不变

(2)右右型左旋

第1个右:在删除节点后,曾祖节点的右子树深度大于左子树深度,说明问题出在曾祖节点的右子树上
第2个右:曾祖节点右孩子的右子树深度大于等于曾祖节点右孩子左子树的深度,说明问题出在曾祖节点右孩子的右子树上,此时我们通过左旋解决这一问题

左旋的步骤:
步骤1:使用祖父节点替换曾祖节点
步骤2:祖父节点原来的左孩子变成祖父节点的右孩子
步骤3:其余节点之间的关系保持不变

(3)左右型左右旋

第1个左:在删除节点后,曾祖节点的左子树深度大于右子树深度,说明问题出在曾祖节点的左子树上
第2个右:曾祖节点左孩子的右子树深度大于曾祖节点左孩子左子树的深度,说明问题出在曾祖节点左孩子的右子树上
此时我们通过左右旋解决这一问题

左右旋的步骤:
步骤1:父节点替代祖父节点,父节点原来的左孩子变成祖父节点的右孩子,祖父节点完成左旋
步骤2:祖父节点替代曾祖节点,祖父节点原来的右孩子变成曾祖节点的左孩子,曾祖节点完成右旋
步骤3:其余节点之间的关系保持不变

(4)右左型右左旋

第1个右:在删除节点后,曾祖节点的右子树深度大于左子树深度,说明问题出在曾祖节点的右子树上
第2个左:曾祖节点右孩子的左子树深度大于曾祖节点右孩子右子树的深度,说明问题出在曾祖节点右孩子的左子树上,此时我们通过右左旋解决这一问题

右左旋的步骤:
步骤1:父节点替代祖父节点,父节点原来的右孩子变成祖父节点的左孩子,祖父节点完成右旋
步骤2:祖父节点替代曾祖节点,祖父节点原来的左孩子变成曾祖节点的右孩子,曾祖节点完成左旋
步骤3:其余节点之间的关系保持不变

四、总结

在看了上面那么多令人头晕眼花的案例之后,我们静下心来,仔细想想,实际上我们需要掌握的旋转方式只有4中:左旋、右旋、左右旋、右左旋。而将这四种旋转方式与能够处理的问题总结在一张表中,可以得到如下结果:

所以从上表我们不难总结出对AVL树进行旋转的方式:左左不平右旋、右右不平左旋,左右不平左右旋,右左不平右左旋。
上面总结的结果,不论是在涉及到3代节点还是4代节点之间的关系、不论是在插入节点的情况下还是删除节点的情况下,都是完全成立的。

发布了98 篇原创文章 · 获赞 165 · 访问量 21万+

猜你喜欢

转载自blog.csdn.net/cyl101816/article/details/99313704