1. 程式人生 > >3.1 C語言_實現AVL平衡二叉樹

3.1 C語言_實現AVL平衡二叉樹

【序】

上節我們實現了資料結構中最簡單的Vector,那麼來到第三章,我們需要實現一個Set

set的特點是 內部有序且有唯一元素值;同時各種操作的期望操作時間複雜度在O(n·logn);

那麼標準的C++ STL(Standard Template Library)  容器內部使用的是什麼呢?

STL使用的是紅黑樹或者hash Tree ,由於筆者現在的水平和精力,沒時間搞這個啦,於是我就

挑了一個稍微熟悉一點的資料結構:AVL 樹;

github:https://github.com/KimAlittleStar/cstd

【1.介紹】

AVL 樹是根據二叉查詢樹改進延伸過來的,我們都知道二叉查詢樹中只有一個規則,

那就是根節點的元素一定會大於左孩子,小於右孩子;如果是隨機數,那麼我們二叉查詢樹的高度無限

接近於 logN(完全二叉樹)。但是由於其策略,在反覆的插入和刪除後,普通的二叉查詢樹將會非常

偏科(偏向右子樹)如果樹的高度非常大(height == N) 那麼他和連結串列的操作時間沒有什麼區別,

為了避免普通二叉查詢樹的缺陷,因此引申出 AVL Tree -> 平衡二叉查詢樹;

AVL 和普通的二叉查詢樹只有一個規則限制:相同節點的兩個孩子的高度最大相差為1,本身節點的高度

為兩個孩子節點中大的那個高度加1;

【2.基礎資料結構】

使用一個node 和 正常的tree 用於管理他的根和size;node 中有左孩子 右孩子和資料還有一個記錄樹的

高度位元組u8(unsigned char),為什麼我們使用u8,因為假設在滿足AVL的規則要求下,壞的情況樹的深度為

deep = logn *2 +1 ; 去除一個起始位0 ,後期可能會用到的錯誤位 -1,那麼在最差的情況下 u8 型別的高度可以儲存

大概多少個節點呢: 

N = 2^254 (個)而我們在tree 中 size 的型別是 u32 (unsigned int)完全夠用了;

參考下圖:(資料結構與演算法C++.pdf)

 

 

 

typedef unsigned int typeClass;

typedef struct __SET_typeClass_node
{
    typeClass data;
    struct __SET_typeClass_node *left;
    struct __SET_typeClass_node *right;
    u8 heigh;
} SET_typeClass_node_t;

typedef struct __SET_typeClass_t
{
    SET_typeClass_node_t *root;
    u32 size;
} SET_typeClass_t;

 

【3 插入】

資料的插入還是依照我們普通的二叉樹進行插入,遞迴判斷我們的資料是否小於當前節點,

小於,遞迴往左走,大於,大於遞迴往右走,等於,不操作,真正的基礎情況是判斷是發現當前

節點為NULL 此時申請記憶體儲存該元素;

 

 

SET_typeClass_node_t *SET_inserttypeClass_node_t(SET_typeClass_node_t *root,
                                                 u8 (*compare)(const typeClass *a, const typeClass *b),
                                                 const typeClass *value, u32 *size)
{
    if (root == NULL)
    {
        root = (SET_typeClass_node_t *)malloc(sizeof(SET_typeClass_node_t));
        if (root == NULL)
        {
            // Out of space!!!
        }
        else
        {
            root->data = (*value);
            root->left = root->right = NULL;
            (*size)++;
        }
        return root;
    }
    else if (compare(value, &root->data))
    {
        root->left = SET_inserttypeClass_node_t(root->left, compare, value, size);
    }
    else if (compare(&root->data, value))
    {
        root->right = SET_inserttypeClass_node_t(root->right, compare, value, size);
    }
    return root;
}
普通二叉樹插入

 

當我們在做完這一切之後,還需要做的是維護AVL的性質:左右節點高度相差最大為1;且需要

遞迴查詢哦。於是乎我們接下來第一步要做的是什麼呢?

更新我們自己的節點的高度;

root->heigh = SET_Max(SET_heighttypeClass(root->left),
                          SET_heighttypeClass(root->right)) +
                  1;

 

更新之後節點之後我們就會發現,哎呀,有些時候(大多數時候)AVL樹的規則被破壞了,那麼該

就需要處理重新符合AVL樹的規則;插入之後呢可能會出現四種情況,其中兩兩映象,因此我們只討論

兩種;

 

 

  

 

 

 

 

 

以上兩種情況分別為 LL ,LR,具體判斷標準為:觀察高度開始不符合的節點,8號節點和 K2節點,

