亚洲精品中文免费|亚洲日韩中文字幕制服|久久精品亚洲免费|一本之道久久免费

      
      

            <dl id="hur0q"><div id="hur0q"></div></dl>

                最透徹的紅黑樹詳解(圖文并茂,一文全解)

                最透徹的紅黑樹詳解(圖文并茂,一文全解)

                前言

                ??剛開始接觸紅黑樹的時候,感覺很難。其實不然,紅黑樹只是情況分的多了一點而已,相較于線段樹,主席樹等等,簡單多了。對于紅黑樹3種插入維護4種刪除維護沒必要去記憶,稍作了解,對于紅黑樹的性質(zhì)和特點,需要特別記憶。

                ??本專欄知識點是通過零聲教育的線上課學習,進行梳理總結寫下文章,對c/c++linux課程感興趣的讀者,可以點擊鏈接:C/C++Linux服務器開發(fā)/后臺架構師【零聲教育】-學習視頻教程-騰訊課堂課程介紹詳細查看課程的服務。

                注意,本文圖中紅黑樹的葉子結點默認不畫出來~

                為什么要有紅黑樹

                二叉搜索樹

                ??二叉搜索樹(又叫二叉排序樹,BST):對于任意一個結點,其左子樹的值必定小于該結點,其右子樹的值必定大于該結點。那么中序遍歷的時候,就是有序的了。理論上來說,增加,刪除,修改的時間復雜度都是O(log(N))。但是它存在一個致命的問題。

                ??退化:向樹中插入[1,2,3,4,5,6],此時樹退化成了一個鏈表,增加,刪除,修改的時間復雜度退化為O(N)

                添加圖片注釋,不超過 140 字(可選)

                平衡二叉搜索樹

                ??平衡二叉搜索樹(AVL Tree):它的左右兩個子樹的高度差的絕對值不超過1,并且左右兩個子樹都是一棵平衡二叉搜索樹。如果向樹中插入[1,2,3,4,5,6]

                添加圖片注釋,不超過 140 字(可選)

                可以看到AVLTree在最壞的情況下,依然保持了“絕對的平衡”:左右兩個子樹的高度差的絕對值不超過1。那么AVL Tree是如何保證平衡的呢,是通過旋轉(zhuǎn),可以看到,無論是插入還是刪除元素,都要去通過旋轉(zhuǎn)維護整個樹的平衡。

                • AVL查詢元素:O(log(N))
                • AVL插入元素:最多一次旋轉(zhuǎn)O(1),加上查詢的時間O(log(N)),插入的復雜度O(log(N))
                • AVL刪除元素:必須檢查從刪除結點開始到根結點路徑上的所有結點的平衡因子。因此刪除的代價比較大,刪除最多需要log(N)次旋轉(zhuǎn),加上查詢的時間,刪除的復雜度O(2log(N))

                紅黑樹

                ??我們發(fā)現(xiàn),AVL樹未免太嚴格了一些,有沒有一種數(shù)據(jù)結構,能讓AVL樹不那么嚴格平衡,降低維護平衡的開銷,同時又不能像BST一樣退化呢?

                當然有,就是本文所寫的紅黑樹(rbTree):

                • rbTree查詢元素:O(log(N))
                • rbTree插入元素:插入最多2次旋轉(zhuǎn),加上查詢的時間O(log(N)),插入的復雜度O(log(N))
                • rbTree刪除元素:刪除最多需要3次旋轉(zhuǎn),加上查詢的時間,刪除的復雜度O(log(N))

                ??雖然插入和刪除元素后,需要旋轉(zhuǎn)和變色(本文中統(tǒng)一為維護),但是這一時間復雜度可以估算為O(1)不計

                ??因為rbTree的第6條性質(zhì)(見下文)

                • 所以紅黑樹的查詢效率略低與AVL的查詢
                • 紅黑樹和AVL插入的速度差不多
                • 紅黑樹刪除的速度比AVL快,因為AVL刪除最多需要og(N)次旋轉(zhuǎn)

                紅黑樹的應用場景

              1. c++ stl map,set(紅黑樹的封裝)
              2. 進程調(diào)度cfs(用紅黑樹存儲進程的集合,把調(diào)度的時間作為key,那么樹的左下角時間就是最小的)
              3. 內(nèi)存管理(每次使用malloc的時候都會分配一塊小內(nèi)存出來,那么這么塊就是用紅黑樹來存,如何表述一段內(nèi)存塊呢,用開始地址+長度來表示,所以key->開始地址,val->大?。?/li>
              4. epoll中使用紅黑樹管理socketfd
              5. nginx中使用紅黑樹管理定時器,中序遍歷第一個就是最小的定時器
              6. 紅黑樹的性質(zhì)(重點)

              7. 每個結點是紅的或者黑的
              8. 根結點是黑的
              9. 每個葉子結點是黑的(因為這條性質(zhì),一般用葉子結點在代碼中被特殊表示)
              10. 如果一個結點是紅的,則它的兩個兒子都是黑的(不存在相鄰紅色
              11. 從任一節(jié)點到葉子節(jié)點,所包含的黑色節(jié)點數(shù)目相同(即黑高度相同)
              12. 最長路徑長度不超過最短路徑長度的2倍(2n-1,一條黑紅黑紅…一條全黑)
              13. 紅黑樹的定義

                #define RED 0#define BlACK 1typedef int KEY_TYPE;typedef struct _rbtree_node { unsigned char color;//顏色 struct _rbtree_node *left;//左子樹 struct _rbtree_node *right;//右子樹 struct _rbtree_node *parent;//父結點 KEY_TYPE key; void *value;} rbtree_node;//紅黑樹結點typedef struct _rbtree { rbtree_node *root;//根結點 rbtree_node *nil;//通用葉子結點} rbtree;//紅黑樹

                紅黑樹的左旋與右旋

                動三個方向,改6個指針。 通過旋轉(zhuǎn),調(diào)整左右高度,使樹達到平衡。

                添加圖片注釋,不超過 140 字(可選)

                左旋leftRotate(T,x)—中右->左中

                降低X結點的高度,提高X結點右結點(即Y)的高度。

              14. x的右子樹指向y的左子樹
              15. 本來指向x結點的父指針,改成指向y
              16. y的左子樹指向x結點
              17. 添加圖片注釋,不超過 140 字(可選)

                右旋rightRotate(T,y)—中左->中右

                降低Y結點的高度,提高Y結點左結點(即X)的高度。

              18. y的左子樹指向x的右子樹
              19. 本來指向y結點的父指針,改成指向x
              20. x的右子樹指向y結點
              21. 添加圖片注釋,不超過 140 字(可選)

                //左旋leftRotate(T,x)—中右->左中//降低X結點的高度,提高X結點右結點(即Y)的高度。void _left_rotate(rbtree *T, rbtree_node *x) { rbtree_node *y = x->right; //1 x->right = y->left;//x的右子樹指向y的左子樹 if (y->left != T->nil) { y->left->parent = x;//y的左子樹的父節(jié)點指向x } //2 y->parent = x->parent;//y的父結點指向x的父結點 if (x->parent == T->nil) {//如果x是根結點 T->root = y; } else if (x == x->parent->left) { x->parent->left = y;//本來指向x結點的父指針,改成指向y } else { x->parent->right = y; } //3 y->left = x;//y的左子樹指向x結點 x->parent = y;//x的父節(jié)點指向y}//右旋//copy左旋的代碼//left改成right,right改成left//x改成y,y改成xvoid _right_rotate(rbtree *T, rbtree_node *y) { rbtree_node *x = y->left; //1 y->left = x->right; if (x->right != T->nil) { x->right->parent = y; } //2 x->parent = y->parent; if (y->parent == T->nil) { T->root = x; } else if (y == y->parent->right) { y->parent->right = x; } else { y->parent->left = x; } //3 x->right = y; y->parent = x;}

                紅黑樹插入結點與插入維護紅黑樹的三種情況

                插入結點

                ??在插入結點時,我們始終認為“插入這個結點之前,原來的紅黑樹是滿足紅黑樹性質(zhì)的==”,那么插入的位置容易找,就是不斷的對比key,最終找到位置,那么新增的結點是什么顏色呢?我們通過性質(zhì)發(fā)現(xiàn):

                • 如果新結點是黑色,違背了第5條性質(zhì)
                • 如果新結點是紅色,可能違背第4條性質(zhì)

                而第四條性質(zhì),我們可以通過旋轉(zhuǎn)與上色的方式修復,所以在我們插入結點的時候,我們始終認為新結點是紅色

                //因為傳入的key可能是字符,可能是整形,所以要提取出來//這里可以看出,其實可以封裝成一個模板int key_compare(KEY_TYPE a, KEY_TYPE b) { //這里假設是int if (a > b) { return 1; } else if (a root; rbtree_node *y = T->nil;//y是x的父節(jié)點 while (x != T->nil) {//二分找位置 y = x; if (key_compare(z->key, x->key) left; } else if (key_compare(z->key, x->key) > 0) { x = x->right; } else { //如果key相等,看自己的業(yè)務情景 //重復插入可以不修改直接退出,可以修改val return; } } //插入 z->parent = y; if (y == T->nil) { T->root = z; } else if (key_compare(z->key, y->key) left = z; } else { y->right = z; } z->left = T->nil; z->right = T->nil; z->color = RED; //維護紅黑樹 rbtree_insert_fixup(T, z);}

                插入結點后維護紅黑樹

                ??我們知道新增結點是紅色,如果新結點是父節(jié)點也是紅色,那么就需要維護紅黑樹了。

                ??如果父結點是爺結點是左子樹,可以歸納出來三種情況。同理如果父結點是爺結點是右子樹,我們也可以歸納出來三種情況。但是這三種情況的差異就是旋轉(zhuǎn)方向的區(qū)別而已。一共是6種情況,但是歸納出來其實是3種,讀者不要搞錯了。

                在下面的圖中:z表示新增結點,y表示叔結點

                父結點是爺結點是左子樹

                1. 叔結點是紅色的

                • 將叔結點和父結點變黑,爺結點變紅
                • 將當前結點變成爺結點(因為爺結點是紅,爺結點的父節(jié)點也可能是紅,所以要遞歸維護)

                添加圖片注釋,不超過 140 字(可選)

                2. 叔結點是黑色的且新結點是左子樹

                • 將父結點變成黑色,爺結點變成紅色
                • 以爺結點為中心右旋

                添加圖片注釋,不超過 140 字(可選)

                3. 叔結點是黑色的且新結點是右子樹

                • 以父結點為中心左旋
                • 將父結點變黑色,爺結點變紅色
                • 以爺結點為中心右旋

                添加圖片注釋,不超過 140 字(可選)

                父結點是爺結點是右子樹

                1. 叔結點是紅色的

                • 將叔結點和父結點變黑,爺結點變紅
                • 將當前結點變成爺結點(因為爺結點是紅,爺結點的父節(jié)點也可能是紅,所以要遞歸維護)

                添加圖片注釋,不超過 140 字(可選)

                2. 叔結點是黑色的且新結點是左子樹

                • 以父結點為中心右旋
                • 將父結點變黑色,爺結點變紅色
                • 以爺結點為中心左旋

                添加圖片注釋,不超過 140 字(可選)

                3. 叔結點是黑色的且新結點是右子樹

                • 將父結點變成黑色,爺結點變成紅色
                • 以爺結點為中心左旋

                添加圖片注釋,不超過 140 字(可選)

                維護代碼

                //修復第4條性質(zhì)void rbtree_insert_fixup(rbtree *T, rbtree_node *z) { while (z->parent->color == RED) {//父結點是紅色的,需要調(diào)整 if (z->parent == z->parent->parent->left) {//如果父結點是爺結點是左子樹 rbtree_node *y = z->parent->parent->right;//叔結點 if (y->color == RED) {//叔結點是紅色的 //先變色,叔,父變黑 z->parent->color = BLACK; y->color = BLACK; //爺結點變紅 z->parent->parent->color = RED; //下面的調(diào)整完了,調(diào)整上面 z = z->parent->parent; } else {//叔父結點是黑色 if (z == z->parent->right) {//新節(jié)點是在右邊 z = z->parent; rbtree_left_rotate(T, z); } z->parent->color = BLACK; z->parent->parent->color = RED; rbtree_right_rotate(T, z->parent->parent); } } else { // 如果父結點是爺結點是右子樹 rbtree_node *y = z->parent->parent->left;//叔父結點 if (y->color == RED) {//叔父結點是紅色的 //先變色,叔,父變黑 z->parent->color = BLACK; y->color = BLACK; //爺結點變紅 z->parent->parent->color = RED; //下面的調(diào)整完了,調(diào)整上面 z = z->parent->parent; } else {//叔父結點是黑色 if (z == z->parent->left) {//新節(jié)點是在左邊 z = z->parent; rbtree_right_rotate(T, z); } z->parent->color = BLACK; z->parent->parent->color = RED; rbtree_left_rotate(T, z->parent->parent); } } } //最后別忘了根節(jié)點始終是黑色 T->root->color = BLACK;}

                紅黑樹刪除結點與刪除維護紅黑樹的四種情況

                刪除結點

                我們定義:

                • 覆蓋結點:z(被指定刪除的結點,實際上被覆蓋)
                • 刪除結點:y(實際上被刪除的結點,一般是后繼結點)
                • 軸心結點:x(維護紅黑樹的結點)

                紅黑樹刪除結點根據(jù)改結點的左右子樹分為三種情況:

              22. 沒有左右子樹
              23. 有且僅有一個子樹
              24. 左右子樹都有
              25. 對不同情況的處理:

                • 情況1:直接刪除該結點
                • 情況2:將該結點的唯一子樹掛到父結點上,然后刪除該結點
                • 情況3:找一個刪除結點y(后繼結點)覆蓋 指定結點z,然后刪除 刪除結點y,對于這個刪除結點y來說,它的情況一定是情況1或情況2

                刪除代碼

                rbtree_node *rbtree_mini(rbtree *T, rbtree_node *x) { while (x->left != T->nil) { x = x->left; } return x;}//找后繼結點rbtree_node *rbtree_successor(rbtree *T, rbtree_node *x) { rbtree_node *y = x->parent; //右子樹不為空,則找右子樹中最左的元素 if (x->right != T->nil) { return rbtree_mini(T, x->right); } //找到結點第一個父結點 while ((y != T->nil) && (x == y->right)) { x = y; y = y->parent; } return y;}//覆蓋結點z//刪除結點y//軸心結點xrbtree_node *rbtree_delete(rbtree *T, rbtree_node *z) { rbtree_node *y = T->nil; rbtree_node *x = T->nil; if ((z->left == T->nil) || (z->right == T->nil)) { y = z;//如果沒有孩子或只有一個 } else {//如果有兩個子樹則找后繼 y = rbtree_successor(T, z); } //一般x是y的右子樹,找到軸心結點 if (y->left != T->nil) { x = y->left; } else if (y->right != T->nil) { x = y->right; } //將該結點的唯一子樹掛到父結點上,然后刪除該結點 x->parent = y->parent; if (y->parent == T->nil) {//根結點 T->root = x; } else if (y == y->parent->left) { y->parent->left = x; } else { y->parent->right = x; } //進行覆蓋操作 if (y != z) { z->key = y->key; z->value = y->value; } //黑色才需要調(diào)整 if (y->color == BLACK) { rbtree_delete_fixup(T, x); } return y;}

                刪除結點后維護紅黑樹

                ??想一想,刪除一個結點,該結點是什么顏色的時候才需要維護紅黑樹呢?

                • 如果是紅色,沒有違反任何性質(zhì)。所以如果是紅色直接刪除即可,無需維護
                • 如果是黑色,黑色被刪除,那么必定違反第5條性質(zhì),破壞了黑高,所以我們需要針對這一情況進行維護

                ??如果當前結點是父結點的左子樹的情況,可以歸納出來四種情況。同理如果當前結點是父結點的右子樹,我們也可以歸納出來四種情況。但是這四種情況的差異就是旋轉(zhuǎn)方向的區(qū)別而已(鏡像的)。一共是8種情況,但是歸納出來其實是4種,讀者不要搞錯了。

                當前結點是父結點的左子樹的情況

                1.當前結點的兄弟結點是紅色的

                • 兄弟節(jié)點變成黑色
                • 父節(jié)點變成紅色
                • 父節(jié)點做左旋
                • 將兄弟結點調(diào)整為父節(jié)點的右子樹

                添加圖片注釋,不超過 140 字(可選)

                2. 當前結點的兄弟結點是黑色的,而且兄弟結點的兩個孩子結點都是黑色的

                • 兄弟節(jié)點變成紅色
                • 軸心結點變?yōu)楦腹?jié)點

                添加圖片注釋,不超過 140 字(可選)

                3. 當前結點的兄弟結點是黑色的,而且兄弟結點的左孩子是紅色的,右孩子是黑色的

                • 將左孩子涂黑
                • 將兄弟節(jié)點變紅
                • 對兄弟節(jié)點右旋
                • 將兄弟結點調(diào)整為父節(jié)點的右子樹
                • 現(xiàn)在情況3就會變成情況4,接著做情況4的步驟

                添加圖片注釋,不超過 140 字(可選)

                4. 當前結點的兄弟結點是黑色的,而且兄弟結點的左孩子是黑色的,右孩子是紅色的

                • 將兄弟節(jié)點換成父節(jié)點的顏色
                • 把父節(jié)點和兄弟節(jié)點的右孩子涂黑
                • 對父節(jié)點做左旋
                • 設置x指針,指向根節(jié)點

                添加圖片注釋,不超過 140 字(可選)

                當前結點是父結點的右子樹的情況

                1. 當前結點的兄弟結點是紅色的

                • 兄弟節(jié)點變成黑色
                • 父節(jié)點變成紅色
                • 父節(jié)點做右旋
                • 將兄弟結點調(diào)整為父節(jié)點的左子樹

                2. 當前結點的兄弟結點是黑色的,而且兄弟結點的兩個孩子結點都是黑色的

                • 兄弟節(jié)點變成紅色
                • 軸心結點變?yōu)楦腹?jié)點

                3. 當前結點的兄弟結點是黑色的,而且兄弟結點的左孩子是黑色的,右孩子是紅色的

                • 將右孩子變黑
                • 將兄弟節(jié)點變紅
                • 對兄弟結點左旋
                • 將兄弟結點調(diào)整為父節(jié)點的左子樹
                • 現(xiàn)在情況3就會變成情況4,接著做情況4的步驟

                4. 當前結點的兄弟結點是黑色的,而且兄弟結點的左孩子是紅色的,右孩子是黑色的

                • 將兄弟節(jié)點換成父節(jié)點的顏色
                • 把父節(jié)點和兄弟節(jié)點的左孩子變黑
                • 對父節(jié)點做右旋
                • 將軸心結點調(diào)整為根結點

                維護代碼

                void rbtree_delete_fixup(rbtree *T, rbtree_node *x) { //如果x是紅色,變成黑色,如果x是黑色,需要調(diào)整 while ((x != T->root) && (x->color == BLACK)) { //當前結點是父結點的左子樹 if (x == x->parent->left) { //兄弟節(jié)點 rbtree_node *w = x->parent->right; // 情況1:兄弟節(jié)點為紅色 if (w->color == RED) { // 兄弟節(jié)點變成黑色 w->color = BLACK; // 父節(jié)點變成紅色 x->parent->color = RED; // 父節(jié)點做左旋 rbtree_left_rotate(T, x->parent); //將兄弟結點調(diào)整為父節(jié)點的右子樹 w = x->parent->right; } // 情況2:兄弟節(jié)點是黑色的,且兄弟的左孩子和右孩子都是黑色 if ((w->left->color == BLACK) && (w->right->color == BLACK)) { // 兄弟節(jié)點變成紅色 w->color = RED; // 軸心結點變?yōu)楦腹?jié)點 x = x->parent; } else { // 情況3:x的兄弟節(jié)點是黑色的,兄弟的左孩子是紅色,右孩子是黑色 if (w->right->color == BLACK) { // 將左孩子涂黑 w->left->color = BLACK; // 將兄弟節(jié)點變紅 w->color = RED; // 對兄弟節(jié)點右旋 rbtree_right_rotate(T, w); // 重新設置x的兄弟節(jié)點 w = x->parent->right; } // 情況4:x的兄弟節(jié)點是黑色;x的兄弟節(jié)點的右孩子是紅色的 // 將兄弟節(jié)點換成父節(jié)點的顏色 w->color = x->parent->color; // 把父節(jié)點和兄弟節(jié)點的右孩子涂黑 x->parent->color = BLACK; w->right->color = BLACK; // 對父節(jié)點做左旋 rbtree_left_rotate(T, x->parent); // 設置x指針,指向根節(jié)點 x = T->root; } } else {//當前結點是父結點的右子樹 //兄弟節(jié)點 rbtree_node *w = x->parent->left; //情況1:兄弟結點為紅色 if (w->color == RED) { // 兄弟節(jié)點變成黑色 w->color = BLACK; // 父節(jié)點變成紅色 x->parent->color = RED; // 父節(jié)點做右旋 rbtree_right_rotate(T, x->parent); //將兄弟結點調(diào)整為父節(jié)點的左子樹 w = x->parent->left; } // 情況2:兄弟節(jié)點是黑色的,且兄弟的左孩子和右孩子都是黑色 if ((w->left->color == BLACK) && (w->right->color == BLACK)) { // 兄弟節(jié)點變成紅色 w->color = RED; // 軸心結點變?yōu)楦腹?jié)點 x = x->parent; } else { // 情況3:x的兄弟結點是黑色的,兄弟的左孩子是黑色,右孩子是紅色 if (w->left->color == BLACK) { //將右孩子變黑 w->right->color = BLACK; //將兄弟節(jié)點變紅 w->color = RED; //對兄弟結點左旋 rbtree_left_rotate(T, w); //將兄弟結點調(diào)整為父節(jié)點的左子樹 w = x->parent->left; } // 情況4:x的兄弟結點是黑色的,兄弟的左孩子是紅色,右孩子是黑色 // 將兄弟節(jié)點換成父節(jié)點的顏色 w->color = x->parent->color; // 把父節(jié)點和兄弟節(jié)點的左孩子變黑 x->parent->color = BLACK; w->left->color = BLACK; // 對父節(jié)點做右旋 rbtree_right_rotate(T, x->parent); //將軸心結點調(diào)整為根結點 x = T->root; } } } // 繼承節(jié)點變?yōu)楹谏?,為了彌補失去的黑高 x->color = BLACK;}

                紅黑樹的遍歷、查詢、測試

                rbtree_node *rbtree_search(rbtree *T, KEY_TYPE key) { rbtree_node *node = T->root; while (node != T->nil) { if (key key) { node = node->left; } else if (key > node->key) { node = node->right; } else { return node; } } return T->nil;}void rbtree_traversal(rbtree *T, rbtree_node *node) { if (node != T->nil) { rbtree_traversal(T, node->left); printf(“key:%d, color:%d”, node->key, node->color); rbtree_traversal(T, node->right); }}int main() { int keyArray[20] = {24, 25, 13, 35, 23, 26, 67, 47, 38, 98, 20, 19, 17, 49, 12, 21, 9, 18, 14, 15}; rbtree *T = (rbtree *) malloc(sizeof(rbtree)); if (T == NULL) { printf(“malloc failed”); return -1; } T->nil = (rbtree_node *) malloc(sizeof(rbtree_node)); T->nil->color = BLACK; T->root = T->nil; rbtree_node *node = T->nil; int i = 0; for (i = 0; i key = keyArray[i]; node->value = NULL; rbtree_insert(T, node); } rbtree_traversal(T, T->root); printf(“—————————————-“); for (i = 0; i root); printf(“—————————————-“); }}

                原文鏈接:隨處可見的紅黑樹詳解_cheems~的博客-CSDN博客

                鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場,版權歸原作者所有,如有侵權請聯(lián)系管理員(admin#wlmqw.com)刪除。
                用戶投稿
                上一篇 2022年7月12日 15:26
                下一篇 2022年7月12日 15:26

                相關推薦

                聯(lián)系我們

                聯(lián)系郵箱:admin#wlmqw.com
                工作時間:周一至周五,10:30-18:30,節(jié)假日休息