算法与数据结构(十一) 平衡二叉树(AVL树)(Swift版)

今天的博客是在上一篇博客的基础上进行的延伸。上一篇博客咱们主要聊了二叉排序树,详情请戳《二叉排序树的查找、插入与删除》。本篇博客咱们就在二叉排序树的基础上来聊聊平衡二叉树,也叫AVL树,AVL是发明平衡二叉树的两个科学家的名字的缩写,在此就不作深究了。其实平衡二叉树就是二叉排序树的一种,比二叉排序树多了一个平衡的条件。在一个平衡二叉树中,一个结点的左右子树的深度差不超过1html

本篇博客咱们就依照平衡二叉树的特色,在建立二叉排序树的同时要保证结点的左右子树的深度差不超过1的规则。当咱们往二叉排序树中插入结点时,咱们要对二叉排序树的平衡性进行检查,若是因插入这个新的结点二叉排序树的平衡性被打破了,咱们就得根据打破平衡二叉树的类型对二叉排序树进行调整使其再次进入到平衡二叉树的状态。固然,在删除结点时也要二叉树的平衡进行检查,发现不平衡时立马进行纠正。今天博客介绍的就是平衡二叉树的建立于结点的删除。废话少说,进入今天博客的主题。git

 

1、平衡二叉树的结点github

在博客的第一部分呢,我想先给出平衡二叉树的结点结构。固然是在上篇博客中的二叉排序树的结点上进行修改的。下方这个AVLTreeNote就是咱们本篇平衡二叉树所使用的结点类。该类与二叉排序树的结点类差很少,就是增长了额外的三个字段。web

  • depth字段用来记录以该结点为根结点的树的深度,由于下方求平衡因子时会使用到该字段。
  • balanceFactor字段就是咱们所说的平衡因子,其实就是左子树的深度减去右子树的深度,由于一棵平衡二叉树的左右子树的深度差不会超过1,因此一颗平衡二叉树的节点的平衡因子为-1,0,或者1。若是为其余值,那么说明该平衡二叉树已再也不平衡,须要被平衡。
  • fatherNote字段用来指向该结点父节点,咱们在调整二叉树的平衡时会用到该指针。

上面就是咱们添加的三个字段,下方咱们会分别给出depthbalanceFactor字段的计算方式。算法

  

 

从上面的代码段中咱们能够看出,depthbalanceFactor这两个字段都是计算属性。接下来咱们将给出这两个计算属性具体的计算方法。函数

下方这段代码就是depth计算属性的计算方法。计算方法也是比较简单的,当该结点的左右子树都存在时,depth就等于左右子树深度较大的那个值进行加1操做。若是左右子树有一个为空的话,那么depth的值就为不为空的那个子树的高度加1. 若是左右子树都为空的话,那么depth的值就为0。具体算法以下所示:测试

  

 

接下来咱们来看一下平衡因子balanceFactor的计算方法,以下所示。从下方的代码段中,咱们也不难看出计算方法也是比较简单的。若是左右子树都存在的话,平衡因子balanceFactor就等于左子树的深度减去右子树的深度。若是左子树不为空,右子树为空的话,那么balanceFactor就等于leftChild.depth + 1, 反之就等于-(rightChild.depth + 1)。若是左右子树都等于nil的话,那么平衡因子就为0。具体算法以下所示:spa

  

 

2、打破平衡的类型以及调整方法3d

平衡二叉树建立的过程与二叉排序树的建立过程大致相同,只不过是在新的节点插入到二叉排序树后,咱们要对其进行平衡的检查。在检查过程当中,若是发现不平衡的节点(平衡因子不为1,0或者-1的状况)咱们就要对其进行相应的调整,让其平衡。固然插入节点打破平衡的状况总结起来总共有四种,也就是本部分要聊的这几种。大致能够分为左左(LL), 左右(LR), 右右(RR), 右左(RL),下方会对每一种状况进行详细的介绍,并给出调整平衡的方案,而且给出具体的代码的实现。指针

 

 一、左左(LL)的状况

当咱们往一个节点的左(Left)孩子左(Left)孩子添加一个结点时,致使该节点出现了不平衡的状况,咱们称之为这种不平衡的状况为左左(LL)的不平衡状况。下方这个示意图就是左左状况以及左左状况的平衡调整。

在下方示意图中,咱们插入了一个3节点,致使8的平衡因子变成了2,所以咱们要对8下方的树进行调整,将其调整为平衡二叉树。平衡的具体步骤以及指针的变化方式以下所示,在此就不作过多赘述了。

  

