二叉樹的基本操作(三)——AVL樹的性質和插入操作

參考資料:
http://www.cnblogs.com/QG-whz/p/5167238.html

一、AVL樹的概念
AVL樹的名字來源於它的發明作者G.M. Adelson-Velsky 和 E.M. Landis。AVL樹是最先發明的自平衡二叉查找樹(Self-Balancing Binary Search Tree,簡稱平衡二叉樹)。查找AVL樹的時間複雜度很低,只有O(logN)。
一棵AVL樹有如下必要條件
條件一:它必須是二叉查找樹
條件二:每個節點的左子樹和右子樹的高度差的絕對值至多爲1
既然這裏提到了AVL樹的高度差,那麼在設計AVL樹的結構時,除了二叉樹常規的左右節點指針和鍵值,還應該加入樹高。

struct avl_node
{
    int data = 0;
    int height = 1; //初始化樹高爲1
    avl_node* left_child = NULL;
    avl_node* right_child = NULL;
};

二、AVL樹的失衡和恢復
圖來自:http://www.th7.cn/system/win/201603/159069.shtml
代碼參考了:http://blog.csdn.net/tiantangrenjian/article/details/12891091
如果我們認爲:節點是一個一個插入AVL樹的,一旦失衡馬上調整,那麼無論將AVL樹的哪個非葉節點當成根節點,在任何時刻最多隻有一側的子樹是失衡的。AVL樹的失衡要考慮四種情況:
1 根節點左節點的左子樹插入了新的元素
這裏寫圖片描述

void LL_change(avl_node* &this_node)
{
    avl_node* left_child = this_node->left_child;
    this_node->left_child = left_child->right_child;
    left_child->right_child = this_node;
    this_node = left_child;
    this_node->right_child->height = max(get_height(this_node->right_child->left_child), get_height(this_node->right_child->right_child)) + 1;
    this_node->height = max(get_height(this_node->left_child), get_height(this_node->right_child)) + 1;
}

2 根節點右節點的右子樹插入了新的元素
這裏寫圖片描述

void RR_change(avl_node* &this_node)
{
    avl_node* right_child = this_node->right_child;
    this_node->right_child = right_child->left_child;
    right_child->left_child = this_node;
    this_node = right_child;
    this_node->left_child->height = max(get_height(this_node->left_child->left_child), get_height(this_node->left_child->right_child)) + 1;
    this_node->height = max(get_height(this_node->left_child), get_height(this_node->right_child)) + 1;
}

3 根節點左節點的右子樹插入了新的元素
這裏寫圖片描述

void LR_change(avl_node* &this_node)
{
    RR_change(this_node->left_child);
    LL_change(this_node);
}

4 根節點右節點的左子樹插入了新的元素
這裏寫圖片描述

void RL_change(avl_node* &this_node)
{
    LL_change(this_node->right_child);
    RR_change(this_node);
}

圖配合代碼應該比較好理解,不需要太多文字敘述。
值得注意的有以下幾點:
1) 如果你在寫代碼之前畫下了這些樹的變換,但是不能確定自己畫得對不對,你可以檢查一下,圖上最底下四個三角形從左到右的標號順序是否和變換前相同
2) 在我的代碼裏,每個函數的形參都是引用的格式,這是因爲在變換時當前樹的根節點會改變,需要反饋給上一級函數,也有很多程序員喜歡通過讓函數返回變換後的根節點,在上一級函數中賦值來達到目的。

三、完整的構造樹和插入的代碼
下面的代碼其實是PAT甲1066題的答案。這題的大意是,依次向空AVL樹插入節點,最後輸出樹的根節點的值。
插入的邏輯很巧妙,需要仔細琢磨一下。我一開始寫代碼犯的一個邏輯錯誤就是,一拿到手就開始分析它究竟是LL,LR,RR,RL哪種情況,分類討論得很費勁還不說,代碼還是錯的。
正確的做法是,先像常規的二叉樹插入節點一樣,遞歸地逐級找到插入的位置,插入完成後,根節點的高度還沒有更新,但是子樹的高度都更新了,根節點的高度不影響我們判斷樹是否平衡。如果不平衡,再分類討論使用哪種情況恢復平衡。最後,子樹都能確定是平衡了,再用子樹的高度更新根節點的高度。這裏先判斷平衡與否的巧妙之處在於,避開了高度爲0,1,2這種情況下插入的分類討論,如果是在前兩層的範圍內插入,無論怎麼插入其實都是平衡的,只要簡單地插入就行了,高度這麼小的樹根本沒有LL,LR,RR,RL這四種情況。

總得來說,這種遞歸的代碼對我這種不常寫遞歸又對AVL樹不甚瞭解的初學者來說還是有點吃力的,有很多地方需要注意。

#include<iostream>
using namespace std;

struct avl_node
{
    int data = 0;
    int height = 1;
    avl_node* left_child = NULL;
    avl_node* right_child = NULL;
};

int get_height(avl_node* a)
{
    if (a == NULL) return 0;
    else return a->height;
}

int max(int a, int b)
{
    if (a > b) return a;
    else return b;
}

bool balance(avl_node* this_node)
{
    int a = get_height(this_node->left_child);
    int b = get_height(this_node->right_child);
    if (a - b == 1 || b - a == 1 || a == b) return true;
    else return false;
}

void LL_change(avl_node* &this_node)
{
    avl_node* left_child = this_node->left_child;
    this_node->left_child = left_child->right_child;
    left_child->right_child = this_node;
    this_node = left_child;
    this_node->right_child->height = max(get_height(this_node->right_child->left_child), get_height(this_node->right_child->right_child)) + 1;
    this_node->height = max(get_height(this_node->left_child), get_height(this_node->right_child)) + 1;
}

void RR_change(avl_node* &this_node)
{
    avl_node* right_child = this_node->right_child;
    this_node->right_child = right_child->left_child;
    right_child->left_child = this_node;
    this_node = right_child;
    this_node->left_child->height = max(get_height(this_node->left_child->left_child), get_height(this_node->left_child->right_child)) + 1;
    this_node->height = max(get_height(this_node->left_child), get_height(this_node->right_child)) + 1;

}

void LR_change(avl_node* &this_node)
{
    RR_change(this_node->left_child);
    LL_change(this_node);
}

void RL_change(avl_node* &this_node)
{
    LL_change(this_node->right_child);
    RR_change(this_node);
}

void insert(int data,avl_node* &this_node)
{
    if (this_node == NULL)
    {
        this_node = new avl_node;
        this_node->data = data;
    }
    else
    {
        if (data < this_node->data)
        {
            insert(data, this_node->left_child);
            if (!balance(this_node))
            {
                if (data < this_node->left_child->data) LL_change(this_node);
                else LR_change(this_node);
            }
        }
        else
        {
            insert(data, this_node->right_child);
            if (!balance(this_node))
            {
                if (data < this_node->right_child->data) RL_change(this_node);
                else RR_change(this_node);
            }
        }
        this_node->height = max(get_height(this_node->left_child), get_height(this_node->right_child)) + 1;
    }
    return;
}



int main()
{
    int num = 5; //cin >> num;
    int data_formal[20] = { 88, 70, 61, 96, 120 };
    //for (int i = 0; i < num; ++i) cin >> data_formal[i];
    avl_node* root_node = NULL;
    for (int i = 0; i < num; ++i)
    {
        insert(data_formal[i], root_node);
    }
    cout << root_node->data;
    getchar(); getchar();
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章