我们知道,对于一般的二叉搜索树(Binary Search Tree),其期望高度(即为一棵平衡树时)为log2n,其各操作的时间复杂度O(log2n)同时也由此而决定。但是,在某些极端的情况下(如在插入的序列是有序的时),二叉搜索树将退化成近似链或链,此时,其操作的时间复杂度将退化成线性的,即O(n)。我们可以通过随机化建立二叉搜索树来尽量的避免这种情况,但是在进行了多次的操作之后,由于在删除时,我们总是选择将待删除节点的后继代替它本身,这样就会造成总是右边的节点数目减少,以至于树向左偏沉。这同时也会造成树的平衡性受到破坏,提高它的操作的时间复杂度。于是就有了我们下边介绍的平衡二叉树。

平衡二叉树

定义:

平衡二叉树或者是空树,或者是满足下列性质的二叉树。

⑴:左子树和右子树深度之差的绝对值不大于1;

⑵:左子树和右子树也都是平衡二叉树。

平衡因子(Balance Factor) :二叉树上结点的左子树的深度减去其右子树深度称为该结点的平衡因子。

因此,平衡二叉树上每个结点的平衡因子只可能是-1、0和1,即
|hlhr|。 否则,只要有一个结点的平衡因子的绝对值大于1, 该二叉树就不是平衡二叉树。

最小二叉平衡树的节点的公式如下:F(n)=F(n−1)+F(n−2)+1

这个类似于一个递归的数列,可以参考Fibonacci数列,1是根节点,F(n−1)是左子树的节点数量,F(n−2)是右子树的节点数量。

平衡查找树(AVL树)

二叉排序树是一种查找效率比较高的组织形式,但其平均查找长度受树的形态影响较大,形态比较均匀时查找效率很好,形态明显偏向某一方向时其效率就大大降低。因此,希望有更好的二叉排序树,其形态总是均衡的,查找时能得到最好的效率,这就是平衡二叉排序树。

平衡二叉排序树(Balanced Binary Tree或Height-Balanced Tree)是在1962年由Adelson-Velskii和Landis提出的,一种高度平衡的排序二叉树,其每一个节点的左子树和右子树的高度差最多等于1,又称AVL树

如果一棵二叉树既是二叉排序树又是平衡二叉树,称为平衡二叉(查找)排序树(Balanced Binary Sort Tree) 。

平衡二叉树所有包括分支节点和叶节点的平衡因子只可能是-1,0和1,只要有一个节点的因子不在这三个值之内,该二叉树就是不平衡的。

纠错:图1是不平衡的

最小不平衡子树:距离插入结点最近的,且平衡因子的绝对值大于1的节点为根的子树。

n个结点的AVL树最大深度约1.44log2n。查找、插入和删除在平均和最坏情况下都是O(logn)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。这个方案很好的解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(logN)。但是频繁旋转会使插入和删除牺牲掉O(logN)左右的时间,不过相对二叉查找树来说,时间上稳定了很多。

可以采用动态平衡技术保持一个平衡二叉树。构造平衡二叉树的时候,也可以采用相同的方法,默认初始时,是一个空树,插入节点时,通过动态平衡技术对二叉树进行调整。

Adeleon-Velskii和Landis提出了一个动态地保持二叉排序树平衡的方法,其基本思想是:在构造二叉排序树的过程中,每当插入一个结点时,首先检查是否因插入而破坏了树的平衡性,若是因插入结点而破坏了树的平衡性,则找出其中最小不平衡树,在保持排序树特性的前提下,调整最小不平衡子树各结点之间的连接关系,以达到新的平衡。通常这样得到的平衡二叉排序树简称为AVL树。

AVL树的自平衡操作——旋转

AVL树最关键的也是最难的一步操作就是旋转。旋转主要是为了实现AVL树在实施了插入和删除操作以后,树重新回到平衡的方法。下面我们重点研究一下AVL树的旋转。

不平衡的四种情况

对于一个平衡的节点,由于任意节点最多有两个儿子,因此高度不平衡时,此节点的两颗子树的高度差2。容易看出,这种不平衡出现在下面四种情况:

  • 1) 节点的左子树3节点高度比右子树7节点2,左子树节点的左子树1节点高度大于右子树4节点,这种情况成为左左(LL)(左孩子的左子树深度大)。
  • 2) 节点的左子树2节点高度比右子树7节点2,左子树节点的左子树1节点高度小于右子树4节点,这种情况成为左右(LR)。
  • 3) 节点的左子树1节点高度比右子树5节点小2,右子树节点的左子树3节点高度大于右子树6节点,这种情况成为右左(RL)。
  • 4) 节点的左子树1节点高度比右子树4节点小2,右子树节点的左子树3节点高度小于右子树6节点,这种情况成为右右(RR)。

从图2中可以可以看出,1和4两种情况是对称的,这两种情况的旋转算法是一致的,只需要经过一次旋转就可以达到目标,我们称之为单旋转。2和3两种情况也是对称的,这两种情况的旋转算法也是一致的,需要进行两次旋转,我们称之为双旋转。

