这个树,怎么一下就平衡了?

发表于 3年以前  | 总阅读数:499 次

什么是AVL树

大家好,我是bigsai,好久不见,甚是想念,今天给大家讲讲AVL树。

对于树这种数据结构,想必大家也已经不再陌生,我们简单回顾一下。

在树的种类中,通常分成二叉树和多叉树,我们熟悉的二叉树种类有二叉搜索(排序、查找)树、二叉平衡树、伸展树、红黑树等等。而熟悉的多叉树像B树、字典树都是经典多叉树。

普通的二叉树,我们研究其遍历方式,因为其没啥规则约束查找和插入都很随意所以很少有研究价值。

但是二叉树结构上很有特点:左孩子和右孩子,两个不同方向的孩子对应二进制的01,判断的对错,比较的大小 ,所以根据这个结构所有树左侧节点比父节点小,右侧节点比父节点大,这时候就诞生了二叉搜索(排序)树。二叉搜索(排序)树的一大特点就是查找效率提高了,因为查找一个元素位置或者查看元素是否存在通过每遇到一个节点直接进行比较就可以一步步逼近结果的位置。

但二叉搜索(排序树)有个很大的问题就是当插入节点很有序,很可能成为一棵斜树或者深度很高,那么这样的一个查找效率还是趋近于线性O(n)级别,所以这种情况二叉搜索(排序)树的效率是比较低的。

所以,人们有个期望:对一棵树来说插入节点,小的还在左面,大的还在右面方便查找,但是能不能不要出现那么斜的情况?

这不,平衡二叉搜索(AVL)树就是这么干的,AVL在插入的时候每次都会旋转自平衡,让整个树一直处于平衡状态,让整个树的查询更加稳定(logN)。我们首先来看一下什么是AVL树:

  • AVL树是带有平衡条件的二叉搜索树,这个平衡条件必须要容易保持,而且要保证它的深度是O(logN)。
  • AVL的左右子树的高度差(平衡因子)不大于1,并且它的每个子树也都是平衡二叉树。
  • 对于平衡二叉树的最小个数,n0=0;n1=1;nk=n(k-1)+n(k-2)+1;(求法可以类比斐波那契)

难点:AVL是一颗二叉排序树,用什么样的规则或者规律让它能够在复杂度不太高的情况下实现动态平衡呢?

不平衡情况

如果从简单情况模型看,其实四种不平衡情况很简单,分别是RR,LL,RL,LR四种不平衡情况。

然后将其平衡的结果也很容易(不考虑其附带节点只看结果),将中间大小数值移动最上方,其他相对位置不变即可:

当然,这个仅仅是针对三个节点情况太过于理想化了,很多时候让你找不平衡的点,或者我们在解决不平衡的时候,我们需要的就是找到第一个不平衡(从底往上)的点将其平衡即可,下面列举两个不平衡的例子:

上述四种不平衡条件情况,可能出现在底部,也可能出现在头,也可能出现在某个中间节点导致不平衡, 而我们只需要研究其首次不平衡点,解决之后整棵树即继续平衡,在具体的处理上我们使用递归的方式解决问题。

四种不平衡情况处理

针对四种不平衡的情况,这里对每种情况进行详细的讲解。

RR平衡旋转(左单旋转)

这里的RR指的是节点模型的样子,其含义是需要左单旋转(记忆时候需要注意一下RR不是右旋转)!

出现这种情况的原因是节点的右侧的右侧较深这时候不平衡节点需要左旋,再细看过程。

  • 在左旋的过程中,root(oldroot)节点下沉,中间节点(newroot)上浮.而其中中间节点(newroot)的右侧依然不变。
  • 它上浮左侧所以需要指向根节点(oldroot)(毕竟一棵树)。但是这样newroot原来左侧节点H空缺。而我们需要仍然让整个树完整并且满足二叉排序树的规则
  • 而刚好本来oldroot右侧指向newroot现在结构改变oldroot右侧空缺,刚好这个位置满足在oldroot的右侧,在newroot的左侧,所以我们将H插入在这个位置。
  • 其中H可能为NULL,不过不影响操作!