這裡說的高度不符的節點表示:左右孩子的高度相差 >1  那麼他們偏離的子節點的方向分別是

8號:Left->Left  k2->Left->Right;  我們在處理LL情況的時候,只需要將7號變成5號的右孩子;

8號變成7號的右孩子即可,相當於6~7~8號順時針旋轉了一下,我們把這個定義為 ”左單旋“,旋轉後

變成如下圖:

 

相對應的也有 ”右單旋“咯。大家自己推導啦;

接下來看我們的LR情況,如果僅僅對K2執行一次左單旋,那麼結果是:

 

 但是這樣依舊沒有滿足 AVL 樹的性質;K2的高度會比 X大超過1;此時我們需要引進一個k1的右節點進行雙旋轉;

 

我們首先把 K1 k2 B 進行一次右單旋;得到

 

 然後我們在將C~k2~k3進行一次左旋:

 

 相對應的我們也會有 RL的情況,映象情況我們就不贅述咯;

下面程式碼是單旋、雙旋的實現:

SET_typeClass_node_t *SET_doubleRotateLefttypeClass(SET_typeClass_node_t *s)
{
    s->left = SET_singleRotateRighttypeClass(s->left);
    return SET_singleRotateLefttypeClass(s);
}
SET_typeClass_node_t *SET_doubleRotateRighttypeClass(SET_typeClass_node_t *s)
{
    s->right = SET_singleRotateLefttypeClass(s->right);
    return SET_singleRotateRighttypeClass(s);
}

SET_typeClass_node_t *SET_singleRotateLefttypeClass(SET_typeClass_node_t *s)
{
    SET_typeClass_node_t *s1;
    s1 = s->left;
    s->left = s1->right;
    s1->right = s;

    s->heigh = SET_Max(
                   SET_heighttypeClass(s->left),
                   SET_heighttypeClass(s->right)) +
               1;
    s1->heigh = SET_Max(
                    SET_heighttypeClass(s1->left),
                    s->heigh) +
                1;
    return s1;
}

SET_typeClass_node_t *SET_singleRotateRighttypeClass(SET_typeClass_node_t *s)
{
    SET_typeClass_node_t *s1;
    s1 = s->right;
    s->right = s1->left;
    s1->left = s;

    s->heigh = SET_Max(
                   SET_heighttypeClass(s->left),
                   SET_heighttypeClass(s->right)) +
               1;
    s1->heigh = SET_Max(
                    SET_heighttypeClass(s1->right),
                    s->heigh) +
                1;
    return s1;
}

 

 

綜合以上,我們最後insert的程式碼就成了:

SET_typeClass_node_t *SET_inserttypeClass_node_t(SET_typeClass_node_t *root,
                                                 u8 (*compare)(const typeClass *a, const typeClass *b),
                                                 const typeClass *value, u32 *size)
{
    if (root == NULL)
    {
        root = (SET_typeClass_node_t *)malloc(sizeof(SET_typeClass_node_t));
        if (root == NULL)
        {
            // Out of space!!!
        }
        else
        {
            root->data = (*value);
            root->left = root->right = NULL;
            root->heigh = 1;
            (*size)++;
        }
        return root;
    }
    else if (compare(value, &root->data))
    {
        root->left = SET_inserttypeClass_node_t(root->left, compare, value, size);
        if (SET_heighttypeClass(root->left) - SET_heighttypeClass(root->right) == 2)
        {
            if (compare(value, &root->left->data))
                root = SET_singleRotateLefttypeClass(root);
            else
                root = SET_doubleRotateLefttypeClass(root);
        }
    }
    else if (compare(&root->data, value))
    {
        root->right = SET_inserttypeClass_node_t(root->right, compare, value, size);
        if (SET_heighttypeClass(root->right) - SET_heighttypeClass(root->left) == 2)
        {
            if (compare(&root->right->data, value))
                root = SET_singleRotateRighttypeClass(root);
            else
                root = SET_doubleRotateRighttypeClass(root);
        }
    }
    root->heigh = SET_Max(SET_heighttypeClass(root->left),
                          SET_heighttypeClass(root->right)) +
                  1;
    return root;
}
AVL inser操作

 

然後我們使用 tree 將其包裝,並且返回是否插入成功;插入不成功有兩種情況(記憶體空間不足和元素已存在)

u8 SET_inserttypeClass_t(SET_typeClass_t *set, const typeClass ele)
{
    if (set == NULL || set->compare == NULL)
        return 0;
    u32 cursize = set->size;
    set->root = SET_inserttypeClass_node_t(set->root, set->compare, &ele, &set->size);
    return (cursize < set->size);
}
AVL insert 封裝

 

 

【4 刪除】