单旋转

单旋转是针对于左左(LL)右右(RR)这两种情况的解决方案,这两种情况是对称的,只要解决了左左这种情况,右右就很好办了。图3是左左情况的解决方案,节点k2不满足平衡特性,因为它的左子树k1比右子树Z深2层,而且k1子树中,更深的一层的是k1的左子树X子树,所以属于左左情况。

为使树恢复平衡,我们把k1变成这棵树的根节点,因为k2大于k1,把k2置于k1的右子树上,而原本在k1右子树的Y大于k1,小于k2,就把Y置于k2的左子树上,这样既满足了二叉查找树的性质,又满足了平衡二叉树的性质。

这样的操作只需要一部分指针改变,结果我们得到另外一颗二叉查找树,它是一棵AVL树,因为X向上一移动了一层,Y还停留在原来的层面上,Z向下移动了一层。整棵树的新高度和之前没有在左子树上插入的高度相同,插入操作使得X高度长高了。因此,由于这颗子树高度没有变化,所以通往根节点的路径就不需要继续旋转了。

同理右右即是向左转。

双旋转

双旋转:对于左右和右左这两种情况,单旋转不能使它达到一个平衡状态,要经过两次旋转。双旋转是针对于这两种情况的解决方案,同样的,这样两种情况也是对称的,只要解决了左右这种情况,右左就很好办了。图4是左右情况的解决方案,节点k3不满足平衡特性,因为它的左子树k1比右子树Z深2层,而且k1子树中,更深的一层的是k1的右子树k2子树,所以属于左右情况。

为使树恢复平衡,我们需要进行两步,第一步,把k1作为根,进行一次右右旋转,旋转之后就变成了左左情况,所以第二步再进行一次左左旋转,最后得到了一棵以k2为根的平衡二叉树树。

小结:

左左或右右:向右转 或 向左转

左右或右左:失衡左孩向左转,首失衡节点向右转 或 失衡右孩向右转,首失衡节点向左转

构建过程

设要构造的平衡二叉树中各结点的值分别是(1,2,3,4,5,6,7,10,9),平衡二叉树的构造过程如图

关键代码示例

/*平衡二叉树的定义结构*/
typedef strucr avlnode
{
datatype data; /*数据元素*/
int bf; /*平衡因子*/
struct avlnode * lchild, * rchild; /*右孩子*/
}AVLNode, * AVLTree; /*LL调整*/
void LL_rotate(AVLTree *p)
{ /*对*p为根的子树,作LL处理,处理之后,p指向为子树的新根*/
lp = *p->lchild; //lp指向*p左子树根结点
*p->lchild = lp->rchild; //lp的右子树挂接*p的左子树
lp->rchild = *p;
*p->bf = ; lp->bf = ; //修改相关的平衡因子
*p = lp; //p指向新的根结点
} /*RR调整*/
void RR_rotate(AVLTree *p)
{ /*对*p为根的子树,作LL处理,处理之后,p指向为子树的新根*/
lp = *p->rchild; //lp指向*p右子树根结点
*p->rchild = lp->lchild; //lp的子左树挂接*p的右子树
lp->rchild = *p;
*p->bf = ; lp->bf = ; //修改相关的平衡因子
*p = lp; //p指向新的根结点
} /*LR调整*/
void LR_rotate(AVLTree *p)
{ /*对*p为根的子树,作LL处理,处理之后,p指向为子树的新根*/
lp = *p->lchild; //lp指向失衡结点a的左孩子b
rlp = lp->rchild; //rlp指向lp的右孩子
if(rlp->bf == -) r=;
else r=; //记忆是LR1还是LR2
RR_rotate(&(*p->lchild)); //对*p的左子树进行RR处理
LL_rotate(p); //对*p进行LL处理
if(r==){ //改变相关结点的平衡因子
*p->bf = ;
lp->bf = ;
}else{
*p->bf = -;
lp->bf = ;
}
*p = rlp;
*p->bf = ;
} /*RL调整*/
void RL_rotate(AVLTree *p)
{ /*对*p为根的子树,作LL处理,处理之后,p指向为子树的新根*/
rp = *p->lchild; //lp指向失衡结点a的右孩子b
lrp = rp->lchild; //lrp指向rp的左孩子
if(lrp->bf == -) lh=;
else lh=; //记忆是LR1还是LR2
LL_rotate(&(*p->rchild)); //对*p的右子树进行LL处理
RR_rotate(p); //对*p进行RR处理
if(lh==){ //改变相关结点的平衡因子
*p->bf = ;
rp->bf = -;
}else{
*p->bf = ;
rp->bf = ;
}
*p = lrp;
*p->bf = ;
}