其更详细流程为:

而左旋的代码可以表示为:

private node getRRbanlance(node oldroot) {//右右深,需要左旋
    // TODO Auto-generated method stub
    node newroot=oldroot.right;
    oldroot.right=newroot.left;
    newroot.left=oldroot;
    oldroot.height=Math.max(getHeight(oldroot.left),getHeight(oldroot.right))+1;
    newroot.height=Math.max(getHeight(newroot.left),getHeight(newroot.right))+1;//原来的root的高度需要从新计算
    return newroot;     
}

LL平衡旋转(右单旋转)

而右旋和左旋相反,但是思路相同,根据上述进行替换即可!

代码:

private node getLLbanlance(node oldroot) {//LL小,需要右旋转
    // TODO Auto-generated method stub
    node newroot=oldroot.left;
    oldroot.left=newroot.right;
    newroot.right=oldroot;
    oldroot.height=Math.max(getHeight(oldroot.left),getHeight(oldroot.right))+1;
    newroot.height=Math.max(getHeight(newroot.left),getHeight(newroot.right))+1;//原来的root的高度需要从新金酸  
    return newroot; 
}

RL平衡旋转(先右后左双旋转)

这个RL你可能有点懵圈,为啥RR叫左旋,LL叫右旋,这个RL怎么就叫先右后左旋转了?

别急别急,这个之所以先后后左,是因为具体需要中间节点右旋一次,然后上面节点左旋一次才能平衡,具体可以下面慢慢看。

首先产生这种不平衡的条件原因是:ROOT节点右侧左侧节点的深度高些,使得与左侧的差大于1,这个与我们前面看到的左旋右旋不同因为旋转一次无法达到平衡!

对于右左结构,中间(R)的最大,两侧(ROOT,R.L)的最小,但是下面(R.L)的比上面(ROOT)大(R.LROOT右侧)所以如果平衡的话,那么R.L应该在中间,而R应该在右侧,原来的ROOT在左侧。

这个过程节点的变化浮动比较大,需要妥善处理各个子节点的移动使其满足二叉排序树的性质!

这种双旋转具体实现其实也不难,不要被外表唬住,这里面双旋转我提供两种解答方法。

思路(标准答案)1:两次旋转RR,LL

这个处理起来非常容易,因为前面已经解决RR(左旋),LL(右旋)的问题,所以这里面在上面基础上可以直接解决,首先对R节点进行一次LL右旋,旋转一次之后R在最右侧,这就转化成RR不平衡旋转的问题了,所以这个时候以ROOT开始一次RR左旋即可完成平衡,具体流程可以参考下面这张图。

思路(个人方法)2:直接分析

根据初始和结果的状态,然后分析各个节点变化顺序=,手动操作这些节点即可。其实不管你怎么操作,只要能满足最后结构一致就行啦!

首先根据ROOT,R,R.L三个节点变化,R.L肯定要在最顶层,左右分别指向ROOT和R,那么这其中R.leftROOT.right发生变化(原来分别是R.L和R)暂时为空。而刚好根据左右大小关系可以补上R.L原来的孩子节点AB

代码为:(注释部分为方案1)

private node getRLbanlance(node oldroot) {//右左深    
//        node newroot=oldroot.right.left;
//        oldroot.right.left=newroot.right;
//        newroot.right=oldroot.right;
//        oldroot.right=newroot.left; 
//        newroot.left=oldroot;
//        oldroot.height=Math.max(getHeight(oldroot.left),getHeight(oldroot.right))+1;
//        newroot.right.height=Math.max(getHeight(newroot.right.left),getHeight(newroot.right.right))+1;
//        newroot.height=Math.max(getHeight(oldroot.left),getHeight(newroot.right))+1;//原来的root的高度需要从新金酸  
    oldroot.right =getLLbanlance(oldroot.right);
    oldroot.height=Math.max(getHeight(oldroot.left), getHeight(oldroot.right))+1;
    return getRRbanlance(oldroot);

    }

