AVL樹的原理及代碼實現

前言:

如果你還沒有學習過二叉查找樹,那麼請你先去看看二叉查找樹,因爲AVL樹便是從二叉查找樹進化而來的,不看的話你無法理解AVL樹。

鏈接:二叉查找樹的原理及實現

如果你已經學習了二叉查找樹,你會覺得二叉查找樹性能在各方面都很好,就只有一丟丟的小毛病,那就是當數據非常坑時,二叉查找樹退化成了一條單鏈表,這樣各種操作的時間複雜度都變爲O(n)了,怎麼辦呢,今天所要學習的AVL樹便以其驚豔四座的高端技巧解決了這一問題,使其在任何情況下的各種操作複雜度都爲O(logn)。

AVL樹:

AVL樹是根據它的發明者G.M. Adelson-Velsky和E.M. Landis命名的。
它是最先發明的自平衡二叉查找樹,也被稱爲高度平衡樹。

在介紹AVL樹時,首先要介紹一個概念——平衡度。

平衡度 = 左子樹的高度 - 右子樹的高度

當樹上所有節點平衡度爲-1,0,1時,我們認爲這棵樹是平衡的,當有節點的平衡度絕對值 > 1時,我們認爲這棵樹是不平衡的,我們就要對這個節點進行調整。

基本實現

存儲實現:

AVL樹與二叉查找樹一樣使用二叉鏈表實現,這樣能夠很好的理解,每個節點有一個元素存儲值,兩個指針分別指向它的左子樹和右子樹,還有一個元素來存儲該節點的高度。

template <class elemType>
class AvlTree{
private:
    
    struct node{
        elemType data;
        node *left;
        node *right;
        int height;
        
        node(const elemType &x, node *ln, node *rn, int h = 1):data(x), left(ln), right(rn), height(h){}
    };
    
    node *root;
}

find操作:

AVL樹的操作與二叉查找樹的find操作原理一模一樣,這裏就不詳細講了,想看的可以去文首的二叉查找樹的鏈接裏看。

此處代碼使用了非遞歸實現:

    elemType *find(const elemType &x){
        node *t = root;
        while(t != NULL && t -> data != x){
            if(t -> data > x){
                t = t -> right;
            }
            else{
                t = t -> left;
            }
        }
        
        if(t == NULL){
            return NULL;
        }
        else{
            return &(t -> data);
        }
    }

midOrder操作:

也與二叉查找樹完全相同,中序遍歷輸出整個樹的值,結果必然是一個從小到大序列。

    void midOrder(){
        midOrder(root);
    }
    void midOrder(node *p){
        if(p == NULL){
            return;
        }
        midOrder(p -> left);
        cout << p -> data << ' ';
        midOrder(p -> right);
    }

insert操作:

insert操作就與二叉查找樹有些不同了,它不但要找到合適位置插入元素,還要判斷插入後是不是破壞了樹的平衡性,如果破壞了要對樹進行調整。

先列舉幾種失去平衡的情況:

LL: 在節點的左子樹的左子樹上插入節點,使節點平衡度變爲2,失去平衡

LR:在節點的左子樹的右子樹上插入節點,使節點平衡度變爲2,失去平衡

RR:在節點的右子樹的右子樹上插入節點,使節點平衡度變爲-2,失去平衡

RL:在節點的右子樹的左子樹上插入節點,使節點平衡度變爲-2,失去平衡

下面來說解決辦法:

LL單旋轉:

如圖,k2失去平衡,k2的左子樹根節點k1頂替k2的位置,且k2的左指針指向其k1的右節點,k1的右指針指向k2,這樣整個樹恢復了平衡。

    void LL(node *t){
        node *t1 = t -> left;
        t -> left = t1 -> right;
        t1 -> right = t;
        t -> height = max(high(t -> left), high(t -> right)) + 1;
        t1 -> height = max(high(t1 -> left), high(t1 -> right)) + 1;
        t = t1;
    }

RR單旋轉: 

如圖,k1失去平衡,k1的左子樹根節點k2頂替k1的位置,且k1的右指針指向其k2的左節點,k2的左指針指向k1,這樣整個樹恢復了平衡。

    void RR(node *t){
        node *t1 = t -> right;
        t -> right = t1 -> left;
        t1 -> left = t;
        t -> height = max(high(t -> left), high(t -> right)) + 1;
        t1 -> height = max(high(t1 -> left), high(t1 -> right)) + 1;
        t = t1;
        
    }

