数据结构二叉树——理论篇

Posted Shun_Hua.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构二叉树——理论篇相关的知识,希望对你有一定的参考价值。

文章目录

一.树的概念

提到树我们就不得不提到:线性结构和非线性结构,

线性结构:数据与数据的关系是唯一的!(一对一
例:顺序表,链表,栈,对列,数组等都是线性结构。
非线性结构:数据与数据的关系不是唯一的!(一对多或者多对一
例:树形结构。

看如上的图我们可以大概画一张图(倒着画):

可以得出: 有分支的结点至少一个结点,结点不是唯一的对应关系,有一对多的关系。因此数是非线性结构的。
提问:树的节点可以是0吗?
百度回答:树的节点是大于等于0的!
因此我们可以得出:树的节点是可以为0的!n=0,为空树
但是:节点为0的可不一定是树哦!
因此我们可以得出树的两个概念:
1.节点大于等于0
2.满足非线性结构

子树:

树能分为:
1.根节点
2.子树/叶子节点

我们接着分析:树是分叉的(从上往下看),也就是说树是分层的,并且层之间的结点,是互不关联的!也就是说树往下分析是一对多的,并且是从上面的唯一 一个节点,分支而来。
例:

分析:
第一棵树:第二层(从上往下数)C与D之间相关,所以不是树。
第二棵树:第三层 E的节点的上层结点有两个(B和C),呈现多对一(下往上),所以不是树。
第三棵树:第三层G节点从下往上看(有A和D两个节点)呈现多对一的结构,所以不是树。
总结:
树的定义满足:
1.节点数大于等于0
2.满足非线性结构
3.子树是相互独立的,不能交叉。
4.树是可以递归的:
1.根节点
2.子树(只有一个节点也算是子树)

说明:只有一个节点不能再继续分,即递归停止。

树的相关概念:

1.节点的度:结点所含的子树的个数称为度。

例:A节点有以B,C,D,E,F,G根节点的树,子树有6个,因此度为6。
二级结论:
证明:
树最大的度为M节点个数为N树的边数为N-1,(2个结点由一个边连起来,由此可推N个结点可由N-1个边数连起来),度为1的结点的边有1条,
度为2的结点的边有2条,可以推出度为N的结点有N条。
设度为0的结点为N0,度为1的结点为N1,度为2的结点为N2……度M的结点为Nm
因此总边数:N-1=0*N0+1 *N1+2*N2+……+M *Nm

2.叶节点或终端节点:度为0的节点称为叶节点.

比如:B,C,H,I,K,L,M,N,P,Q结点没有子树,因此是叶节点。

3.非终端节点或分支节点:度不为0的节点

比如:A,D,E,F,G,J,这些结点向下有分支的结点,称之为分支节点。

4.双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点。
5.孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;

比如:A结点向下有分结点——B,C,D,E,F,G。则A称为B,C,D,E,F,G的父节点,那B,C,D,E,F,G就称之为A的子结点(孩子结点)。

6.兄弟节点:具有相同父节点的节点互称为兄弟节点;

A的子结点有B,C,D,E,F,G。A的子结点都有一个父节点,所以B,C,D,E,F,G互称为兄弟节点。

7.树的度:一棵树中,最大的节点的度称为树的度,这里的最大的节点指的是,树中,度为最大的节点

此棵树中度最大的节点为A,所以A的度就是树的度——6

8.节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推。

比如:A的节点的层数为1,B,C,D,E,F,G的节点的层数为2,H,I,J,K,L,M,N.
节点的层数为3,P,Q的层数为4.

9.树的高度或深度:树中节点的最大层次

从上往下数分了几层就是树的高度,这棵树分了4层,高度就是4。

10.堂兄弟节点:双亲节点在同一层的节点互为堂兄弟,这里的双亲是不同一个父节点。

比如第三层的H,I节点不一个父节点,所以它们互为堂兄弟。

11.节点的祖先:从根到该节点所经分支上的所有节点

比如:Q节点,到达Q节点的路径上的节点,A,E,j这三个节点称之为Q节点的祖先。

12.子孙:以某节点为根的子树中任一节点都称为该节点的子孙。

比如:A是这棵树的根节点,一切节点都由A节点发展而来,所以A是所有节点的祖先,反之,A以下的所有节点称之为A节点的子孙。

13.森林:由m(m>0)棵互不相交的树的集合称为森林。


这样的不相交的两棵树称为森林。
一棵树去掉根节点可以变成森林 ,上面的树(最上面那棵大树)去掉A节点,就变成了森林。

二.树的表示方法

如何表示棵树?
由于非线性结构一对多的关系,想表示一棵树很难,由于大佬的存在,有了如下三种表示形式。

1.双亲表示法

由于每个节点都只有一个父节点,所以我们可通过双亲来表示一棵树。具体方式通过数组的形式实现。

从根节点以下标0的位置(数组的第一个元素下标为0)开始,从左往右依次增大,从上往下呈现增大趋势。

一个数据有两种性质,所存数据的值,父节点的下标位置——根节点没有父节点所以设置父节点的下标位置为-1,因此我们可采用二维数组的形式进行存储信息(父节点存的是元素的行标,元素的下标恒为0)。

2.孩子表示法

由于每个节点都可能有孩子,所以我们通过数组的形式,数组的元素为链表(用于记录该节点的孩子),从而表示一棵树。

3.孩子兄弟表示法

每个节点都可能有亲兄弟和孩子结点,所以我们可以通过链表的形式,进行表示。

这样父节点可以间接地指向其所有的孩子。

三.二叉树

1.概念

1.二叉树是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树。
递归定义:
二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树,这跟树的递归很像。
图:

特点:
1. 二叉树不存在度大于2的结点
2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
说明: 这里的不能颠倒指的是:颠倒之后就不是原来的树了,所以树有左右之分,并且树是有序的。

3.任何一棵二叉树都由以下几个组成部分组成:

2.特殊的二叉树

满二叉树

定义:每一层的结点都满了不能再插入结点。
图:

结点的个数:设高度/深度为h
T(N)=2^0 + 2的1次方+2的2次方+……2的h-1次方,由等比数列前N项和可得
T(N)=2的h次方-1

完全二叉树

定义:设完全的二叉树的高度为h,则h-1层的结点满了,h层的结点至少为1个。
图:

结点个数的范围:设高度为h
完全二叉树的特殊情况:最后一层只有1个节点,或满二叉树。
最后一层只有1个节点:T(N)=2的h减一次方-1(这是前h-1层满的情况)+1(最后一层为1)
满二叉树: T(N)= 2的h次方-1
因此节点的范围:2的h减1次方到2的n次方减一,这是闭区间。

注意:

这种的 最后一层点没有按顺序排列不叫完全二叉树。

二叉树结论

设高度为:h,二叉树的节点个数为N
1.分空二叉树的最多节点数:2的h次方-1
2.最深层的最多节点数:2的h减一次方
3.满二叉树的节点数:2的h次方-1
4.完全二叉树的节点数范围:2的h-1次方到2的h次方-1
5.满二叉树的深度为:log以2为底(N+1)
6.完全二叉树的最小深度:logN + 1,对数的底数为2
7.一个结点的孩子结点为:2M+1,2M+2,(M是结点的下标)
代入下图验证一下

算法笔记-6:平衡二叉树(理论篇)

        上期我们说到,如果按从小到大的顺序向二叉搜索树插入节点,那么搜索树会退化成链表,这样就背离我们利用二叉搜索树的特性方便查询元素的初衷了。

        有没有办法让这个比萨斜塔一样的结构变成一棵左右平衡的二叉搜索树呢?

图1-退化成链表的二叉搜索树


        不如,把链表打个对折?

算法笔记-6:平衡二叉树(理论篇)

图2-链表对折


        感觉像搭了个人字梯,中间还是太空了……

算法笔记-6:平衡二叉树(理论篇)

        没事,注意到20左右两边分别可以视为链表,继续对折。

算法笔记-6:平衡二叉树(理论篇)

图3-继续折叠


        这种方法看起来对链表效果不错,但是对于更一般的失衡树,就很难发挥作用了,会遇到大量的子树冲突问题。

算法笔记-6:平衡二叉树(理论篇)

图4-几种非链表失衡树

        

        与其将不平衡的二叉树调整成平衡,不如一开始就构造一个平衡的二叉搜索树。每新增一个节点,就判断并重新调整平衡。这便是构造平衡二叉树的过程。

        构造平衡二叉搜索树(又称自平衡二叉搜索树)并不只有一种方法,但我们常说的平衡二叉树一般指AVL树。AVL树是最早被发明的平衡二叉树,由Adelson Velsky 和 Evgenii Landis在1962年发表的论文中提出,并以二人的名字命名。这也是我们今天将要介绍的内容。




一、平衡二叉搜索树的定义


        如果一棵二叉搜索树中,任何一个节点的左右子树高度(深度)差不超过1,那么它就是一棵平衡二叉搜索树。

算法笔记-6:平衡二叉树(理论篇)

图5-平衡二叉树

        换句话说,AVL树的平衡,是通过限制左右子树的高度差来实现的。所以AVL树又被称为高度平衡树。

        来看看如下哪些属于平衡二叉树?

算法笔记-6:平衡二叉树(理论篇)

图6

        (a)不是平衡二叉树,虽然根节点左右子树高度一样,但子节点的子树一边高度为2,一边为0。

        (b)不是平衡二叉树,虽然根节点是平衡的,右边只比左边低1层。但左子树自身仍是失衡的。

        (c)是平衡二叉树。

        (d)不是平衡二叉树。根节点失衡,左子树高为3,右子树为1。


二、平衡因子


        三四层高的树,就很容易眼花分不清平不平衡了。要是树有20多层,一层层数不得累坏去?构造平衡树,判断平不平衡的工作最终还是要交给程序来办。为了使程序能够判断二叉搜索树的平衡性,Adelson Velsky 和 Evgenii Landis在论文里提出了平衡因子的概念:

        

        节点的左子树与右子树的高度(深度)差即为该节点的平衡因子。

        

算法笔记-6:平衡二叉树(理论篇)

图7-平衡因子计算


        对于程序来说,每个节点记录以自己的高度,然后把两个子节点上报的高度相减(缺失的子节点高度设为0),就可以得到平衡因子。


        一棵空树肯定是平衡的,一棵只有根节点的树和一棵只有一个根和一个子节点的树也是平衡的。再往后,不论树中有多少个节点,一定是插入节点导致失衡,或者删除节点时导致失衡。如果我们每次插入或者删除节点后,都通过一定处理使树重新恢复平衡,那么最终一定能得到平衡二叉树。这就是构造平衡二叉树的原理。



三、插入节点导致失衡时的调整


        假如我们要在图5的节点60下面插入节点70,先来计算插入前每个节点的平衡因子,标在节点旁边:

算法笔记-6:平衡二叉树(理论篇)

图8-插入前的平衡情况

        插入节点70后,部分节点的平衡因子发生了变化:

算法笔记-6:平衡二叉树(理论篇)

图9-插入后的平衡情况

        如果沿着新插入的节点70不断地访问父节点,直到找到第一个平衡因子绝对值大于1的节点,也就是40。那么,以40这个节点为根节点的子树,就被称为最小失衡子树。

        如果把整棵树调整成平衡树太难,那么我们就先调整这棵最小失衡子树。而调整平衡,需要用到旋转方法,至于怎么旋转,则需要分四种情况讨论:


1、在失衡节点的左子节点的左子树插入节点导致失衡(LL)

        如图10,假如我们在节点5下方插入了节点2,导致节点20成为最小失衡树根节点。此时20的平衡因子是2,左子树比右子树高2,我们需要进行右旋,增加右子树高度,降低左子树高度,使平衡因子降低。

算法笔记-6:平衡二叉树(理论篇)

图10-LL右旋

        具体做法是,将20的左子节点10提升为根节点,20自身沉降为10的右子节点。此时会出现10拥有5、15、20三个子节点的情况。

        10原来的右子节点15安放在哪里呢?因为20原来的左子节点10已经变成了它的父节点,那么20现在的左子节点一定是空缺的,15刚好可以放在这个位置。

        所以右旋就是把左子节点提升为根节点,根节点沉降为右子节点,左子节点的右子树变为右子节点的左子树。为方便记忆,我们可以简称为“左升右降,左右变右左”。


2、在失衡节点的右子节点的右子树插入节点导致失衡(RR)

          如图11,假如我们在节点60下方插入了节点70,节点40的平衡因子变成-2,该子树成为最小失衡子树。

算法笔记-6:平衡二叉树(理论篇)


图11-RR左旋

        为了使其重新平衡,需要将其左旋:根节点40向左沉降,右子节点50提升为新根节点。50的原左子节点45,变成40的新右子节点。

        左旋操作可以简记为“右升左降,右左变左右”。


3、在失衡节点的左子节点的右子树插入节点导致失衡(LR)

        如图12,假如我们在节点15下方插入节点12,节点20平衡因子变为2,成为最小失衡子树的根节点。此时只进行一次右旋无法使树重新平衡,需要先左旋后右旋。

算法笔记-6:平衡二叉树(理论篇)

图12-LR先左旋后右旋

        先将20的左子节点10为根节点的子树左旋,节点15升为根节点(20的新左子节点),节点10向左沉降。12因为小于15大于10,它变成了10的右子节点。

        然后将20为根的子树右旋,15再次升为根节点,20向右沉降。由于15没有右子节点,所以20没有得到新的左子节点。

        先左旋后右旋的操作可以简记为“左子树左旋,根右旋”。


4、在失衡节点的右子节点的左子树插入节点导致失衡(RL)
        如图13,假如我们在节点45的下方插入节点48,节点40平衡因子变为-2,成为最小失衡树的根节点。只进行一次左旋无法使树重新平衡,需要先右旋后左旋。

算法笔记-6:平衡二叉树(理论篇)

图13-RL先右旋后左旋

        先将40的右子节点50为根节点的子树右旋,节点45升为根节点(40的新右子节点),节点50向右沉降。48需要转变为50的左子节点。

        然后将40为根的子树左旋,45再次升为根节点,40向左沉降。由于45没有左子节点,40没有获得新的右子节点。

        先右旋后左旋的操作可以简记为“右子树右旋,根左旋”。



四、四种插入情况的平衡因子分析


        这四种插入情况,如何让程序判断呢?

        仔细观察最小失衡子树中平衡因子的变化。首先,插入节点导致失衡,必然是因为失衡树的其中一个子树树高发生了变化。从左子树中插入,必然是提高了左子树高度,那么根节点平衡因子一定为2;如果是在右子树中插入,平衡因子一定为-2。

算法笔记-6:平衡二叉树(理论篇)

图14-失衡节点平衡因子比较

        平衡因子为2的情况下,考察左子节点的平衡因子,如果是1,说明它的左子树高于右子树,高的原因是其左子树插入了节点,这种情况是LL。

        如果是-1,一定是其右子树插入了节点,这种情况是LR。

算法笔记-6:平衡二叉树(理论篇)

图15-LL和LR左子节点平衡因子比较

        同样地,平衡因子为-2的情况下,就考察右子节点的平衡因子,如果是1,说明是RL情况;如果是-1,说明是RR情况。

算法笔记-6:平衡二叉树(理论篇)

图16-RL和RR右子节点平衡因子比较


        总结四种插入的情况和相应的旋转方法,如下表:

算法笔记-6:平衡二叉树(理论篇)

图17-四种插入情况总结



五、删除节点导致失衡时的调整


        在上期算法笔记中,我们讨论了二叉搜索树的删除一共分三种情况:

        1、删除的节点为叶子节点

        2、删除的节点只有一个子节点

        3、删除的节点有两个子节点


        如果严格按照上期中所述流程操作,AVL树删除节点后至少可以成为一棵二叉搜索树。接下来只需要保证是否所有节点满足平衡树的要求,即平衡因子绝对值小于2就行了。

        那么,是否需要把每个节点的平衡因子遍历一遍呢?


1、删除节点后的平衡性检查

        考察下图删除节点35和50的过程。删除节点35后,其父节点没有了左子树,右子树高仍为1,因此平衡因子变成了-1。但40所处的子树高度没有变化,因此40的父节点30平衡因子不变。

算法笔记-6:平衡二叉树(理论篇)

图18-删除节点35和50

        继续删除节点50,40的平衡因子变为了0。而因为40所处的子树高变成了1,其父节点30的平衡因子变为2,成为最小失衡节点。


        从上图的例子中我们可以看出,删除会使被删除节点所属的子树高度变化,进而影响到其父节点,乃至父节点的父节点平衡因子变化。但是,并不影响其兄弟节点和父节点的兄弟节点。因此,我们只需要从被删除节点的位置不断向上访问父节点。当所有父节点平衡因子绝对值小于2时,整棵树就仍旧是平衡的。

        而当删除导致失衡时,第一个访问到平衡因子为2或者-2的节点即是最小失衡节点。然后,我们可以判断其失衡类型来选择将其恢复平衡的旋转方式。因为我们已经分析出了LL,LR,RL,RR四种失衡类型其失衡节点和子节点平衡因子的关系,所以虽然这次不是因为插入节点导致的失衡,我们仍旧可以根据节点30平衡因子为2和节点20平衡因子为-1推断出其属于LR失衡类型,需要进行先左旋,后右旋的操作。

算法笔记-6:平衡二叉树(理论篇)

图19-删除50后的旋转调整

        仔细观察旋转过程,我们可以发现25成为了新的(子)树根。而25原来的两个子节点:左子节点22在左旋(先)过程中成为了20的右子节点,而28在右旋(后)过程中成为了30的左子节点。


2、插入节点中未出现过的失衡情况

        需要注意的是,假如节点10还有子节点5和15,此时节点20的平衡因子为0。这种失衡节点平衡因子为2,其子节点却为0的情况,是我们在讨论插入节点引起的失衡时从未遇到过的,这时该怎么办呢?

        简单尝试后就会发现,这种情况一次右旋即可达到平衡,因此它可以归入LL类型。

算法笔记-6:平衡二叉树(理论篇)

图20-一次右旋即可平衡


        根据这个新发现,我们可以更新图17的表格:

算法笔记-6:平衡二叉树(理论篇)

图21-失衡类型及处理


3、发生节点替代后的平衡调整

        另一个需要注意的点是,删除叶子节点和只有一个子节点的节点。都可以从被删除的节点开始向上搜索父节点,访问平衡因子的情况。但是删除有两个子节点的节点,会涉及到寻找替代节点的问题。找到替代节点后,需要从替代节点的原位置开始向上搜索父节点。


        如下图,删除节点40后,找到替代节点45。而替代节点换上去之后,引起了节点50的失衡。

图22-删除节点40

        可以根据平衡因子推断出节点50的失衡类型为RR失衡,一次左旋即可恢复平衡,如下图:

图23-一次左旋即可平衡

        接着检查50的父节点60、45、30的平衡因子,都是0,那么整棵树恢复平衡完毕。


        综上所述,AVL删除节点的流程可以归纳为:

        1、按二叉搜索树的流程删除节点

        2、从被删除节点或替代节点的位置向上搜索,直到搜索到最小失衡节点或者根节点

        3、判断失衡类型,执行旋转操作

        4、从原最小失衡节点继续向上搜索


        由于AVL树的篇幅较长,这次只介绍AVL树的理论知识。编写代码的内容,我们放在下一期中介绍。这里抛出一个问题供大家思考:为何LR和RL失衡类型无法通过一次旋转达到平衡?大家可以试着旋转操作看看,把答案打在


以上是关于数据结构二叉树——理论篇的主要内容,如果未能解决你的问题,请参考以下文章

数据结构与算法篇二叉树

数据结构&算法篇--二叉树的构建与遍历

数据结构&算法篇--二叉树的构建与遍历

数据结构+算法(第12篇)玩平衡二叉树就像跷跷板一样简单!

在路上---学习篇Python 数据结构和算法 二分查找二叉树遍历

[数据结构]链式二叉树基础概念篇