数据结构与算法16—平衡二叉(AVL)树的更多相关文章

  1. 1.红黑树和自平衡二叉(查找)树区别 2.红黑树与B树的区别

    1.红黑树和自平衡二叉(查找)树区别 1.红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单. 2.平衡 ...

  2. 看动画学算法之:平衡二叉搜索树AVL Tree

    目录 简介 AVL的特性 AVL的构建 AVL的搜索 AVL的插入 AVL的删除 简介 平衡二叉搜索树是一种特殊的二叉搜索树.为什么会有平衡二叉搜索树呢? 考虑一下二叉搜索树的特殊情况,如果一个二叉搜 ...

  3. 自平衡二叉(查找树/搜索树/排序树) binary search tree

    在计算机科学中,AVL树是最先发明的自平衡二叉查找树.AVL树得名于它的发明者 G.M. Adelson-Velsky 和 E.M. Landis,他们在 1962 年的论文 "An alg ...

  4. 数据结构与算法-基础(十一)AVL 树

    AVL 树 是最早时期发明的自平衡二叉搜索树之一.是依据它的两位发明者的名称命名. AVL 树有一个重要的属性,即平衡因子(Balance Factor),平衡因子 == 某个节点的左右子树高度差. ...

  5. Java数据结构和算法(十二)——2-3-4树

    通过前面的介绍,我们知道在二叉树中,每个节点只有一个数据项,最多有两个子节点.如果允许每个节点可以有更多的数据项和更多的子节点,就是多叉树.本篇博客我们将介绍的——2-3-4树,它是一种多叉树,它的每 ...

  6. 算法学习 - 平衡二叉查找树实现(AVL树)

    平衡二叉查找树 平衡二叉查找树是非常早出现的平衡树,由于全部子树的高度差不超过1,所以操作平均为O(logN). 平衡二叉查找树和BS树非常像,插入和删除操作也基本一样.可是每一个节点多了一个高度的信 ...

  7. 【数据结构与算法Python版学习笔记】树——平衡二叉搜索树(AVL树)

    定义 能够在key插入时一直保持平衡的二叉查找树: AVL树 利用AVL树实现ADT Map, 基本上与BST的实现相同,不同之处仅在于二叉树的生成与维护过程 平衡因子 AVL树的实现中, 需要对每个 ...

  8. 二叉搜索树、AVL平衡二叉搜索树、红黑树、多路查找树

    1.二叉搜索树 1.1定义 是一棵二叉树,每个节点一定大于等于其左子树中每一个节点,小于等于其右子树每一个节点 1.2插入节点 从根节点开始向下找到合适的位置插入成为叶子结点即可:在向下遍历时,如果要 ...

  9. 算法:非平衡二叉搜索树(UnBalanced Binary Search Tree)

    背景 很多场景下都需要将元素存储到已排序的集合中.用数组来存储,搜索效率非常高: O(log n),但是插入效率比较低:O(n).用链表来存储,插入效率和搜索效率都比较低:O(n).如何能提供插入和搜 ...

随机推荐

  1. <虚树+树型DP> SDOI2011消耗战

    <虚树+树型DP> SDOI2011消耗战 #include <iostream> #include <cstdio> #include <cstring&g ...

  2. vue中使用element-ui自定义主题后,vue-cli跑不起来了

    环境:vue-cli 2.x版本 自己在官网配置了主题并放到了项目中https://element.eleme.cn/#/zh-CN/theme 然后,我的脚手架在我的电脑中休息了几天,就跑不通了呢! ...

  3. 一个sh脚本 同时运行 多个sh脚本

    问题: 原来的启动方式 cd /opt/OpenIMSCore ./pcscf.sh ./icscf.sh ./scscf.sh cd /opt/OpenIMSCore/FHoSS/deploy ./ ...

  4. cad.net 块裁剪边界反向修剪

    Querying for XCLIP information inside AutoCAD using .NET  这里下面观众讨论了 How do I determine if an x-clip ...

  5. 文本编辑 工具vim

    一vim的3种常用模式:一般模式.编辑模式和命令模式 如果系统里没有vim工具,需安装 1,一般模式 首先复制一个文件到/tmp/目录下,并改名为1,txt 移动光标: h 或 向左箭头键(←)    ...

  6. 【mysql】搜索带\字符

    模糊查询 LIKE '%\\\%'

  7. 【java】获取客户端访问的公网ip和归属地

    import com.alibaba.druid.support.json.JSONUtils; import org.thymeleaf.util.StringUtils; import javax ...

  8. python实现栈结构

    # -*- coding:utf-8 -*- # __author__ :kusy # __content__:文件说明 # __date__:2018/9/30 17:28 class MyStac ...

  9. [转帖]美团在Redis上踩过的一些坑-5.redis cluster遇到的一些问题

    美团在Redis上踩过的一些坑-5.redis cluster遇到的一些问题 博客分类: redis 运维 redis clustercluster-node-timeoutfailover  转载请 ...

  10. bootstrap使用笔记

    本文翻译自[http://getbootstrap.com/2.3.2/scaffolding.html#gridSystem] 1.需要在H5的文档声明中使用: 2.如下一个简单的两列布局,先添加一 ...