手把手教,手寫AVL樹

不止是程式設計發表於2019-06-11

 

平衡二叉樹相信大家都不陌生了,它是一顆平衡的二叉排序樹,它的查詢、插入刪除的時間複雜度均為O(logn),是非常好且重要的動態查詢結構,因此被廣泛的應用。而平衡二叉樹的實現因為引入了旋轉操作而經常讓人難以理解,樓主寫這篇文章的目的,其實是希望能夠通過造輪子的方式,幫助到大家完全理解從普通二叉排序樹逐步優化到平衡二叉樹(AVL樹)的過程。

一、平衡二叉樹之AVL樹簡介

 

而AVL樹是一種高度平衡的平衡二叉樹,樹中的任意節點的左右子樹的高度之差不超過1。如果將二叉樹上結點的平衡因子BF(Balanced Factor)定義為該結點的左子樹與右子樹的高度之差,根據AVL樹的定義,AVL樹中的任意結點的平衡因子只可能是-1(右子樹高於左子樹)、0或者1(左子樹高於右子樹),在某些圖中也會表示為絕對高度差,即0,1,2這種形式,請注意理解。它通過樹的旋轉操作來維護平衡特性。下面我會逐步通過C++程式碼來理解、最終實現AVL樹。

二、AVL樹實現

1.二叉排序樹實現

AVL樹是一顆平衡的二叉排序樹,所以為了實現AVL樹,我們可以先從實現一顆普通的二叉排序樹入手,實現一顆普通二叉排序樹。為了接下來將二叉排序樹優化為AVL樹,程式碼中將樹直接取名為AVL。

樹中節點具有雙親和左右兩個子節點的指標,

插入刪除操作都是先查詢到節點位置,再進行插入或者刪除。

刪除時,如果刪除的是同時有左右子樹的非葉子節點,則尋找該節點的後繼節點,替換該節點再將後繼節點刪除,這裡不做過多解釋,程式碼如下:

#include<stdio.h>
#include<malloc.h>

struct AVLNode{
    int value;
    int balance;  //AVL樹的平衡欄位,普通二叉排序樹並不需要用到該欄位
    AVLNode* left;
    AVLNode* right;
    AVLNode* parent;
};
//這是一顆普通的二叉排序樹,,命名為AVLTree僅僅因為方便起見
struct AVLTree{
    AVLNode* root;
}tree;
//查詢操作
struct AVLNode* select(int value,AVLNode* root){
    if(root==0){
        return 0;
    }
    if(root->value==value){
        return root;
    }
    if(root->value>value){
        if(root->left)
            return select(value,root->left);
        else
            return root;
    }
    if(root->value<value){
        if(root->right)
            return select(value,root->right);
        else
            return root;
    }
}
//插入操作
void insert(int value,AVLNode* root){
    AVLNode* node=select(value,root);
    if(node==0){
        tree.root=(AVLNode*)malloc(sizeof(AVLNode));
        tree.root->value=value;
        tree.root->left=tree.root->right=0;
        tree.root->parent=0;
        tree.root->balance=0;
    }
    else if(node->value!=value){
        if(node->value>value){
            node->left=(AVLNode*)malloc(sizeof(AVLNode));
            node->left->value=value;
            node->left->left=node->left->right=0;
            node->left->parent=node;
            node->left->balance=0;
        }else if(node->value<value){
            node->right=(AVLNode*)malloc(sizeof(AVLNode));
            node->right->value=value;
            node->right->left=node->right->right=0;
            node->right->parent=node;
            node->right->balance=0;
        }
    }
}
void delnodeifhas1childornot(AVLNode* a){
    if(a->parent==0){
        if(a->left){
            tree.root=a->left;
            a->left->parent=0;
        }else{
            tree.root=a->right;
            a->right->parent=0;
        }
    }else{
        if(a->parent->left==a){
            if(a->left){
                a->parent->left=a->left;
                a->left->parent=a->parent;
            }else{
                a->parent->left=a->right;
                if(a->right)
                    a->right->parent=a->parent;
            }
        }else{
            if(a->left){
                a->parent->right=a->left;
                a->left->parent=a->parent;
            }else{
                a->parent->right=a->right;
                if(a->right)
                    a->right->parent=a->parent;
            }
        }
    }
}
struct AVLNode* getmin(AVLNode* a){
    if(a->left)
        getmin(a->left);
    else
        return a;
} 
void delnodeifhas2child(AVLNode* a){
    AVLNode* after=getmin(a->right);
    a->value=after->value;
    delnodeifhas1childornot(after);
}
//刪除操作
void del(int value,AVLNode* root){ AVLNode* node=select(value,root); if(node->value==value){ if(node->left&&node->right){ delnodeifhas2child(node); }else{ delnodeifhas1childornot(node); } } }

 