LR雙旋轉:

如圖,k3失去了平衡,如果只對k3使用LL旋轉,那樣k2作爲k3的子樹,k1節點則會失去平衡,這時就需要兩次旋轉來實現。先對k1進行RR旋轉,再對k3進行LL旋轉,這樣就恢復了平衡,是不是被如此酷炫的操作亮瞎了雙眼。來看看代碼實現:

    void LR(node *t){
        RR(t -> left);
        LL(t);
    }

RL雙旋轉:

與LR對稱着來看就好。

    void RL(node *t){
        LL(t -> right);
        RR(t);
    }

學會了這些操作之後,insert操作就很好理解了,只要在插入後判斷一下是否平衡,若不平衡,對症下藥調整一下就好。注意要注意每次插入後要從插入點到根節點一個一個更新height值,也就是代碼的最後一行。

    void insert(const elemType &x, node *&t){
        if(t == NULL){
            t = new node(x, NULL, NULL);
        }
        else{
            if(x < t -> data){
                insert(x, t -> left);
                if(high(t -> left) - high(t -> right) == 2){
                    if(x < t -> left -> data){
                        LL(t);
                    }
                    else{
                        LR(t);
                    }
                }
            }
            else{
                if(x == t -> data){
                    cout << "The node has existed" << endl;
                    return;
                }
                else{
                    insert(x, t -> right);
                    if(high(t -> right) - high(t -> left) == 2){
                        if(x > t -> right -> data){
                            RR(t);
                        }
                        else{
                            RL(t);
                        }
                    }
                }
            }
        }
        t -> height = max(high(t -> left), high(t -> right)) + 1; //!!!
    }

remove操作:

同樣的道理,remove操作也在二叉查找樹的基礎上增加了判平衡操作,這裏我們使用到了反向思維

這是RL操作的圖:

舉個栗子,假入說A下面原來掛着一個x,現在我們把它刪了,是不是就跟上述情況一樣了呢。

其他幾種情況自己對照着上面的四種情況這樣理解一下,相信你會恍然大悟的。

    void remove(const elemType &x, node *&t){
        if(t == NULL){
            return;
        }
        if(x < t -> data){
            remove(x, t -> left);
            if(high(t -> right) - high(t -> left) == 2 ){
                if(t -> right -> left != NULL && high(t -> right -> left) > high(t -> right -> right)){
                    RL(t);
                }
                else{
                    RR(t);
                }
            }
        }
        else{
            if(x > t -> data){
                remove(x, t -> right);
                if(high(t -> left) - high(t -> right) == 2 ){
                    if(t -> left -> right != NULL && high(t -> left -> right) > high(t -> left -> left)){
                        LR(t);
                    }
                    else{
                        LL(t);
                    }
                }
                
            }
            else{   //==
                if(t -> left != NULL && t -> right != NULL){
                    node *tmp = t -> right;
                    while(tmp -> left != NULL){
                        tmp = tmp -> left;
                    }
                    t -> data = tmp -> data;
                    remove(t -> data, t -> right);
                    if(high(t -> left) - high(t -> right) == 2 ){
                        if(t -> left -> right != NULL && high(t -> left -> right) > high(t -> left -> left)){
                            LR(t);
                        }
                        else{
                            LL(t);
                        }
                    }
                }
                else{
                    node *old = t;
                    if(t -> left == NULL && t -> right == NULL){
                        delete old;
                    }
                    else{
                        if(t -> left!= NULL){
                            t = t -> left;
                        }
                        else{
                            t = t -> right;
                        }
                        delete old;
                    }
                }
            }
        }
        t -> height = max(high(t -> left), high(t -> right)) + 1;
    }

完整代碼:

#include <iostream>

using namespace std;

template <class elemType>
class AvlTree{
private:
    
    struct node{
        elemType data;
        node *left;
        node *right;
        int height;
        
        node(const elemType &x, node *ln, node *rn, int h = 1):data(x), left(ln), right(rn), height(h){}
    };
    
    node *root;
    
public:
    
    AvlTree(){
        root = NULL;
    }

    ~AvlTree(){
        clear(root);
    }
    
    void clear(node *t){
        if(t == NULL){
            return;
        }
        clear(t -> left);
        clear(t -> right);
        delete t;
    }
    
    elemType *find(const elemType &x){
        node *t = root;
        while(t != NULL && t -> data != x){
            if(t -> data > x){
                t = t -> right;
            }
            else{
                t = t -> left;
            }
        }
        
        if(t == NULL){
            return NULL;
        }
        else{
            return &(t -> data);
        }
    }
    