LR平衡旋转(先左后右双旋转)

这个情况和RL情况相似,采取相同操作即可。

根据上述RL修改即可

这部分代码为

private node getLRbanlance(node oldroot) {
    oldroot.left =getRRbanlance(oldroot.left);
    oldroot.height=Math.max(getHeight(oldroot.left), getHeight(oldroot.right))+1;
    return getLLbanlance(oldroot);

    }

代码实现

首先对于节点多个height属性。用于计算高度(平衡因子)

插入是递归插入,递归是一个来回的过程,去的过程进行插入,回的过程进行高度更新,和检查是否平衡。推荐不要写全局递归计算高度,效率太低下,事实上高度变化只和插入和平衡有关,仔细考虑即不会有疏漏!

代码写的比较早,如有命名不规范的情况,还请勿喷,如果有疏漏还请指出!

import java.util.ArrayDeque;
import java.util.Queue;

public class AVLTree {

    class node
    {
        int value;
        node left;
        node right;
        int height;
        public node() {

        }
        public node(int value)
        {
            this.value=value;
            this.height=0;
        }
        public node(int value,node left,node right)
        {
            this.value=value;
            this.left=left;this.right=right;
            this.height=0;
        }
    }
    node root;// 根

    public AVLTree() {
        this.root = null;
    }

    public boolean isContains(int x)// 是否存在
    {
        node current = root;
        if (root == null) {
            return false;
        }
        while (current.value != x && current != null) {
            if (x < current.value) {
                current = current.left;
            }
            if (x > current.value) {
                current = current.right;
            }
            if (current == null) {
                return false;
            } // 在里面判断如果超直接返回
        }
        // 如果在这个位置判断是否为空会导致current.value不存在报错
        if (current.value == x) {
            return true;
        }
        return false;
    }

    public int getHeight(node t)
    {
        if(t==null) {return -1;}//
        return t.height;
        //return 1+Math.max(getHeight(t.left), getHeight(t.right));这种效率太低
    }
    public void cengxu(node t) {//层序遍历
        Queue<node> q1 = new ArrayDeque<node>();
        if (t == null)
            return;
        if (t != null) {
            q1.add(t);
        }
        while (!q1.isEmpty()) {
            node t1 = q1.poll();
            if (t1.left != null)
                q1.add(t1.left);
            if (t1.right != null)
                q1.add(t1.right);
            System.out.print(t1.value + " ");
        }
        System.out.println();
    }
    public void zhongxu(node t)//中序遍历 中序遍历:左子树---> 根结点 ---> 右子树
    {//为了测试改成中后都行
        if(t!=null)
        {
            zhongxu(t.left);
            System.out.print(t.value+" ");//访问完左节点访问当前节点
            zhongxu(t.right);
            //System.out.print(t.value+" ");//访问完左节点访问当前节点
        }
    }
    public void qianxu(node t)//前序递归 前序遍历:根结点 ---> 左子树 ---> 右子树
    {
        if(t!=null) {
            System.out.print(t.value+" ");//当前节点
            qianxu(t.left );
            qianxu(t.right);}
    }
    public void insert(int value) {
        root=insert(value, root);
    }
    public node insert(int x,node t)//插入   t是root的引用
    {
        node a1=new node(x);
        //if(root==null) {root=a1;return root;}        
        if(t==null)    {return a1;}
        //插入操作。递归实现
        else if(t!=null)
        {
            if(x<t.value)
            { t.left=insert(x,t.left);}
            else
            { t.right= insert(x,t.right);}
        }
        /*
         * 更新当前节点的高度,因为整个插入只有被插入的一方有影响,
         * 所以递归会更新好最底层的,上层可直接调用
         */
        t.height=Math.max(getHeight(t.left),getHeight(t.right))+1;//不要写成递归, 递归效率低
        return banlance(t);//因为java对象传参机制,需要克隆,不可直接t=xx 否则变换      
    }