2.從二叉排序樹到AVL樹

2.1 旋轉操作原理

AVL樹通過左旋右旋來實現它的平衡性。理解左旋和右旋是AVL樹實現的關鍵點,那麼問題來了,所謂左旋右旋又具體是怎麼樣的操作呢?以右旋為例。

初始的AVL樹有兩個節點,a和b,此時樹平衡。

              

 

插入節點c,若節點c小於b,則成為b的左子節點,此時a左子樹深度為2,右子樹深度為0,處於不平衡狀態,此時執行右旋操作。

                           

右旋就是使a、b進行右旋1/4圓,使得b節點作為父節點,a節點變為b節點的右節點,b的左子樹在經過右旋操作後仍是b的左子樹。

如果b有右子樹。那麼右旋後b的右子樹將成為a的左子樹。

 

這就是右旋操作的具體實現,左旋是右旋對稱的過程。當二叉樹插入或者刪除一個節點時,如果破壞了樹的平衡性,就會根據樹的失衡情況進行旋轉操作,如果左子樹高於右子樹,則右旋,如果右子樹高於左子樹,則左旋。

右旋及左旋的程式碼如下:



struct
AVLNode* turnRight(AVLNode* a){ // 右旋使a的父節點的子節點替換為b AVLNode* b=a->left; if(a->parent!=0){ if(a->parent->right==a){ a->parent->right=b; }else{ a->parent->left=b; } } b->parent=a->parent; //將b的右子樹作為a的左子樹,並將a作為b的右子樹 a->parent=b; a->left=b->right; if(a->left!=0) a->left->parent=a; b->right=a; //重新設定a、b節點的balance值 setBalance(a); setBalance(b); //返回b節點 return b; }

struct AVLNode* turnLeft(AVLNode* a){
    //左旋把a的父節點的子節點替換為b
    AVLNode* b=a->right;
    if(a->parent!=0){
        if(a->parent->right==a){
            a->parent->right=b;
        }else{
            a->parent->left=b;
        }
    }
    b->parent=a->parent;
    //將a作為b的左子樹,並將b的左子樹作為a的右子樹
    a->parent=b;
    a->right=b->left;
    b->left=a;
    if(a->right!=0)
        a->right->parent=a;
    //重新設定a\b的balance值
    setBalance(a);
    setBalance(b);
//返回b節點
return b; }
int height(AVLNode* a){
    if(a==0){
        return 0;
    }
    int rightheight=height(a->right);
    int leftheight=height(a->left);
    return rightheight > leftheight ? (rightheight+1) : (leftheight+1);
}
void setBalance(AVLNode* a){
    if(a)
        a->balance=height(a->right)-height(a->left);
}

上面將樹從失衡狀態恢復到平衡狀態只進行了一次旋轉操作,事實上還有一種稍微複雜的情況,需要進行兩次旋轉才能將樹重新恢復平衡

 

如果節點插入到a的左子樹的右節點,則需要先左旋再右旋來重新平衡樹。同理,如果節點插入到a的右子樹的左子節點,則需要先右旋再左旋來重新平衡樹。如果直接進行右旋,生成的樹將仍然是失衡的。

程式碼如下,呼叫了上面實現的旋轉操作:

struct AVLNode* turnLeftThenRight(AVLNode* a){
    a->left = turnLeft(a->left);
    return turnRight(a);
}
struct AVLNode* turnRightThenLeft(AVLNode* a){
    a->right = turnRight(a->right);
    return turnLeft(a);
}

2.2 實現AVL樹插入刪除

 AVL樹的insert操作,僅僅在二叉排序樹的insert基礎上增加了rebalance操作。

//AVL樹在insert或者del之後進行rebalance操作,從插入節點的父節點開始,逐層向上遍歷進行rebalance,直到遍歷到根節點,由於樹是平衡的,所以rebalance的時間複雜度是O(logn)。
void rebalance(AVLNode* a){
    setBalance(a);
    if(a->balance== -2){                   //如果左子樹比右子樹高2層,需要通過旋轉來重新平衡
        if(a->left->balance <=0){          //如果左子節點的左子樹比右子樹高,則進行一次右旋;如果左子節點的右子樹比左子樹高,則先進行左旋,再進行右旋。
            a=turnRight(a);
        }else{
            a=turnLeftThenRight(a);
        }
    }else if(a->balance==2){       //如果右子樹比左子樹高2層,需要通過旋轉來重新平衡
        if(a->right->balance>=0){       //如果右子節點的右子樹比左子樹高,則進行一次左旋;如果右子節點的右子樹比左子樹低,則先進行右旋,再進行左旋。
            a=turnLeft(a);
        }else{
            a=turnRightThenLeft(a);
        }
    }
    if(a->parent){
        rebalance(a->parent);
    }else{
        tree.root=a;
    }
}

void insert(int value,AVLNode* root){
    AVLNode* node=select(value,root);
    if(node==0){
        tree.root=(AVLNode*)malloc(sizeof(AVLNode));
        tree.root->value=value;
        tree.root->left=tree.root->right=0;
        tree.root->parent=0;
        tree.root->balance=0;
    }
    else if(node->value!=value){
        if(node->value>value){
            node->left=(AVLNode*)malloc(sizeof(AVLNode));
            node->left->value=value;
            node->left->left=node->left->right=0;
            node->left->parent=node;
            node->left->balance=0;
            rebalance(node);           //插入完成後進行rebalance
        }else if(node->value<value){
            node->right=(AVLNode*)malloc(sizeof(AVLNode));
            node->right->value=value;
            node->right->left=node->right->right=0;
            node->right->parent=node;
            node->right->balance=0;
            rebalance(node);           //插入完成後進行rebalance
        }
    }
}

 

AVL樹的刪除操作,也只是在二叉排序樹的刪除操作上,增加了rebalance操作。

void delnodeifhas1childornot(AVLNode* a){
    if(a->parent==0){
        if(a->left){
            tree.root=a->left;
            a->left->parent=0;
        }else{
            tree.root=a->right;
            a->right->parent=0;
        }
    }else{
        if(a->parent->left==a){
            if(a->left){
                a->parent->left=a->left;
                a->left->parent=a->parent;
            }else{
                a->parent->left=a->right;
                if(a->right)
                    a->right->parent=a->parent;
            }
        }else{
            if(a->left){
                a->parent->right=a->left;
                a->left->parent=a->parent;
            }else{
                a->parent->right=a->right;
                if(a->right)
                    a->right->parent=a->parent;
            }
        }
        rebalance(a->parent);
    }
}
struct AVLNode* getmin(AVLNode* a){
    if(a->left)
        getmin(a->left);
    else
        return a;
} 
void delnodeifhas2child(AVLNode* a){
    AVLNode* after=getmin(a->right);
    a->value=after->value;
    delnodeifhas1childornot(after);
}
void del(int value,AVLNode* root){         //刪除操作
    AVLNode* node=select(value,root);
    if(node->value==value){
        if(node->left&&node->right){
            delnodeifhas2child(node);
        }else{
            delnodeifhas1childornot(node);
        }
    }
}

完整AVL樹c++程式碼如下:

#include<stdio.h>
#include<malloc.h>

struct AVLNode{
    int value;
    int balance;
    AVLNode* left;
    AVLNode* right;
    AVLNode* parent;
};

struct AVLTree{
    AVLNode* root;
}tree;

int height(AVLNode* a){
    if(a==0){
        return 0;
    }
    int rightheight=height(a->right);
    int leftheight=height(a->left);
    return rightheight > leftheight ? (rightheight+1) : (leftheight+1);
}
struct AVLNode* select(int value,AVLNode* root){
    if(root==0){
        return 0;
    }
    if(root->value==value){
        return root;
    }
    if(root->value>value){
        if(root->left)
            return select(value,root->left);
        else
            return root;
    }
    if(root->value<value){
        if(root->right)
            return select(value,root->right);
        else
            return root;
    }
}
void setBalance(AVLNode* a){
    if(a)
        a->balance=height(a->right)-height(a->left);
}

struct AVLNode* turnLeft(AVLNode* a){
    AVLNode* b=a->right;
    if(a->parent!=0){
        if(a->parent->right==a){
            a->parent->right=b;
        }else{
            a->parent->left=b;
        }
    }
    b->parent=a->parent;
    a->parent=b;
    a->right=b->left;
    b->left=a;
    if(a->right!=0)
        a->right->parent=a;
    setBalance(a);
    setBalance(b);
    return b;
}
struct AVLNode* turnRight(AVLNode* a){
    AVLNode* b=a->left;
    if(a->parent!=0){
        if(a->parent->right==a){
            a->parent->right=b;
        }else{
            a->parent->left=b;
        }
    }
    b->parent=a->parent;
    a->parent=b;
    a->left=b->right;
    if(a->left!=0)
        a->left->parent=a;
    b->right=a;
    setBalance(a);
    setBalance(b);
    return b;
}

struct AVLNode* turnLeftThenRight(AVLNode* a){
    a->left = turnLeft(a->left);
    return turnRight(a);
}
struct AVLNode* turnRightThenLeft(AVLNode* a){
    a->right = turnRight(a->right);
    return turnLeft(a);
}
void rebalance(AVLNode* a){
    setBalance(a);
    if(a->balance== -2){
        if(a->left->balance <=0){
            a=turnRight(a);
        }else{
            a=turnLeftThenRight(a);
        }
    }else if(a->balance==2){
        if(a->right->balance>=0){
            a=turnLeft(a);
        }else{
            a=turnRightThenLeft(a);
        }
    }
    if(a->parent){
        rebalance(a->parent);
    }else{
        tree.root=a;
    }
}

void insert(int value,AVLNode* root){
    AVLNode* node=select(value,root);
    if(node==0){
        tree.root=(AVLNode*)malloc(sizeof(AVLNode));
        tree.root->value=value;
        tree.root->left=tree.root->right=0;
        tree.root->parent=0;
        tree.root->balance=0;
    }
    else if(node->value!=value){
        if(node->value>value){
            node->left=(AVLNode*)malloc(sizeof(AVLNode));
            node->left->value=value;
            node->left->left=node->left->right=0;
            node->left->parent=node;
            node->left->balance=0;
            rebalance(node);
        }else if(node->value<value){
            node->right=(AVLNode*)malloc(sizeof(AVLNode));
            node->right->value=value;
            node->right->left=node->right->right=0;
            node->right->parent=node;
            node->right->balance=0;
            rebalance(node);
        }
    }
}
void delnodeifhas1childornot(AVLNode* a){
    if(a->parent==0){
        if(a->left){
            tree.root=a->left;
            a->left->parent=0;
        }else{
            tree.root=a->right;
            a->right->parent=0;
        }
    }else{
        if(a->parent->left==a){
            if(a->left){
                a->parent->left=a->left;
                a->left->parent=a->parent;
            }else{
                a->parent->left=a->right;
                if(a->right)
                    a->right->parent=a->parent;
            }
        }else{
            if(a->left){
                a->parent->right=a->left;
                a->left->parent=a->parent;
            }else{
                a->parent->right=a->right;
                if(a->right)
                    a->right->parent=a->parent;
            }
        }
        rebalance(a->parent);
    }
}
struct AVLNode* getmin(AVLNode* a){
    if(a->left)
        getmin(a->left);
    else
        return a;
} 
void delnodeifhas2child(AVLNode* a){
    AVLNode* after=getmin(a->right);
    a->value=after->value;
    delnodeifhas1childornot(after);
}
void del(int value,AVLNode* root){
    AVLNode* node=select(value,root);
    if(node->value==value){
        if(node->left&&node->right){
            delnodeifhas2child(node);
        }else{
            delnodeifhas1childornot(node);
        }
    }
}
void insertNodetest(){
    insert(5,tree.root);
    insert(6,tree.root);
    insert(7,tree.root);
    insert(3,tree.root);
    insert(4,tree.root);
    insert(4,tree.root);
    insert(5,tree.root);
    insert(5,tree.root);
    insert(3,tree.root);
    insert(8,tree.root);
    insert(9,tree.root);
    insert(10,tree.root);
    insert(11,tree.root);
    insert(12,tree.root);
}
void delNodetest(){
    del(6,tree.root);
    del(11,tree.root);
    del(12,tree.root);
    del(3,tree.root);
    del(3,tree.root);
    del(5,tree.root);
    del(9,tree.root);
    del(12,tree.root);
}
int main(){
    insertNodetest(); // 測試構建AVL樹
    delNodetest();    // 測試刪除AVL樹節點
}

至此,我們就完全實現了一顆AVL樹的插入刪除和查詢功能。

 

 (歡迎加qq:1363890602,討論qq群:297572046,備註:程式設計藝術)

相關文章