猴子也能看懂的【平衡(AVL)二叉树】原理

1.引入:

 二叉查找树的时间复杂度问题:

        众所周知,要从一条线性表中查找一个数据,如果从头到尾一个一个地去遍历匹配,最坏的情况就是需要查找的数据在最后一个,所以时间复杂度是O(n)。

        如果想要缩短查找的时间复杂度,可以使用二叉排序树,二叉排序树的特点是:任意找一个结点,在该结点左边的数据一定小于该结点的数据;在该结点右边的数据一定大于该结点的数据。

        二叉排序树的查找方式与二分查找的原理基本是一致的。从根结点开始,如果要查找的数据比它大,则与结点的右孩子比对;如果要查找的数据比他小,则与结点的左孩子比对。所以时间复杂度与二分查找一样,都是O(log n)。

问题:

        然而,当构造这个二叉树的时候,如果插入的序列原本就是有序序列,例如 [1, 2, 3, 4, 5]。根据二叉排序树的原则,要插入的结点与之前的结点比对,如果比它大,就往它的右孩子插。结果会得到一个这样的二叉树:

        可以看到上图中,先在树中插入一个1 ,然后准备插入2,与1比对,比1大就插在1的右孩子结点。再插入3,比1大,插到右边,但是右边已经有了2,再与2比对,比2大,插到2的右孩子结点上。以此类推,原本我们想的是能够得到一个二叉排序树,但是最后构造出来,我们却得到了一个线性表,此时二叉树的查找优势就消失了。

        显然,如果想要查找得比较快,我们会希望二叉树是比较“对称”的,这样比较数据的效率最高。此时,就可以构造AVL平衡二叉树。

2.定义

        平衡二叉树的定义:平衡二叉查找树,简称平衡二叉树,是最早被提出的平衡二叉树,它得名于它的发明者,G.M. Adelson-VelskyE.M. Landis。在avl中,任何两棵子树的高度差最大不超过1,所以也被称为“高度平衡树”。

        性质:

        1、可以为空。

        2、如果不为空,任意两颗子树之间的高度差都不会超过1,且任何子树也都是平衡二叉树。

        平衡的意思,类似于天平,以根结点为天平的中心,左右两棵子树的重量(高度差)相差不能太大(不能超过1)。

例如,还是取上面的例子

        右图是平衡的,而左图就不是一个平衡的二叉树。对于数据为 2 的这个点,它的左子树高度为1,右子树高度为3,不平衡,我们称这样的点为:失衡点

3.几种失衡情况

        对于一个二叉树,它的失衡基本可以用四种情况来概括:左左插入,右右插入,左右插入以及右左插入。下面进行简单的介绍:

1、左左插入,即在结点左孩子的左子树进行插入。

 2、右右插入,即在结点右孩子的右子树进行插入。

3、左右插入,即在结点的左孩子的右子树进行插入。

4、右左插入,即在结点的右孩子的左子树进行插入。

        虽然说是几个“插入”,但是会导致失衡的操作除了插入,删除肯定也是另一个。试想一下,让一个天平失去平衡的方法除了增加砝码,肯定还有移除砝码。

4.几种失衡情况对应的平衡方法

1、左左插入对应方法:右旋

        什么是右旋?听起来好像很抽象,但是这个名字其实是比较形象的。右旋,顾名思义,是一种类似于旋转的方法,因为左边“重”了一点,所以将整个树往右“转”,让重心转移。

分为几个步骤:

        先将失衡点的左孩子向上提,成为新的根结点;

        再将失衡点下坠,成为它左孩子的右孩子;

        如果有的话,失衡点将它原先左孩子的右孩子,接纳为自己的左孩子

为什么要将这个结点接纳为自己的左孩子?为什么可以这样做?

这里我们看下面的左旋操作,根据具体数值再来做解释。

        这样,原本左左插入的二叉树,经过右旋操作,重新达到了平衡状态。就像一条挂在钉子上的绳子,左边垂下来得比较长,于是就人为地将这条绳子向右转动,从右边扯一下,保持左右垂下的长度是差不多的。

2、右右插入对应方法,左旋

与上面的右旋原理相同,都是通过改变树的重心(根结点),来重新达到平衡的效果。

同样的也是三个步骤:

先将失衡点的右孩子上提,成为新的根结点;

这里就是将[ 4 ]向上提,成为这棵树的新的根结点,4的右子树保持不变

再将失衡点[ 2 ] 下坠,成为[ 4 ]的左孩子

这里还有一个[ 3 ],是被失衡点[ 2 ]挤掉了,那么这个 3 要往哪里走呢?

        这与二叉排序树的性质有密不可分的关系:上面讲到,二叉排序树的性质是,任意结点左边的结点,一定都比它小,右边的结点一定都比它大。

        因为这个点是失衡点右子树的结点,所以一定比失衡点大,还是放在失衡点右边;同时它又原来是新根节点的左孩子,所以一定比新的根节点小,还是放在根节点左边。又要放在失衡点右边,又要放在根结点的左边,所以放在失衡点的右孩子结点是比较好的选择。

经过左旋,二叉树重新平衡。

代码:

//左旋
BTNode* RotateLeft(BTNode* node)
{
    BTNode* t = node->Right;  //定义一个指向失衡节点右孩子的指针t
    t->parent = node->parent; //将失衡点的双亲,赋给t的双亲,t成为新的根结点
    node->Right = t->Left;    //失衡点接纳t的左孩子
    t->Left = node;           //失衡点下坠,成为t新的左孩子
    node->parent = t;         //t成为失衡点新的双亲

    if(node->Right != NULL)   //失衡点接纳t的左孩子之后,将左孩子的双亲变为自己
    node->Right->parent = node;
    return t;
}

3、左右插入的对应方法:左右旋

        何为左右旋?简单来说,就是先左旋,再右旋;左旋哪个?左旋左子树!右旋哪个?右旋整个二叉树!

        

先左旋左子树,这样整体就变成了一个左左插入,再对整体进行右旋,就可以让二叉树重新平衡。

先左旋,变成了左左插入的形式

再右旋

代码:

BTNode* Rotate_LeftRight(BTNode* root)
{
    BTNode* t = root->Left;  //定义一个指针指向失衡点的左孩子
    t = RotateLeft(t); //把以左孩子为根节点的子树,即左子树,做左旋操作
    return RotateRight(root); //再将整个二叉树做右旋操作
}

4、右左插入的对应方法,右左旋

与左右旋的实现方法与原理是完全一致的,就不做赘述了。

5.总结

        本文主要从查找的时间复杂度角度入手,指出了二叉树的缺点,再引出AVL平衡二叉树,能将时间复杂度控制在O(log n)。构造一个平衡二叉树,最主要的其实就是,对几种失衡情况的判断以及实施对应的再平衡操作,在插入,以及删除后,检测二叉树是否存在失衡点,调用平衡函数进行平衡。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值