平衡二叉树

平衡二叉树的定义 (AVL—— 发明者为Adel'son-Vel'skii 和 Landis)

 

平衡二叉查找树,又称 AVL树。 它除了具备二叉查找树的基本特征之外,还具有一个非常重要的特点: 的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值平衡因子 ) 不超过1。 也就是说AVL树每个节点的平衡因子只可能是-1、0和1(左子树高度减去右子树高度)。

 

那么如何是二叉查找树在添加数据的同时保持平衡呢?基本思想就是:当在二叉排序树中插入一个节点时,首先检查是否因插入而破坏了平衡,若 破坏,则找出其中的最小不平衡二叉树,在保持二叉排序树特性的情况下,调整最小不平衡子树中节点之间的关系,以达 到新的平衡。所谓最小不平衡子树 指离插入节点最近且以平衡因子的绝对值大于1的节点作为根的子树。 

 

平衡二叉树的操作

 

1. 查找操作

       平衡二叉树的查找基本与二叉查找树相同。

 

2. 插入操作

       在平衡二叉树中插入结点与二叉查找树最大的不同在于要随时保证插入后整棵二叉树是平衡的。那么调整不平衡树的基本方法就是: 旋转 。 下面我们归纳一下平衡旋转的4中情况

1) 绕某元素左旋转  

                                 80                                    90  

                                 /  \             左旋               /    \

                               60 90          ---- ->         80     120

                                    /  \                               /  \       /

                                  85 120                    60  85 100

                                        /

                                      100     

                               a)  BST树                              b ) AVL树

     分析一下:在插入数据100之前,a图的B ST树只有80节点的平衡因子是-1(左高-右高),但整棵树还是平衡的。加入100之后,80节点的平衡因子就成为了-2,此时平衡被破坏。需要左旋转成b 图。

     当树中节点X的右孩子的右孩子上插入新元素,且平衡因子从-1变成-2后,就需要绕节点X进行左旋转。

 

2) 绕某元素右旋转  

                                100                                   85

                                 /  \               右旋              /    \

                              85  120         ------ ->     60    100  

                              /  \                                      \      /   \

                            60 90                                 80  90 120

                              \

                              80

                             a) B ST树                                b) AVL树

     当树中节点X的左孩子的左孩子上插入新元素,且平衡因子从1变成2后,就需要绕节点X进行右旋转。

 

3) 绕某元素的左子节点左旋转,接着再绕该元素自己右旋转。 此情况下就是左旋与右旋 的结合,具体操作时可以分 解成这两种操作,只是围绕点不一样而已。

                                                      

                            100                             100                                90

                             /  \             左旋            /  \              右旋           /    \

                          80  120       ------>      90  120        ------>     80   100  

                          / \                                  /                                    /  \      \

                       60 90                            80                              60  85  120

                            /                               / \

                          85                            60 85 

      当树中节点X的左孩子的右孩子上插入新元素,且 平衡因子从1变成2后,就需要 先绕X的左子节点Y左旋转,接着再绕X右旋转


4) 绕某元素的右子节点右旋转,接着再绕该元素自己左旋转。 此情况下就是 右旋与左旋 的结合,具体操作时可以分解 成这两种操作,只是围绕点不一样而已 。

 

                               80                               80                                       85  

                               /   \              旋          /  \                               /  \     

                            60  100      ------>      60 85            ------->          80 100

                                   /  \                                 \                                   /     /   \       

                                85  120                        100                           60    90 120

                                   \                                   /  \

                                   90                           90  120

       当树中节点X的右孩子的左孩子上插入新元素,且 平衡因子从-1变成-2后,就需要 先绕X的右子节点Y右旋转,接着再绕X左旋转

 

 

平衡二叉树性能分析


平衡二叉树的性能优势:

      很显然,平衡二叉树的优势在于不会出现普通二叉查找树的最差情况。其查找的时间复杂度为O(logN)。

 

平衡二叉树的缺陷:

      (1) 很遗憾的是,为了保证高度平衡,动态插入和删除的代价也随之增加。因此,我们在下一专题中讲讲《红黑树》 这种更加高效的查找结构。

 

      (2) 所有二叉查找树结构的查找代价都与树高是紧密相关的,能否通过减少树高来进一步降低查找代价呢。我们可以通过多路查找树的结构来做到这一点,在后面专题中我们将通过《多路查找树/B-树/B+树 》来介绍。

 

      (3) 在大数据量查找环境下(比如说系统磁盘里的文件目录,数据库中的记录查询 等),所有的二叉查找树结构(BST、AVL、RBT)都不合适。如此大规模的数据量(几G数据),全部组织成平衡二叉树放在内存中是不可能做到的。那么把这棵树放在磁盘中吧。问题就来了:假如构造的平衡二叉树深度有1W层。那么从根节点出发到叶子节点很可能就需要1W次的硬盘IO读写。大家都知道,硬盘的机械部件读写数据的速度远远赶不上纯电子媒体的内存。 查找效率在IO读写过程中将会付出巨大的代价。在大规模数据查询这样一个实际应用背景下,平衡二叉树的效率就很成问题了