根据上述的示意图,咱们不难给出左左状况下调整状况下的代码,下方就是具体的代码实现。若是咱们要对8进行调整,那么咱们只须要将8所对应的结点指针做为下方函数的参数便可。具体代码的意思请结合着上方示意图的步骤来看,由于每一个结点都会有一个父节点指针,在调整完,不要忘记调整父节点的指向呢,其余的就不作过多赘述了。

  

 

2.左右(LR)的状况

该状况与上述状况相似,只不过是往一个结点的左子树的右子树上添加了一个新结点时致使的不平衡。这种不平衡的方式调整起来会麻烦一些,不过还算是好理解。咱们先将左右的状况转换成左左的状况,而后调用左左的方法来进行调整。下方示意图就是将左右转换成左左的状况,而后再按照左左的状况进行调整。由于下方示意图比较明确了,在此就不作过多赘述了。

  

根据上面的示意图,给出相应的代码实现并不困难。代码段中的前几行代码就是将左右的状况转换成左左的状况,而后调用咱们上一部分左左的方法进行调整。具体作法以下代码一致。

  

 

3.右右(RR)的状况

右右的状况与左左的状况极为类似,就是方向不一样。右右状况就是由于往结点的右孩子的右孩子上添加了一个结点,而后致使该结点不平衡的状况。右右不平衡状况的调整与左左的步骤相似,只是操做的子节点不一样。下方示意图就是右右不平衡状况的调整步骤。每一步的详细操做请看示意图下方的解说。

  

根据上述的示意图,而后在根据咱们以前左左状况的代码,给出右右状况的代码要简单的多。下方的方法就是右右状况调整的代码段,其实就是根据左左状况改的。以下所示:

  

 


4.右左(RL)的状况

看完上面三步,右左什么意思就一目了然了。右左确定是往该结点的右子树的左结点上添加一个结点致使以该结点为根节点的子树不平衡的状况。这种状况下,要进行树的调整也是比较好作的,咱们就类比左右的那种状况。咱们先将右左转换成右右的状况,而后在按照右右的状况进行调整便可。下方就是这种状况调整的完整过程。

  

根据上述过程,给出具体的代码实现并不困难,下发就是根据上述示意图给出的具体代码实现。

  

 

3、平衡二叉树建立的完整示意图 

上面聊完调节平衡二叉树的具体方法后,接下来为了更直观的了解平衡二叉树的建立步骤,咱们来一个完整的实例。观察一下一棵平衡二叉树从无到有的整个过程,下方就是整个过程。在本部分中,咱们须要将下方的这个序列转换成平衡二叉树进行存储。

  

步骤1:取出3,加入到平衡二叉树中

由于此刻咱们的平衡二叉树为空的,因此咱们将3做为平衡二叉树的根节点。此刻3的左右子树为空,因此3的平衡因子为0,此刻咱们现有的二叉树是平衡的不须要调整。

  

步骤2:将2取出,插入到平衡二叉树中

由于2比3小,因此将2做为3的左孩子。由于2是叶子节点,因此2的平衡因子和深度都为零。而节点3左子树的深度为1,右子树为空,因此3的平衡因子=左子树的深度-右子树深度+1 = 1。从平衡因子中判断,此刻咱们的二叉树是平衡二叉树。

  

步骤3:取出1插入的二叉树中

由于1比2要小,因此将1做为2的左孩子。此刻咱们不难计算出节点3的平衡因子为2,能够看出以3为根节点的树以及不在平衡,咱们须要对其进行调整。咱们不难发现,由于王3的左孩子的左孩子上添加了一个节点,因此属于左左的状况,咱们须要按照上一部分左左的状况处理。将该树根据左左的状况再度调整到平衡,具体以下所示:

  

步骤4:将4插入到平衡二叉树中

下方将4插入到以前的平衡二叉树中,插入后,二叉树仍然是平衡的,以下所示:

  

步骤5:将5插入到平衡二叉树中 

将5插入到平衡二叉树中后,咱们发现以3为结点的子树是由于此节点引发的最小不平衡二叉树。因此咱们须要以3结点为基准进行调整。不难看出,此种状况为RR的状况,按照RR的状况进行调整便可,以下所示:

  

步骤6:将6取出插入到平衡二叉树中

结点6插入后,咱们不难看出结点2是最小不平衡二叉树的结点,并且是RR的状况,咱们须要对其经过RR的状况进行调整,以下所示: 

  

