數(shù)據(jù)結構算法 - 紅黑樹

紅黑樹是一棵自平衡的二叉搜索樹,因此在學習紅黑樹之前瓤帚,我們需要回顧一下之前所學的知識二叉搜索樹和平衡二叉樹留储。

1.二叉搜索樹

二叉搜索樹又叫二叉查找樹或者二叉排序樹七嫌,它首先是一個二叉樹惩激,而且必須滿足下面的條件:

1)若左子樹不空店煞,則左子樹上所有結點的值均小于它的根節(jié)點的值蟹演;

2)若右子樹不空风钻,則右子樹上所有結點的值均大于它的根結點的值

3)左、右子樹也分別為二叉搜索樹


二叉搜索樹示例
2.平衡二叉樹

二叉搜索樹解決了許多問題酒请,比如可以快速的查找最大值和最小值骡技,可以快速找到排名第幾位的值,快速搜索和排序等等羞反。但普通的二叉搜索樹有可能出現(xiàn)極不平衡的情況(斜樹)布朦,這樣我們的時間復雜度就有可能退化成 O(N) 的情況。比如我們現(xiàn)在插入的數(shù)據(jù)是 [1,2,3,4,5,6,7] 轉(zhuǎn)換為二叉樹如下:

斜樹

由于普通的二叉搜索樹會出現(xiàn)極不平衡的情況昼窗,那么我們就必須得想想辦法了是趴,這個時候平衡二叉樹就能幫到我們了。什么是平衡二叉樹澄惊?平衡二叉搜索樹(Self-balancing binary search tree)又被稱為AVL樹(有別于AVL算法)唆途,且具有以下性質(zhì):它是一 棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,并且左右兩個子樹都是一棵平衡二叉樹掸驱。

平衡二叉樹有一個很重要的性質(zhì):左右兩個子樹的高度差的絕對值不超過1肛搬。那么解決方案就是如果二叉樹的左右高度超過 1 ,我們就把當前樹調(diào)整為一棵平衡二叉樹毕贼。這就涉及到左旋温赔、右旋先右旋再左旋鬼癣、先左旋再右旋陶贼。

2.1 右旋:

右旋.png

   TreeNode<K, V> *R_Rotation(TreeNode<K, V> *pNode) {
        TreeNode<K, V> *left = pNode->left;
        TreeNode<K, V> *right = left->right;
        left->right = pNode;
        pNode->left = right;
        // 重新調(diào)整高度
        pNode->height = max(getHeight(pNode->left), getHeight(pNode->right)) + 1;
        left->height = max(getHeight(left->left), getHeight(left->right)) + 1;
        return left;
    }

2.2 左旋:

左旋

  TreeNode<K, V> *L_Rotation(TreeNode<K, V> *pNode) {
        TreeNode<K, V> *right = pNode->right;
        TreeNode<K, V> *left = right->left;
        right->left = pNode;
        pNode->right = left;
        // 重新調(diào)整高度
        pNode->height = max(getHeight(pNode->left), getHeight(pNode->right)) + 1;
        right->height = max(getHeight(right->left), getHeight(right->right)) + 1;
        return right;
    }

2.3 先右旋再左旋:

先右旋再左旋

  TreeNode<K, V> *R_L_Rotation(TreeNode<K, V> *pNode) {
    pNode->right = R_Rotation(pNode->right);
    return L_Rotation(pNode);
  }

2.4 先左旋再右旋:

先左旋再右旋

  TreeNode<K, V> *L_R_Rotation(TreeNode<K, V> *pNode) {
    pNode->left = L_Rotation(pNode->left);
    return R_Rotation(pNode);
  }
3.紅黑樹

紅黑樹用法就比較廣了,比如 JDK 1.8 的 HashMap待秃,TreeMap骇窍,C++ 中的 map 和 multimap 等等。紅黑樹學習起來還是有一點難度的锥余,這時如果我們心中有 B 樹就有助于理解它腹纳,如果沒有 B 樹也沒有關系。

紅黑樹的特性:
(1)每個節(jié)點或者是黑色,或者是紅色嘲恍。
(2)根節(jié)點是黑色足画。
(3)每個葉子節(jié)點(NIL)是黑色。 [注意:這里葉子節(jié)點佃牛,是指為空(NIL或NULL)的葉子節(jié)點淹辞!]
(4)如果一個節(jié)點是紅色的,則它的子節(jié)點必須是黑色的俘侠。
(5)從一個節(jié)點到該節(jié)點的子孫節(jié)點的所有路徑上包含相同數(shù)目的黑節(jié)點象缀。


紅黑樹