刪除的操作呢,比插入要更加複雜一些;但是我們依舊是從基礎的二叉樹刪除來入手;普通的二叉樹首先

遞迴尋找到資料,然後將資料分成兩種情況:該元素有兩個孩子和該元素沒有兩個孩子;沒有兩個孩子的邏輯就

很簡單,判斷當前左孩子是否為空,是:將元素的指標指向他的右孩子,否則指向左孩子;如果兩個孩子都為NULL,

指向誰都一樣;

如果是有兩個孩子的呢?那麼我們就將這個元素下尋找到他最小的一個子輩(可能是他的孩子、孫子、曾孫、玄孫。。。)

然後把他的子輩賦值給他,然後刪除他的那個子輩;因為他的子輩一定是左孩子為NULL的;

以下為實現:

SET_typeClass_node_t *SET_removetypeClass_node_t(SET_typeClass_node_t *root,
                                                 u8 (*compare)(const typeClass *a, const typeClass *b),
                                                 void (*deleteSub)(const typeClass *ele),
                                                 const typeClass *value, u32 *size)
{
    if (root == NULL)
    {
        // no has this value
    }
    else if (compare(value, &root->data))
    {
        root->left = SET_removetypeClass_node_t(root->left, compare, deleteSub, value, size);
    }
    else if (compare(&root->data, value))
    {
        root->right = SET_removetypeClass_node_t(root->right, compare, deleteSub, value, size);
    }
    else
    {
        /*real delete option*/
        if (root->right != NULL && root->left != NULL)
        {
            /* has two child */
            SET_typeClass_node_t *temp = root;
            while (temp->left != NULL)
            {
                temp = temp->left;
            }
            if (deleteSub != NULL)
                deleteSub(&root->data);
            root->data = temp->data;

            /* deleteSub == NULL because this min not to free ,just become root->data; */
            root->left = SET_removetypeClass_node_t(root->left, compare, NULL, &root->data, size);
        }
        else
        {
            /* has only child or no child */
            SET_typeClass_node_t *t = (root->right == NULL) ? (root->left) : (root->right);
            if (deleteSub != NULL)
                deleteSub(&root->data);
            free(root);
            (*size)--;
            root = t;
        }
    }

    return root;
}
普通二叉樹remove節點

 刪除完節點之後,我們依舊需要考慮的問題是平衡的問題;刪除會出現什麼問題呢?我們上述的刪除到最後

一定以刪除一個在末梢的節點(孩子最多隻有一個),因此我們只需要考慮此情況即可;

我們可以換一個思路,其實刪除帶來的影響就是在樹的另一邊插入了一個值;那麼相對應的,那邊高度高了我

就往哪邊旋轉就好了;只不過我們判斷的時候如果是刪除左邊,那麼我們就要判斷當前是不是右邊高度-左邊高度 > 2 就好了。

嗯~很簡單是不是?

不不不,在insert中我們判斷是否需要使用雙旋轉的依據是:

root->right = SET_inserttypeClass_node_t(root->right, compare, value, size); if (SET_heighttypeClass(root->right) - SET_heighttypeClass(root->left) == 2) {   if (compare(&root->right->data, value))     root = SET_singleRotateRighttypeClass(root);   else     root = SET_doubleRotateRighttypeClass(root); }

 

因為在插入時,如果出現的不平衡,那麼假設插入的值比當前root的右孩子要大,那麼就會插入在右孩子的左邊;

就會出現下圖所示 (插入了 14 )此時需要雙旋轉,是因為k1的右孩子高度高且右孩子的左側比右側要高;這句話有

點繞口,我們分成兩步,

第一步:k1節點破壞了AVL樹的規則且需要旋轉的是右邊;

第二步:在k1的右孩子k3上,雖然沒有違法AVL的規則,但是他的左孩子高度要比右孩子高度高;這就是我們所說

的RL情況,

所以需要雙旋;

 

 

 

那麼在刪除是,也應遵循此規則,也就是說刪除的時候我們就不要比較 刪除值的大小啦,因為刪除完值之後,

相當於我們需要知道另一側的情況是不是需要雙旋,這個時候怎麼辦呢?我們就依照上述一二步的原理,判斷如果

左邊高度高高於右邊高度 >1 了,那麼我就看左邊孩子的兩個孩子的高度是不是右邊 > 左邊,如果是->雙旋轉,如果

不是,單旋轉;由於有兩個孩子的節點到最後也會是刪除一個葉子節點,故不需要考慮;

以下是remove判斷是否需要雙旋轉的程式碼:

