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