    void insert(const elemType &x){
        insert(x, root);
    }
    
    void insert(const elemType &x, node *&t){
        if(t == NULL){
            t = new node(x, NULL, NULL);
        }
        else{
            if(x < t -> data){
                insert(x, t -> left);
                if(high(t -> left) - high(t -> right) == 2){
                    if(x < t -> left -> data){
                        LL(t);
                    }
                    else{
                        LR(t);
                    }
                }
            }
            else{
                if(x == t -> data){
                    cout << "The node has existed" << endl;
                    return;
                }
                else{
                    insert(x, t -> right);
                    if(high(t -> right) - high(t -> left) == 2){
                        if(x > t -> right -> data){
                            RR(t);
                        }
                        else{
                            RL(t);
                        }
                    }
                }
            }
        }
        t -> height = max(high(t -> left), high(t -> right)) + 1;
    }
    
    void remove(const elemType &x){
        remove(x, root);
    }
    
    void remove(const elemType &x, node *&t){
        if(t == NULL){
            return;
        }
        if(x < t -> data){
            remove(x, t -> left);
            if(high(t -> right) - high(t -> left) == 2 ){
                if(t -> right -> left != NULL && high(t -> right -> left) > high(t -> right -> right)){
                    RL(t);
                }
                else{
                    RR(t);
                }
            }
        }
        else{
            if(x > t -> data){
                remove(x, t -> right);
                if(high(t -> left) - high(t -> right) == 2 ){
                    if(t -> left -> right != NULL && high(t -> left -> right) > high(t -> left -> left)){
                        LR(t);
                    }
                    else{
                        LL(t);
                    }
                }
                
            }
            else{   //==
                if(t -> left != NULL && t -> right != NULL){
                    node *tmp = t -> right;
                    while(tmp -> left != NULL){
                        tmp = tmp -> left;
                    }
                    t -> data = tmp -> data;
                    remove(t -> data, t -> right);
                    if(high(t -> left) - high(t -> right) == 2 ){
                        if(t -> left -> right != NULL && high(t -> left -> right) > high(t -> left -> left)){
                            LR(t);
                        }
                        else{
                            LL(t);
                        }
                    }
                }
                else{
                    node *old = t;
                    if(t -> left == NULL && t -> right == NULL){
                        delete old;
                    }
                    else{
                        if(t -> left!= NULL){
                            t = t -> left;
                        }
                        else{
                            t = t -> right;
                        }
                        delete old;
                    }
                }
            }
        }
        t -> height = max(high(t -> left), high(t -> right)) + 1;
    }
    
    int high(node *t){
        if(t == NULL){
            return 0;
        }
        else{
            return t -> height;
        }
    }
    
    void LL(node *t){
        node *t1 = t -> left;
        t -> left = t1 -> right;
        t1 -> right = t;
        t -> height = max(high(t -> left), high(t -> right)) + 1;
        t1 -> height = max(high(t1 -> left), high(t1 -> right)) + 1;
        t = t1;
    }
    
    void LR(node *t){
        RR(t -> left);
        LL(t);
    }
    
    void RR(node *t){
        node *t1 = t -> right;
        t -> right = t1 -> left;
        t1 -> left = t;
        t -> height = max(high(t -> left), high(t -> right)) + 1;
        t1 -> height = max(high(t1 -> left), high(t1 -> right)) + 1;
        t = t1;
        
    }
    
    void RL(node *t){
        LL(t -> right);
        RR(t);
    }
    
    int max(int a, int b){
        if(a > b){
            return a;
        }
        else{
            return b;
        }
    }
    
    void midOrder(){
        midOrder(root);
    }
    void midOrder(node *p){
        if(p == NULL){
            return;
        }
        midOrder(p -> left);
        cout << p -> data << ' ';
        midOrder(p -> right);
    }

    
};

總結

AVL樹很好的解決了平衡二叉樹在特殊情況下會退化成單鏈表的問題,這樣AVL樹就一直會保持在矮胖的狀態,而不會成爲一顆瘦高的樹。AVL樹的查找、插入和刪除在平均和最壞情況下都是O(logn)。只要學好AVL樹的旋轉操作,就能學好AVL樹。

 

(以上圖片全部來自於skywang12345的博客:http://www.cnblogs.com/skywang12345/p/3576969.html,沒辦法,我畫的話可能此生都別想看懂AVL樹了)

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章