步骤7:将7插入到平衡二叉树

结点7插入后,咱们能够看出,最小不平衡二叉树的结点是5,因此咱们要以5为调整结点,按照RR的状况对咱们不平衡的二叉树进行调整。具体以下所示:

  

步骤8:将10插入到平衡二叉树

10插入到平衡二叉树上,没有引发不平衡,咱们保持不变。

  

步骤9:将9插入到平衡二叉树

将9插入后,引发了新的不平。最小不平衡二叉树的根节点为7,不平衡的状况为RL, 因此咱们能够根据RL状况进行调整,以下所示:

步骤10:将8插入

节点8插入后,引发了二叉树的不平衡,最小不平衡二叉树的节点为6。不平衡的状况为RL, 咱们能够根据RL状况对不平衡的二叉树进行调整。

  

 

4、建立平衡二叉树的具体代码实现

上面的示意图聊的也挺足的了,接下来咱们就要给出完整的平衡二叉树建立的代码了。平衡二叉树的插入和查找与二叉排序树的插入和查找相似,只不过平衡二叉树在插入元素后须要的查找该树在插入节点后是否是平衡,若是不平衡就要根据相应调整平衡的策略进行调整。若是进行调整在本篇博客的第一部分咱们就已经详细的给出了。本部分咱们详细的给出若是在节点插入后寻找最小不平衡二叉树的根节点

 

1.寻找最小不平衡二叉树的根节点

开门见山,下方就是寻找最小不平衡二叉树的根节点的代码,代码比较简单。由于每个节点都有一个指针指向其父节点,因此咱们能够以插入的结点为基准,依次往父节点找,知道找到那个平衡因子不为-1,0或者1的节点为止。若是找到该结点咱们就调用调整平衡的相关函数便可,下方就是该过程的具体实现。

  

 

2.肯定不平衡的类型

找到不平衡节点后,在对其进行调整以前,咱们须要肯定具体是那种不平衡类型。下方这个代码段,就是根据不平衡节点来肯定不平衡类型的。具体肯定方式以下:

  • 若是根节点的平衡因子为2,则说明确定是根节点是左孩子引发的不平衡,因而乎咱们肯定了第一个 。而后咱们在查看根节点的左孩子的平衡因子, 若是为1,那么说明是在根节点的左孩子上添加了左孩子致使的不平衡,因此是左左的状况。同理, 若是是-1,那么说明是在根节点的左孩子上添加了右孩子,因此是左右的状况
  • 若是根节点的平衡因子为-1,那么很显然是根节点的右孩子引发的不平衡。参考左左,左右的状况,若是根节点右孩子的平衡因子为1,那么为右左的状况,若是根节点的右孩子的平衡因子为-1,那么为右右的状况。

上述过程用代码来表示,就以下所示:

  

 

3.具体调整平衡方法的调用

肯定完不平衡类型后,咱们须要根据fixNoBalanceType()方法提供的不平衡类型来调用相应的调整平衡的方法,具体代码以下所示。在Switch-Case语句中调用的这些方法,咱们在本篇博客的第二部分已经给出了。

  

平衡二叉树的删除方法在本篇博客中就不作过多赘述了,在删除一个结点后,咱们要以该删除结点的父节点为准,往上查找不平衡的那个点,而后根据咱们聊的不平衡的状况进行调整便可。咱们在github上分享的代码包括平衡二叉树的结点删除的代码,具体请查看github上的代码实现。

 

5、测试用例

本篇博客的测试用例,咱们就使用第三部分使用的测试用例。在建立完平衡二叉树后咱们对平衡二叉树中的结点进行删除,而后查看二叉树是否依然平衡。

   

下方是插入结点没有调用调整平衡的功能所建立的二叉排序树,咱们能够看一下其中序遍历的输出结果。从结果中咱们就能够看出,这棵二叉排序树是不平衡的。

  

接下来咱们就要启动咱们二叉排序树调节平衡的功能,下方就是咱们建立的平衡二叉树输出的结构,以及每次调整平衡的结点。

  

而下方的输出结果是删除某个结点后的输出结果,由于咱们在删除结点后,对二叉树也进行了检查,若是不平衡咱们要对其进行调节,输出结果以下所示;

  

 

上述代码的运行结果以下所示:

本篇博客所涉及的demo在github上的分享地址以下:

github分享地址:https://github.com/lizelu/DataStruct-Swift/tree/master/AVLTree

相关文章
相关标签/搜索