1. 程式人生 > >平衡二叉樹AVL的插入及刪除操作

平衡二叉樹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型就得對根結點做該旋轉)

/*
  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