平衡二叉树AVL的插入及删除操作

转载于https://blog.csdn.net/sysu_arui/article/details/7897017 及https://blog.csdn.net/sysu_arui/article/details/7906303 

AVL树维基百科:http://zh.wikipedia.org/wiki/AVL树

在计算机科学中,AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下都是O(log n)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。AVL树得名于它的发明者G.M. Adelson-Velsky和E.M. Landis,他们在1962年的论文《An algorithm for the organization of information》中发表了它。

原理请看上面维基百科词条,可以参考严蔚敏数据结构或其它书籍,这里就不对原理做过多解释了,下面将直接给出其实现,代码有详细注释。

1、基本约定

使用平衡二叉树就是为了高效的查找,一般是根据关键字查找记录,而记录一般是复杂的类型对象。这里我们以一个Student类作为记录类型,学号作为关键字。

我们假定所使用的元素类型,都能进行各种比较和赋值。用LH,EH,RH分别表示左子树高,等高,右子树高,即平衡因子-1、0、1。

#define LH +1 //左高 
#define EH 0  //等高
#define RH -1 //右高
 
#define EQ(a,b) ((a) == (b))
#define LT(a,b) ((a) < (b))
#define LQ(a,b) ((a) <= (b))
 
//结点元素类型 
typedef struct Student
{
    int key;
    string major;
    Student(){}
    Student(int k,string s) : key(k), major(s){}
}ElementType;
 
ostream& operator<<(ostream& out, const Student& s)
{
    out<<"("<<s.key<<","<<s.major<<")";
    return out;
}
 
istream& operator>>(istream& in,Student& s)
{
    in>>s.key>>s.major;
}
 
typedef int KeyType;//关键字类型 
 
typedef struct AVLNode
{
    ElementType data;
    int bf;
    struct AVLNode* lchild;
    struct AVLNode* rchild;
    
    AVLNode(){}
    AVLNode(ElementType& e, int ibf=EH, AVLNode* lc=NULL, AVLNode* rc=NULL)
        : data(e), bf(ibf), lchild(lc),rchild(rc){}
}AVLNode, *AVL;
2、初始化、销毁

/*
  *Description: 初始化(其实可以不用)
*/
void initAVL(AVL& t)
{
    t = NULL;
}
 
