• AVL自平衡的原理


      AVL树规定,左右子树的高度差不超过1,当结点的高度差也就是平衡因子的绝对值超过1就会失衡,而AVL树就会自平衡。一直以来我都被自平衡搞得很痛苦,搞不清左旋和右旋。于是干脆写一篇blog彻底说明AVL的插入自平衡怎么实现的。

      首先定义AVL结点:(这里还是用C的描述方法)

    #include<iostream>
    #include<queue>
    using namespace std;
    typedef struct node{
        int date,height;//结点的数据和高度
        Node*lchild,*rchild;
    }Node,*pnode;

    平衡因子没法直接得到,要用该结点的左节点减去右节点得到,于是定义一个获取平衡因子的函数;插入和删除都可能导致结点的高度发生改变,于是需要有更新结点高度的函数

    int get_height(Node*root){//获取结点的高度
        if(root==NULL)return 0;
        return root->height;
    }
    int get_balance(Node*root){//获取结点的平衡因子
        if(root==NULL)return 0;
        return get_height(root->lchild)-get_height(root->rchild);
    }
    void update_height(Node*root){//更新节点的高度
        root->height=__max(get_height(root->lchild),get_height(root->rchild))+1;    
    }
    Node* newNode(int x){//用数据x新建一个结点
        pnode te=new Node;
        te->date=x;
        te->height=1;//叶节点高度为1
        te->lchild=te->rchild=NULL;
        return te;
    }

    假设AVL树当前为: 插入数据 4后,很明显会破坏AVL树的平衡:

     我们需要对AVL树进行调整,调整的原则是改变不平衡的结点。数据4插入在左子树,很明显如果平衡被破坏,那么不符合AVL原则结点的平衡一定为2(平衡因子的定义就是用左子树减去右子树),既然是左子树高度过高,就要削减左子树的高度,所以用右旋的方式来调节左右子树高度,达到平衡因子为1.

    右旋调整高度后为: 

     所以我们需要一个右旋的函数来实现右旋:

    void R(Node*& root){//注意结点一定要用引用!
        Node*temp=root->lchild;//先定义一个temp临时变量用于记录root的左孩子,因为右旋就是将左孩子替换root结点
        root->lchild=temp->rchild;//旋转后temp的右孩子将会是root结点,但二叉树只有两个孩子,temp结点原来的右孩子就要加在root结点的左孩子上
        temp->rchild=root;//现在在更改temp的右孩子,我们基本完成了右旋,但root的父节点的孩子结点还是root没有变过
        //已经旋转好了,但还差把temp改为root;
        update_height(root);
        update_height(temp);
        root=temp;//将root结点指针指向改为temp结点,完成旋转
    }

    但用右旋看上去并不能解决所有的失衡情况,例如

    假设AVL树当前为: 插入数据19后,AVL树失衡:

     根据我们刚才的结论如果插在左子树,那么失衡的结点平衡因子肯定是2,所以我们右旋,但右旋后得到的二叉树却还是失衡的

    右旋后的AVL树: 根节点的平衡因子为-2,也是失衡的,那么右旋看上去不能解决失衡问题。

     不难知道插入19后的AVL树中数据为16的结点的的平衡因子为-1,所谓的右旋实现的功能就是就是使待平衡的结点以及他的左孩子的平衡因子降低,待平衡结点平衡因子2右旋后为1达到平衡,左节点平衡因子-1右旋后为-2,所以继续失衡。

    同理,左旋实现的功能就是就是使待平衡的结点以及他的右孩子的平衡因子均增加。

      所以如果一个结点失衡,仅仅考虑该节点是不足够的,还要看它的孩子结点,左旋和右旋要达到的目的是平衡失衡结点以及不制造新的不平衡结点

      对于失衡节点平衡因子为2,左孩子平衡因子为-1这种情况,定义为LR型,要先调整它的左子树,让左子树再右旋时不会失衡,所以我们同理继续定义左旋:

     //左旋的代码

    void L(Node*& root){
        Node*temp=root->rchild;
        root->rchild=temp->lchild;
        temp->lchild=root;
        update_height(root);
        update_height(temp);
        root=temp;
    }

    左旋后得到的新二叉树: ,在进一步右旋,得到平衡的AVL树: 

    最后附上insert新节点的代码:

    void insert(Node*&root,int v){
        if(root==NULL){
            root=newNode(v);
            return;
        }
        if(v<root->date){
            insert(root->lchild,v);
            update_height(root);//插入后就要更新结点高度
            if(get_balance(root)==2){
            //完成了root的左子树的插入,如果AVL失衡那么root的平衡因子只可能为2
                if(get_balance(root->lchild)==1)//LL型,只需要右旋一次
                    R(root);
                else if(get_balance(root->lchild)==-1)
                    {//LR型,先对左子树左旋一次变为LL型,在对root右旋一次
                        L(root->lchild);
                        R(root);
                    }
            }
            //失衡的root完成了平衡处理后,root的祖宗节点就都平衡了。
        }
        else
        {//同理
            insert(root->rchild,v);
            update_height(root);
            if(get_balance(root)==-2){
                if(get_balance(root->lchild)==-1)
                    L(root);
                else if(get_balance(root->lchild)==1)
                    {
                        R(root->lchild);
                        L(root);
                    }
            }
    
        }
        
    }
  • 相关阅读:
    能帮你找到网页设计灵感的16个网站
    [转]自定义SqlMembershipProvider方法
    C#实现的根据年月日计算星期几的函数
    分享一个我自己写的支持多条件组合查询的分页存储过程
    史上最强的福克斯遥控钥匙失灵解决方案(zt)
    在页面实现数据还原,在终止数据库进程时,报不能用kill来终结自己的进程
    ViewState使用兼谈序列化
    jQuery 的上传图片预览插件
    Asp.net 备份、还原Ms SQLServer及压缩Access数据库
    aspnet_Membership表的意义
  • 原文地址:https://www.cnblogs.com/kalicener/p/13333789.html
Copyright © 2020-2023  润新知