假設我們現(xiàn)在要插入一個新的節(jié)點,如過插入的這個新的節(jié)點為黑色爷速,那么必然會違反性質(zhì)(5)央星,所以我們把新插入的點定義為紅色的。但是如果插入的新節(jié)點為紅色惫东,就可能會違反性質(zhì)(4) 莉给,因此我們需要對其進行調(diào)整,使得整棵樹依然滿足紅黑樹的性質(zhì)廉沮,也就是雙紅修正颓遏。接下來我們只要分情況分析就可以了:

  1. 如果沒有出現(xiàn)雙紅現(xiàn)象,父親是黑色的不需要修正滞时;
  2. 叔叔是紅色的 叁幢,將叔叔和父親染黑,然后爺爺染紅坪稽;
  3. 叔叔是黑色的曼玩,父親是爺爺?shù)淖蠊?jié)點,且當前節(jié)點是其父節(jié)點的右孩子刽漂,將“父節(jié)點”作為“新的當前節(jié)點”演训,以“新的當前節(jié)點”為支點進行左旋。然后將“父節(jié)點”設為“黑色”贝咙,將“祖父節(jié)點”設為“紅色”样悟,以“祖父節(jié)點”為支點進行右旋;
  4. 叔叔是黑色的庭猩,父親是爺爺?shù)淖蠊?jié)點窟她,且當前節(jié)點是其父節(jié)點的左孩子,將“父節(jié)點”設為“黑色”蔼水,將“祖父節(jié)點”設為“紅色”震糖,以“祖父節(jié)點”為支點進行右旋;
  5. 叔叔是黑色的趴腋,父親是爺爺?shù)挠夜?jié)點吊说,且當前節(jié)點是其父節(jié)點的左孩子论咏,將“父節(jié)點”作為“新的當前節(jié)點”,以“新的當前節(jié)點”為支點進行右旋颁井。然后將“父節(jié)點”設為“黑色”厅贪,將“祖父節(jié)點”設為“紅色”,以“祖父節(jié)點”為支點進行左旋雅宾;
  6. 叔叔是黑色的养涮,父親是爺爺?shù)挠夜?jié)點,且當前節(jié)點是其父節(jié)點的右孩子眉抬,將“父節(jié)點”設為“黑色”贯吓,將“祖父節(jié)點”設為“紅色”,以“祖父節(jié)點”為支點進行左旋蜀变;

上面的雙紅修正現(xiàn)象看似比較復雜悄谐,但實際上只有三種情況,一種是沒有雙紅現(xiàn)象昏苏,另一種是父親和叔叔都是紅色的尊沸,最后一種是叔叔是黑色的威沫。我們來畫個實例看下:


void solveDoubleRed(TreeNode *pNode) {
        while (pNode->parent && pNode->parent->color == red) {// 情況 1
            TreeNode *uncle = brother(parent(pNode));

            if (getColor(uncle) == red) {// 情況2
                // 設置雙親和叔叔為黑色
                setColor(parent(pNode), black);
                setColor(uncle, black);
                // 指針回溯至爺爺
                pNode = parent(parent(pNode));
            } else {
                // 父親是爺爺?shù)淖髢鹤?                if (parent(parent(pNode))->left = parent(pNode)) { // 情況 3 和 4
                    // 自己是父親的右兒子
                    if (parent(pNode)->right == pNode) {
                        pNode = parent(pNode);
                        L_Rotation(pNode);
                    }
                    // 把我自己這邊的紅色節(jié)點挪到隔壁樹上贤惯,但仍然不能違反性質(zhì) 4 和 5
                    setColor(parent(pNode), black);
                    setColor(parent(parent(pNode)), red);
                    R_Rotation(parent(parent(pNode)));
                } else { // 情況 5 和 6
                    // 自己是父親的左兒子
                    if (parent(pNode)->left == pNode) {
                        pNode = parent(pNode);
                        R_Rotation(pNode);
                    }
                    // 把我自己這邊的紅色節(jié)點挪到隔壁樹上,但仍然不能違反性質(zhì) 4 和 5
                    setColor(parent(pNode), black);
                    setColor(parent(parent(pNode)), red);
                    L_Rotation(parent(parent(pNode)));
                }
            }
        }

        // 根結點為黑色
        root->color = black;
    }

哎~好復雜這怎么記得住棒掠。如果要記住肯定不太可能而且費勁孵构,接下來我們來分析下為什么要這么操作,還有沒有更好的調(diào)整方法烟很。我們所有的調(diào)整都是為了不違反性質(zhì)4和性質(zhì)5颈墅,假設我在左邊的這個支樹上新增了一個紅色的節(jié)點,違反了性質(zhì)4 雾袱。想法就是我把左支樹上的一個紅色節(jié)點恤筛,挪動右支樹上去,這樣就解決了我有兩個連續(xù)紅色節(jié)點的問題芹橡。但挪給右支樹的過程中不能違反性質(zhì)4和性質(zhì)5毒坛,所以必須得考慮叔叔節(jié)點的顏色。