if (compare(value, &root->data))
{
    root->left = SET_removetypeClass_node_t(root->left, compare, deleteSub, value, size);
    if (SET_heighttypeClass(root->right) - SET_heighttypeClass(root->left) == 2)
    {
        if (SET_heighttypeClass(root->right->right) > SET_heighttypeClass(root->right->left))
            root = SET_singleRotateRighttypeClass(root);
        else
            root = SET_doubleRotateRighttypeClass(root);
    }    
}
remove是否需要雙旋轉

 

 

需要我們注意的是在刪除有兩個孩子的節點的時候,雖然我們是刪除,但是我實際上還是呼叫的遞迴,一次我們還

是需要判斷一次平衡規則;

最後remove的程式碼如下:

SET_typeClass_node_t *SET_removetypeClass_node_t(SET_typeClass_node_t *root,
                                                 u8 (*compare)(const typeClass *a, const typeClass *b),
                                                 void (*deleteSub)(const typeClass *ele),
                                                 const typeClass *value, u32 *size)
{
    if (root == NULL)
    {
        // no has this value
    }
    else if (compare(value, &root->data))
    {
        root->left = SET_removetypeClass_node_t(root->left, compare, deleteSub, value, size);
        if (SET_heighttypeClass(root->right) - SET_heighttypeClass(root->left) == 2)
        {
            if (SET_heighttypeClass(root->right->right) > SET_heighttypeClass(root->right->left))
                root = SET_singleRotateRighttypeClass(root);
            else
                root = SET_doubleRotateRighttypeClass(root);
        }
    }
    else if (compare(&root->data, value))
    {
        root->right = SET_removetypeClass_node_t(root->right, compare, deleteSub, value, size);
        if (SET_heighttypeClass(root->left) - SET_heighttypeClass(root->right) == 2)
        {
            if (SET_heighttypeClass(root->left->left) > SET_heighttypeClass(root->left->right))
                root = SET_singleRotateLefttypeClass(root);
            else
                root = SET_doubleRotateLefttypeClass(root);
        }
    }
    else
    {
        /*real delete option*/
        if (root->right != NULL && root->left != NULL)
        {
            /* has two child */
            SET_typeClass_node_t *temp = root;
            while (temp->left != NULL)
            {
                temp = temp->left;
            }
            if (deleteSub != NULL)
                deleteSub(&root->data);
            root->data = temp->data;

            /* deleteSub == NULL because this min not to free ,just become root->data; */
            root->left = SET_removetypeClass_node_t(root->left, compare, NULL, &root->data, size);
            if (SET_heighttypeClass(root->right) - SET_heighttypeClass(root->left) == 2)
            {
                if (SET_heighttypeClass(root->right->right) > SET_heighttypeClass(root->right->left))
                    root = SET_singleRotateRighttypeClass(root);
                else
                    root = SET_doubleRotateRighttypeClass(root);
            }
        }
        else
        {
            /* has only child or no child */
            SET_typeClass_node_t *t = (root->right == NULL) ? (root->left) : (root->right);
            if (deleteSub != NULL)
                deleteSub(&root->data);
            free(root);
            (*size)--;
            root = t;
        }
    }
    if (root != NULL)
        root->heigh = SET_Max(SET_heighttypeClass(root->left),
                              SET_heighttypeClass(root->right)) +
                      1;
    return root;
}
AVL remove 程式碼

 

同理使用一個tree對他進行封裝,同時返回是否remove成功,remove不成功只有一種可能,那就是set中不存在此元素;

u8 SET_removetypeClass_t(SET_typeClass_t *set, const typeClass ele)
{
    if (set == NULL || set->compare == NULL)
        return 0;
    u32 cursize = set->size;
    set->root = SET_removetypeClass_node_t(set->root, set->compare, set->deleteSub, &ele, &set->size);
    return (cursize > set->size);
}

 

在AVL樹中最重要的就是插入和刪除啦;

其他的像 delete 遍歷呀,find findMax之類的太簡單我就不說啦。基本上就是使用遍歷或者一直往右找或往左找;

最後我考慮到上文中SET_typeClass_t是一個型別,所以所有的比較都抽象化了compare函式,還有有可能在刪除的時候

需要釋放SET_typeClass_t中指向的空間,所以抽象化成為函式指標deleteSub;

 

【結束語】

下一節就是將我們今天所實現的這些函式變成巨集,完成 STL 資料容器 SET

 

 

目錄

 

1.引言

 

2.1 C語言_實現簡單基礎的vector

 

2.2 C語言_實現資料容器vector(排序功能)

 

3.1 C語言_實現AVL平衡二叉樹

 

3.2 C語言_實現資料容器set(基礎版)

 

4 C語言_實現簡單基礎的map

 

 

 

 參考資料 : 資料結構與演算法C++實現.pdf;

 

 

 

 

 

&n