    private node banlance(node t) {
        // TODO Auto-generated method stub
        //if(t==null)return null;
        int lefthigh=getHeight(t.left);
        int righthigh=getHeight(t.right);
        if(Math.abs(lefthigh-righthigh)<=1)//不需要平衡滴
        {    return t;}
        else if(lefthigh<righthigh)//右侧大
        {
            if(getHeight(t.right.left)<getHeight(t.right.right))//RR需要左旋
            {
                return  getRRbanlance(t);
            }
            else {
                return getRLbanlance(t);
            }
        }
        else {
            if(getHeight(t.left.left)>getHeight(t.left.right))//ll 左左
            {
                return getLLbanlance(t);
            }
            else {
                return getLRbanlance(t);
            }
        }
    }
    /*
     *        oldroot(平衡因子为2,不平衡)    ==>   newroot
     *       /    \                              /    \
     *      B     newroot(平衡因子为1)        oldroot   D
     *             /    \                      / \      \
     *            C      D                    B   C      E
     *                    \
     *                     E
     */

    private node getRRbanlance(node oldroot) {//右右深,需要左旋
        // TODO Auto-generated method stub
        node newroot=oldroot.right;
        oldroot.right=newroot.left;
        newroot.left=oldroot;
        oldroot.height=Math.max(getHeight(oldroot.left),getHeight(oldroot.right))+1;
        newroot.height=Math.max(getHeight(newroot.left),getHeight(newroot.right))+1;//原来的root的高度需要从新计算
        return newroot;
    }
    /*
     * 右旋同理
     */
    private node getLLbanlance(node oldroot) {//LL小,需要右旋转
        // TODO Auto-generated method stub
        node newroot=oldroot.left;
        oldroot.left=newroot.right;
        newroot.right=oldroot;
        oldroot.height=Math.max(getHeight(oldroot.left),getHeight(oldroot.right))+1;
        newroot.height=Math.max(getHeight(newroot.left),getHeight(newroot.right))+1;//原来的root的高度需要从新金酸    
        return newroot;
    }

    private node getLRbanlance(node oldroot) {
        oldroot.left =getRRbanlance(oldroot.left);
        oldroot.height=Math.max(getHeight(oldroot.left), getHeight(oldroot.right))+1;
        return getLLbanlance(oldroot);

    }

    /*          (不平衡出现在右左节点)
     *         oldroot       ==>          newroot
     *        /        \                 /       \
     *       A          B             oldroot     B
     *                /   \           /    \     /  \
     *           newroot   D         A      E    F   D
     *            /   \
     *           E     F
     */

    private node getRLbanlance(node oldroot) {//右左深    
//        node newroot=oldroot.right.left;
//        oldroot.right.left=newroot.right;
//        newroot.right=oldroot.right;
//        oldroot.right=newroot.left; 
//        newroot.left=oldroot;
//        oldroot.height=Math.max(getHeight(oldroot.left),getHeight(oldroot.right))+1;
//        newroot.right.height=Math.max(getHeight(newroot.right.left),getHeight(newroot.right.right))+1;
//        newroot.height=Math.max(getHeight(oldroot.left),getHeight(newroot.right))+1;//原来的root的高度需要从新金酸  
        oldroot.right =getLLbanlance(oldroot.right);
        oldroot.height=Math.max(getHeight(oldroot.left), getHeight(oldroot.right))+1;
        return getRRbanlance(oldroot);

    }
}

测试情况:

AVL的理解需要时间,当然笔者的AVL自己写的可能有些疏漏,如果有问题还请各位一起探讨!

当然,除了插入,AVL还有删除等其他操作,(原理相似。删除后平衡)有兴趣可以一起研究。

结语

原创不易,如果本文对你有帮助 !

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/jackutRD9YXTMl-wZQ8BeQ

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237229次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8063次阅读
 目录