最后我們來看下紅黑樹的刪除操作林说,紅黑樹的刪除操作要比新增操作要復雜些煎殷,但總體來說都是出現(xiàn)問題就去解決問題。當我們移除的是一個紅色節(jié)點腿箩,那么根本就不會影響我們的性質(zhì)4和性質(zhì)5豪直,我們不需要調(diào)整,但倘若我們移除的是一個黑色的節(jié)點珠移,這時肯定會違反我們的性質(zhì)5弓乙,所以我們只需要調(diào)整移除黑色節(jié)點的情況末融。分情況討論下:

  1. 如果兄弟節(jié)點是紅色的,把兄弟節(jié)點染黑暇韧,父節(jié)點染紅滑潘,左/右旋父節(jié)點;
  2. 如果兄弟節(jié)點是黑色的锨咙,并且兩個侄子節(jié)點都是黑色的语卤,將兄弟節(jié)點染紅,指針回溯至父親節(jié)點酪刀;
  3. 如果兄弟節(jié)點是黑色粹舵,的并且遠侄子是黑色的,近侄子是紅色的骂倘,將進侄子染黑眼滤,兄弟染紅,左/右旋兄弟節(jié)點历涝,進入下面情況 4 诅需;
  4. 如果兄弟節(jié)點是黑色的,并且遠侄子是紅色的荧库,近侄子隨意堰塌,將兄弟節(jié)點染成父親節(jié)點的顏色,父親節(jié)點染黑分衫,遠侄子染黑场刑,左/右旋父親節(jié)點。


void solveLostBlack(TreeNode *pNode) {
        while (pNode != root && getColor(pNode) == black) {
            if (left(parent(pNode)) == pNode) {
                TreeNode *sib = brother(pNode);
                if (getColor(sib) == red) {
                    setColor(sib, black);
                    setColor(parent(pNode), red);
                    L_Rotation(parent(pNode));
                    sib = brother(pNode);
                }

                if (getColor(left(sib)) == black && getColor(right(sib)) == black) {
                    setColor(sib, red);
                    pNode = parent(pNode);
                } else {
                    if (getColor(right(sib)) == black) {
                        setColor(left(sib), black);
                        setColor(sib, red);
                        R_Rotation(sib);
                        sib = brother(pNode);
                    }

                    setColor(sib, getColor(parent(pNode)));
                    setColor(parent(pNode), black);
                    setColor(right(sib), black);
                    L_Rotation(parent(pNode));
                    pNode = root;
                }
            } else {
                TreeNode *sib = brother(pNode);
                if (getColor(sib) == red) {
                    setColor(sib, black);
                    setColor(parent(pNode), red);
                    R_Rotation(parent(pNode));
                    sib = brother(pNode);
                }

                if (getColor(left(sib)) == black && getColor(right(sib)) == black) {
                    setColor(sib, red);
                    pNode = parent(pNode);
                } else {
                    if (getColor(left(sib)) == black) {
                        setColor(right(sib), black);
                        setColor(sib, red);
                        L_Rotation(sib);
                        sib = brother(pNode);
                    }

                    setColor(sib, getColor(parent(pNode)));
                    setColor(parent(pNode), black);
                    setColor(left(sib), black);
                    R_Rotation(parent(pNode));
                    pNode = root;
                }
            }
        }
        pNode->color = black;
    }

如果有任何疑問蚪战?請大家在最后留言牵现,我一一解答。

視頻鏈接:https://pan.baidu.com/s/1wSlx2Nt4dJCBn4cSjrR2Uw
視頻密碼:731b

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末邀桑,一起剝皮案震驚了整個濱河市瞎疼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌壁畸,老刑警劉巖贼急,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瓤摧,居然都是意外死亡竿裂,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門照弥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腻异,“玉大人,你說我怎么就攤上這事这揣』诔#” “怎么了影斑?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長机打。 經(jīng)常有香客問我矫户,道長,這世上最難降的妖魔是什么残邀? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任皆辽,我火速辦了婚禮,結果婚禮上芥挣,老公的妹妹穿的比我還像新娘驱闷。我一直安慰自己,他們只是感情好空免,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布空另。 她就那樣靜靜地躺著,像睡著了一般蹋砚。 火紅的嫁衣襯著肌膚如雪扼菠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天坝咐,我揣著相機與錄音循榆,去河邊找鬼。 笑死畅厢,一個胖子當著我的面吹牛冯痢,可吹牛的內(nèi)容都是我干的氮昧。 我是一名探鬼主播框杜,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼袖肥!你這毒婦竟也來了咪辱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤椎组,失蹤者是張志新(化名)和其女友劉穎油狂,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寸癌,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡专筷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蒸苇。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片磷蛹。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖溪烤,靈堂內(nèi)的尸體忽然破棺而出味咳,到底是詐尸還是另有隱情庇勃,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布槽驶,位于F島的核電站责嚷,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏掂铐。R本人自食惡果不足惜罕拂,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望全陨。 院中可真熱鬧聂受,春花似錦、人聲如沸烤镐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽炮叶。三九已至碗旅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間镜悉,已是汗流浹背祟辟。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留侣肄,地道東北人旧困。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像稼锅,于是被迫代替她去往敵國和親吼具。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345