/*
  *Description: 销毁平衡二叉树 
*/
void destroyAVL(AVL& t)
{
    if(t)
    {
        destroyAVL(t->lchild);
        destroyAVL(t->rchild);
        delete t;
        t = NULL;
    }
}
3、遍历和查找
//前序遍历
void preOrderTraverse(AVL t)
{
    if(t)
    {
        cout<<t->data<<" ";
        preOrderTraverse(t->lchild);
        preOrderTraverse(t->rchild);
    }

 
//中序遍历
void inOrderTraverse(AVL t)
{
    if(t)
    {
        inOrderTraverse(t->lchild);
        cout<<t->data<<" ";
        inOrderTraverse(t->rchild);
    }

 
//以前序和中序输出平衡二叉树
void printAVL(AVL t)
{
    cout<<"inOrder: "<<endl;
    inOrderTraverse(t);
    cout<<endl;
    cout<<"preOrder: "<<endl;
    preOrderTraverse(t);
    cout<<endl;
}
 
/*
  Description: 
        在根指针t所指平衡二叉树中递归地查找某关键字等于key的数据元素, 
        若查找成功,则返回指向该数据元素结点的指针,否则返回空指针。
        根据需要,也可以返回一个bool值 
*/
AVLNode* searchAVL(AVL& t, KeyType key)
{
    if((t == NULL)||EQ(key,t->data.key))
        return t; 
       else if LT(key,t->data.key) /* 在左子树中继续查找 */
         return searchAVL(t->lchild,key);
       else
         return searchAVL(t->rchild,key); /* 在右子树中继续查找 */
}

4、旋转处理
左旋和右旋,大家记住“左逆右顺”就可以了。

(1)左旋-逆时针旋转(如RR型就得对根结点做该旋转)

扫描二维码关注公众号,回复: 4715506 查看本文章

/*
  Description: 
        对以*p为根的二叉排序树作左旋处理,处理之后p指向新的树根结点,即旋转 
        处理之前的右子树的根结点。也就是书上说说的RR型. 
*/
void L_Rotate(AVLNode* &p)
{
    AVLNode * rc = NULL;
    rc = p->rchild;            //rc指向p的右子树根结点
    p->rchild = rc->lchild;//rc的左子树挂接为p的右子树 
    rc->lchild = p;
    p = rc;                    //p指向新的根结点 
}
(2)右旋-顺时针旋转(如LL型就得对根结点做该旋转)

/*
  Description:
        对以*p为根的二叉排序树作右旋处理,处理之后p指向新的树根结点,即旋转 
        处理之前的左子树的根结点。也就是书上说说的LL型. 
*/
void R_Rotate(AVLNode* &p)

    AVLNode * lc = NULL;
    lc  = p->lchild;        //lc指向p的左子树根结点
    p->lchild = lc->rchild;    //lc的右子树挂接为p的左子树 
    lc->rchild = p;
    p = lc;                    //p指向新的根结点 
}

5、左平衡处理
所谓左平衡处理,就是某一根结点的左子树比右子树过高,从而失去了平衡。

(1)插入时如果需要左平衡处理,根结点左子树根平衡因子只可能为LH和RH。

(2)删除和插入不同,根结点左子树根的平衡因子三种情况都可能出现,因为是删除根结点右子树中的结点从而引起左子树过高,在删除前,根结点左子树根的平衡因子是可以为EH的,此种情况同样是对根结点做简单右旋处理。

/*对以指针t所指结点为根的二叉树作左平衡旋转处理
    包含LL旋转和LR旋转两种情况 
    平衡因子的改变其实很简单,自己画图就出来了 
*/
void leftBalance(AVLNode* &t)
{
    AVLNode* lc = NULL;
    AVLNode* rd = NULL;
    lc = t->lchild;
    switch(lc->bf)
    {
        case LH:                    //LL旋转 
            t->bf = EH;
            lc->bf = EH;
            R_Rotate(t);        
            break;
        
        case EH:                    //deleteAVL需要,insertAVL用不着 
            t->bf = LH;
            lc->bf = RH;
            R_Rotate(t);
            break;
        
        case RH:                    //LR旋转 
            rd = lc->rchild;
            switch(rd->bf)
            {
                case LH:
                    t->bf = RH;
                    lc->bf = EH;
                    break;    
                case EH:
                    t->bf = EH;
                    lc->bf = EH;
                    break;
                case RH:
                    t->bf = EH;
                    lc->bf = LH;
                    break;
            }
            rd->bf = EH;
            L_Rotate(t->lchild);//不能写L_Rotate(lc);采用的是引用参数 
            R_Rotate(t);
            break;
    }
}

6、右平衡处理
类似左平衡处理,所谓右平衡处理,就是某一根结点的右子树比左子树过高,从而失去了平衡。

(1)插入时如果需要右平衡处理,根结点右子树根平衡因子只可能为LH和RH。

(2)删除和插入不同,根结点右子树根的平衡因子三种情况都可能出现,因为是删除根结点左子树中的结点从而引起右子树过高,在删除前,根结点右子树根的平衡因子是可以为EH的,此种情况同样是对根结点做简单左旋处理。

/*对以指针t所指结点为根的二叉树作右平衡旋转处理
    包含RR旋转和RL旋转两种情况 
*/
void rightBalance(AVLNode* &t)
{
    AVLNode* rc = NULL;
    AVLNode *ld = NULL;
    
    rc = t->rchild;
    switch(rc->bf)
    {
        case LH:                //RL旋转 
            ld = rc->lchild; 
            switch(ld->bf)
            {
                case LH:
                    t->bf = EH;
                    rc->bf = RH;
                    break;
                case EH:
                    t->bf = EH;
                    rc->bf = EH;
                    break;
                case RH:
                    t->bf = LH;
                    rc->bf = EH;
                    break;
            }
            ld->bf = EH;
            R_Rotate(t->rchild);//不能写R_Rotate(rc);采用的是引用参数 
            L_Rotate(t);
            break;
            
        case EH:                //deleteAVL需要,insertAVL用不着 
            t->bf = RH;
            rc->bf = LH;
            L_Rotate(t);
            break;
                
        case RH:                //RR旋转 
            t->bf = EH;
            rc->bf = EH;
            L_Rotate(t);
            break;
    }
}

7、插入处理
在插入一个元素时,总是插入在一个叶子结点上。我们采用递归插入,也就是不断搜索平衡二叉树,找到一个合适的插入点(当然相同关键字不插入)。插入后,引起的第一个不平衡的子树的根结点,一定是在查找路径上离该插入点最近的,注意看代码中递归后的回溯。

/* 
若在平衡的二叉排序树t中不存在和e有相同关键字的结点,则插入一个 
数据元素为e的新结点,并返回true,否则返回false。若因插入而使二叉排序树 
失去平衡,则作平衡旋转处理,布尔变量taller反映t长高与否
*/
bool insertAVL(AVL& t, ElementType& e, bool& taller)
{
    if(t == NULL)
    {
        t = new AVLNode(e);                //插入元素 
        taller = true;
    }
    else
    {
        if(EQ(e.key, t->data.key))        //树中已含该关键字,不插入 
        {
            taller = false;
            return false;
        }
        else if(LT(e.key, t->data.key))//在左子树中查找插入点 
        {
            if(!insertAVL(t->lchild, e, taller))//左子树插入失败 
            {
                return false;
            }
            if(taller)                    //左子树插入成功,且左子树增高 
            {
                switch(t->bf)
                {
                    case LH:            //原来t的左子树高于右子树 
                        leftBalance(t); //做左平衡处理 
                        taller = false;
                        break;
                    case EH:            //原来t的左子树和右子树等高 
                        t->bf = LH;        //现在左子树比右子树高 
                        taller = true;    //整棵树增高了 
                        break;
                    case RH:            //原来t的右子树高于左子树
                        t->bf = EH;        //现在左右子树等高 
                        taller = false;
                        break;
                }
            }
        }
        else                            //在右子树中查找插入点 
        {
            if(!insertAVL(t->rchild, e, taller))//右子树插入失败 
            {
                return false;
            }
            if(taller)                    //右子树插入成功,且右子树增高
            {
                switch(t->bf)
                {
                    case LH:            //原来t的左子树高于右子树 
                        t->bf = EH;
                        taller = false;
                        break;
                    case EH:            //原来t的左子树和右子树等高 
                        t->bf = RH;
                        taller = true;
                        break;
                    case RH:            //原来t的右子树高于左子树
                        rightBalance(t);//做右平衡处理
                        taller = false;
                        break;
                }
            }
        }
    }
    return true;                        //插入成功 
}

8、删除处理
删除和插入不同的是,删除的结点不一定是叶子结点,可能是树中的任何一个结点。前面在讲解二叉查找树时,我们知道删除的结点可能有三种情况:(1)为叶子结点,(2)左子树或右子树有一个为空,(3)左右子树都不空。对第三种情况的处理我们介绍了三种处理方式,这里我们采用删除前驱的方式。注意到我们仍然采用的是递归删除,然后判断删除后树是否“变矮”了,然后进行相应的处理。对(1)(2)中情况,很好处理,树的确是“变矮”了。对于第(3)种情况,我们不能直接找到前驱结点,然后把数据拷贝到原本要删除的根结点,最后直接删除前驱结点。因为这么做,我们无法判断原先根结点子树高度的变化情况。所以我们在找到前驱结点后,不是直接删除,而是采用在根结点左子树中递归删除前驱的方式。

/* 
若在平衡的二叉排序树t中存在和e有相同关键字的结点,则删除之 
并返回true,否则返回false。若因删除而使二叉排序树 
失去平衡,则作平衡旋转处理,布尔变量shorter反映t变矮与否
*/
bool deleteAVL(AVL& t, KeyType key, bool& shorter)
{
    if(t == NULL)                        //不存在该元素 
    {
        return false;                    //删除失败 
    }
    else if(EQ(key, t->data.key))        //找到元素结点
    {
        AVLNode* q = NULL;
        if(t->lchild == NULL)            //左子树为空 
        {
            q = t;
            t = t->rchild;
            delete q;
            shorter = true;
        }
        else if(t->rchild == NULL)        //右子树为空 
        {
            q = t;
            t = t->lchild;
            delete q;
            shorter = true;
        }
        else                            //左右子树都存在,
        {
            q = t->lchild;
            while(q->rchild)
            {
                q = q->rchild;
            }
            t->data = q->data;
            deleteAVL(t->lchild, q->data.key, shorter);    //在左子树中递归删除前驱结点 
        }
    }
    else if(LT(key, t->data.key))        //左子树中继续查找 
    {
        if(!deleteAVL(t->lchild, key, shorter))
        {
            return false;
        }
        if(shorter)
        {
            switch(t->bf)
            {
                case LH:
                    t->bf = EH;
                    shorter = true;
                    break;
                case EH:
                    t->bf = RH;
                    shorter = false;
                    break;
                case RH:
                    rightBalance(t);    //右平衡处理
                    if(t->rchild->bf == EH)//注意这里,画图思考一下 
                        shorter = false;
                    else
                        shorter = true;
                    break;
            }
        }
    }
    else                                //右子树中继续查找 
    {
        if(!deleteAVL(t->rchild, key, shorter))
        {
            return false;
        }
        if(shorter)
        {
            switch(t->bf)
            {
                case LH:
                    leftBalance(t);        //左平衡处理 
                    if(t->lchild->bf == EH)//注意这里,画图思考一下 
                        shorter = false;
                    else
                        shorter = true;
                    break;
                case EH:
                    t->bf = LH;
                    shorter = false;
                    break;
                case RH:
                    t->bf = EH;
                    shorter = true;
                    break;
            }
        }
    }
    return true;
}

1、测试代码

为减小篇幅,只给出了主程序,其他函数模块请看(上)中的描述。

#include <cstdlib>
#include <iostream>
#include <string>
 
using namespace std;
 
int main(int argc, char *argv[])
{
    AVL t ;
    initAVL(t);
    bool taller = false;
    bool shorter = false;
    int key;
    string major;
    ElementType e;
    int choice = -1;
    bool flag = true;
    
    while(flag)
    {
        cout<<"--------------------"<<endl;
        cout<<"0. print"<<endl
            <<"1. insert"<<endl 
            <<"2. delete"<<endl
            <<"3. search"<<endl
            <<"4. exit"<<endl
            <<"--------------------"<<endl
            <<"please input your choice: ";
        cin>>choice;
        switch(choice)
        {
            case 0:
                printAVL(t);
                cout<<endl<<endl;
                break;
            case 1:
                inOrderTraverse(t);
                cout<<endl<<"input the elements to be inserted,end by 0:"<<endl;
                while(cin>>key && key)
                {
                    cin>>major;
                    ElementType e(key,major);
                    if(insertAVL(t,e,taller))
 
                    {
                        cout<<"insert element "<<e<<" successfully"<<endl;
                    }
                    else
                    {
                        cout<<"there already exists an element with key "<< e.key<<endl;
                    }
                }
                //while(cin>>e && e.key)
//                {
//                    if(insertAVL(t,e,taller))
//                    {
//                        cout<<"insert element "<<e<<" successfully"<<endl;
//                    }
//                    else
//                    {
//                        cout<<"there already exists an element with key "<< e.key<<endl;
//                    }
//                }
                cout<<"after insert: "<<endl;
                printAVL(t);
                cout<<endl<<endl;
                break;
                
            case 2:
                inOrderTraverse(t);
                cout<<endl<<"input the keys to be deleted,end by 0:"<<endl;
                while(cin>>key && key)
                {
                    if(deleteAVL(t,key,shorter))
                    {
                        cout<<"delete the element with key "<<key<<" successfully"<<endl;
                    }
                    else
                    {
                        cout<<"no such an element with key "<<key<<endl;
                    }
                }
                cout<<"after delete: "<<endl;
                printAVL(t);
                cout<<endl<<endl;
                break;
                
            case 3:
                inOrderTraverse(t);
                cout<<endl<<"input the keys to be searched,end by 0:"<<endl;
                while(cin>>key && key)
                {
                    if(searchAVL(t,key))
                        cout<<key<<" is in the tree"<<endl;
                    else
                        cout<<key<<" is not in the tree"<<endl;
                }
                cout<<endl<<endl;
                break;
                
            case 4:
                flag = false;
                break;
                
            default:
                cout<<"error! watch and input again!"<<endl<<endl;
                
        }
    }
    destroyAVL(t);
    
    system("PAUSE");
    return EXIT_SUCCESS;
}

注:
(1)前面我们对元素类型输入和输出操作符进行了重载,这里可以直接输入和输出。当然也可以采用先获取数据成员,然后构造对象的方式。

(2)请注意上面代码的I/O格式,下面的测试用例会给出示例。我们假设没有关键字为0,即采用0作为输入结束。

2、测试用例

(1)  输入1,开始insert。接着输入要插入的数据元素,每行一个(学号和专业之间以空格分隔),如果采用的是重载>>后的输入方式,那么以 0 0作为结束,如果采用的是另外的方式,直接输入0结束,上面的代码插入删除查找都是以0作为输入结束。

20 dm

10 english

5 physics

30 chinese

40 language

15 japanese

25 biology

23 mathematics

50 chemistry

1 physics

3 geography

0

插入完成后,会给出提示,最后给出前序和中序输出。可以对比下面的图看是否正确。

(2)  输入3,进行search。依次输入1 2 3 5 7 8 10 13 15 17 20 23 30 31 50 60 0

观察输出结果看是否正确

(3)  输入2,进行delete。依次输入15 23 25 1 30 50 40 3 0

(4)  输入1,打印平衡二叉树。比较看看输出和自己画的是否相符。

3、插入删除图例

1)依次插入20、10、5、30、40、15、25、23、50、1、3

(2)在上图中依次删除15、23、25、1、30、50、40、3

猜你喜欢

转载自blog.csdn.net/weixin_41265887/article/details/84575756
今日推荐