以下为C语言代码

#include <stdio.h>

#include <stdlib.h>
#include <string.h>


typedef int Elemtype;


typedef struct Balanced_Binary_Tree
{
    Elemtype e;
    int bf;
    struct Balanced_Binary_Tree *child[2];
}*AVL;


///------------简单的位操作-------------------
void setbit(char *i,char val,char pos)
{
    if(pos==1)
        (*i)=(*i)|1;
    else
    {
        if(val==1)    (*i)=(*i)|2;
        else    (*i)=(*i)&1;
    }
}
char getbit(char i,char pos)
{
    ///调试时,发现这里能返回2///
//    return (i&pos); 出错的地方
    return (i&pos)&&1;
    /
}
///--------------------------------------------


///-----------生成一个结点---------------------
AVL createnode(Elemtype e)
{
    AVL node=NULL;


    node=(AVL)malloc(sizeof(struct Balanced_Binary_Tree));
    node->e=e;    node->bf=0;
    node->child[0]=node->child[1]=NULL;
    
    return node;
}
///---------------------------------------------


///★★★★★★★★AVL的核心操作★★★★★★★★★★★★
///★★★★★★★★★保持平衡★★★★★★★★★★★★★★


//改变因子函数
void setfactor(AVL f,int button)
{
    char fir=button/10,sec=button%10;
    AVL s=f->child[fir],ss=s->child[sec];
    char choice=ss->bf;
    int a=1,b=0;


    //调试时发现,删除时的特殊情况/
/插入时,不会有这种情况,若button=0,则s->bf=1//
/若button=11,则s->bf=-1;然而删除时,却会出现/
/button=0或者button=11时 s->bf=0!!!!!!!
/那么这种特殊情况,平衡后所得的因子就跟一般的//
/不一样了!!!如下///
    if(button==0 && s->bf==0)    f->bf=1,s->bf=-1;
    else if(button==11 && s->bf==0)    f->bf=-1,s->bf=1;
    ///
    else if(button==0 || button==11)
    {
        f->bf=0;
        s->bf=0;
    }
    else
    {
        /写博客时,发现这里有问题///
    //    if(button==1)    choice=-choice;
        /但是为什么我测试的时候怎么都对?!///
/经再次测试,上边确实错了!!!
/改为下边应该就对了吧///
        if(button==1)    {a^=b,b^=a,a^=b;}
       


        if(choice==-1)    f->bf=a,s->bf=b;
        else if(choice==0)    f->bf=s->bf=0;
        else    f->bf=-b,s->bf=-a;
        
        ss->bf=0;
    }
}
//两节点变换函数
void conversion(AVL *T,char direction)
{
    AVL f=*T,s=f->child[direction];


    f->child[direction]=s->child[!direction];
    s->child[!direction]=f;
    *T=s;
}
//保持平衡函数
void keepbalance(AVL *T,char fir,char sec)
{
    AVL *s=&((*T)->child[fir]);
    int button=fir*10+sec;


    if(button==0 || button==11)
    {
        setfactor((*T),button);
        conversion(T,fir);
    }
    else
    {
        setfactor((*T),button);
        conversion(s,sec);
        conversion(T,fir);
    }
}
///★★★★★★★★★★★★★★★★★★★★★★★★★★


///------------插入时的选向操作-------------------
void selectforInsert(AVL *T,char *info,int direction)
{
    AVL cur=*T;
    char firdirection,secdirection;


    if(direction==0)    (cur->bf)++;
    else    (cur->bf)--;


    if(cur->bf==0)    setbit(info,1,1);
    else if(cur->bf==-1 || cur->bf==1)    setbit(info,direction,2);
    else
    {        
        firdirection=direction;
        secdirection=getbit(*info,2);
        keepbalance(T,firdirection,secdirection);
        setbit(info,1,1);
    }
}
//----------------------------------------------


//*************插入操作************************//
char InsertAVL(AVL *T,Elemtype e)
{                                //可用于查询
    char info;
    
    if(!(*T))
    {
        (*T)=createnode(e);
        return 0;
    }
    else if((*T)->e==e)        return -1;
    else if((*T)->e>e)//左
    {
        info=InsertAVL(&((*T)->child[0]),e);


        if(getbit(info,1))    return info;
        
        selectforInsert(T,&info,0);
    }
    else              //右
    {
        info=InsertAVL(&((*T)->child[1]),e);


        if(getbit(info,1))    return info;


        selectforInsert(T,&info,1);
    }
    return info;
}
//*********************************************//


//-------------删除时的选向操作--------------------
void selectforDelete(AVL *T,char *info,char direction)
{
    AVL cur=(*T);
    char firdirection,secdirection;


    if(direction==0)    (cur->bf)--;
    else    (cur->bf)++;
    
    if(cur->bf==0)    *info=0;
    else if(cur->bf==-1 || cur->bf==1)    *info=1;
    else
    {
        firdirection=!direction;
        ///调试时,发现这里少写了一个等号
//        if(cur->child[firdirection]->bf=1)    secdirection=0;草,真帅气!原来1==a这样写确实有必要!
        if(1==cur->child[firdirection]->bf)    secdirection=0;
        /
        else    secdirection=1;
        keepbalance(T,firdirection,secdirection);


        /
///调试时,发现经过子树平衡操作后,*info不一定都是0,就是那个特殊情况,在setfactor中/
///的那种特殊情况时,这里*info应改为1! 所以代码改如下://
/
    //    *info=1; 写代码时:这跟插入可不一样啊...该子树平衡了,它父节点的因子比变!
//    *info=0;//因此,这还没完还要是0!! ............啊……这里不一定是0! 
还是那个特殊情况搞的鬼!//
        if(cur->bf==0)    *info=0;
        else    *info=1;
        /
    }
}
//------------------------------------------------


//-------------变向递归--辅助删点-----------------
char find(AVL *gogal,AVL *p)
{
    char info;
    AVL tp=NULL;
    
    if(NULL!=(*p)->child[0])
    {
        info=find(gogal,&((*p)->child[0]));
        if(info!=0)    return info;
        selectforDelete(p,&info,0);
    }
    else
    {
        (*gogal)->e=(*p)->e;
        tp=(*p)->child[1];
        free((*p));
        *p=tp;
        info=0;
    }
    return info;
}
//------------------------------------------------


//**************删除操作*************************//
char DeleteAVL(AVL *T,Elemtype e)
{
    char info;
    AVL tp=NULL;


    if(!(*T))    return -1;//原if(!T)    return -1;于2011年11月29日8:59:15修改
    else if((*T)->e==e)
    {
        if(NULL!=(*T)->child[1])
        {
            info=find(T,&((*T)->child[1]));
            if(info!=0)    return info;
            selectforDelete(T,&info,1);
        }
        else
        {
            //调试时,发现这样写不对!!!///
        //    (*T)=(p=(*T)->child[0])-(free((*T)),0);//Just save a variable! 这里出问题
//    (*T)=p-(free((*T)),0); 可以
//    (*T)=(p=((*T)->child[0]))+(free((*T)),0); 不可以
            tp=((*T)->child[0]);
            free((*T));
            *T=tp;
            //调试时,发现这里漏了给info赋值
            info=0;
            ///
        }
    }
    else if((*T)->e>e)
    {
        info=DeleteAVL(&(*T)->child[0],e);
        if(info!=0)    return info;
        selectforDelete(T,&info,0);
    }
    else
    {
        info=DeleteAVL(&(*T)->child[1],e);
        if(info!=0)    return info;
        selectforDelete(T,&info,1);
    }
    return info;
}
//************************************************//




//*****************JUST FOR TEST************************//
#define MOVE(x)    (x=(x+1)%1000)
AVL queue[1000];


void print(AVL p,int i)
{
    int front,rear,temp,count;


    front=rear=-1; count=temp=0;
    queue[MOVE(rear)]=p; count++;
    
    printf("%d\n",i);
    while(front!=rear)
    {
        p=queue[MOVE(front)];    count--;
        
        
        if(p->child[0])    queue[MOVE(rear)]=p->child[0],temp++;
        if(p->child[1])    queue[MOVE(rear)]=p->child[1],temp++;
        
        printf("%d->%d ",p->e,p->bf);
        if(count==0)
        {
            printf("\n");
            count=temp;
            temp=0;
        }    
    }
    printf("\n");
}
//**************************************************//




int main()
{
    AVL T=NULL;
    int i,nodenum=0;


    freopen("input.txt","w",stdout);
    nodenum=100;
    for(i=0;i<nodenum;i++)
    {
        InsertAVL(&T,i);
    }
    
    print(T,-1);


    for(i=0;i<nodenum-1;i++)
    {
        DeleteAVL(&T,i);
        print(T,i);
    }
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值