生活随笔
收集整理的這篇文章主要介紹了
算法导论之红黑树的学习
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
最近學習了二叉搜索樹中的紅黑樹,感覺收獲頗豐,在此寫一篇文章小結一下學到的知識,順便手寫一下Java代碼。
?
1.引言 先來講講什么是二叉搜索樹,二叉搜索樹有如下特點:他是以一顆二叉樹(最多有兩個子結點)來組織的,對于樹中的某個節點,其左子樹的所有元素均小于該節點,其右子樹的元素均大于該節點。我們知道一顆有N個節點的二叉樹的高度至少為lgN,然后在樹上的操作都與其高度有關,因此限制樹的高度就顯得非常有必要。當一個二叉搜索樹的高度是lgN時,在該樹上的插入刪除搜索等操作均為O(lgN)的時間復雜度,但當二叉搜索樹不小心插入成了鏈表,高度為N的時候,在樹上的操作就變為O(N)了。因此我們有許多種平衡二叉樹通過特定的方法來限制樹的高度,紅黑樹就是其中的一種。紅黑樹(Red Black Tree) 是一種自平衡二叉查找樹,是在計算機科學中用到的一種數據結構,它在每個節點上增加了一個存儲位來表示節點的顏色,可以為紅色或黑色。通過對任何一條從根到葉子的簡單路徑上各個節點的顏色進行約束,紅黑樹確保沒有一條路徑會比其他路徑長出2倍,因此是近似于平衡的。
2.紅黑樹的性質 一顆紅黑樹是滿足以下紅黑性質的二叉搜索樹:
每個節點是紅色或黑色 根是黑色 葉節點(null)是黑色的 紅色的節點的兩個子結點均為黑色 對于每個節點,從該節點到其所有后代的簡單路徑上,均包含相同數目的黑色節點(我們把到葉節點的黑色節點數稱為黑高) Java中樹的節點類如下:
[java] ?view plaincopy
enum?RBColor?{?? ????RED,?BLACK;?? }?? ?? class?RBTreeNode?{?? ????RBTreeNode?p?=?nullNode;???? ????RBTreeNode?left?=?nullNode;? ????RBTreeNode?right?=?nullNode;???? ????int?val;???? ????RBColor?color;?? ?????? ????public?RBTreeNode()?{};?? ????RBTreeNode(int?val)?{?? ????????this.val?=?val;?? ????}?? ????@Override?? ????public?String?toString()?{?? ????????return?"?("?+?val?+?"?"?+??color?+?")?";?? ????}?? ???? ????public?static?RBTreeNode?nullNode?=?new?RBTreeNode()?{?? ????????{?? ????????????color?=?RBColor.BLACK;? ????????}?? ????????@Override?? ????????public?String?toString()?{?? ????????????return?"?(null?"?+??color?+?")?";?? ????????}?? ????};?? }?? 3.旋轉 紅黑樹的關鍵操作在于其插入和刪除操作,但在講解這兩步關鍵操作之前,我們得定義一些輔助方法來讓我們更好的完成任務,該輔助方法就是樹的旋轉,示意圖如下: 旋轉由兩種,分別是左旋和右旋,相信上圖已經表示的非常明確,這里就不再細說,值得注意的是:在旋轉操作中只有指針的改變,其他屬性都保持不變。對旋轉前后的樹使用中序遍歷將得到相同的結果。 下面是旋轉的Java代碼:
[java] ?view plaincopy
public?static?RBTreeNode?leftRotate(RBTreeNode?root,?RBTreeNode?node)?{?? ????if?(node.right?==?RBTreeNode.nullNode)?? ????????return?root;???? ?????? ????RBTreeNode?right?=?node.right;?? ???? ????node.right?=?right.left;?? ????if?(node.right?!=?RBTreeNode.nullNode)?? ????????node.right.p?=?node;?? ???? ????if?(node.p?!=?RBTreeNode.nullNode)?{?? ????????right.p?=?node.p;?? ????????if?(node.p.left?==?node)?? ????????????node.p.left?=?right;?? ????????else?? ????????????node.p.right?=?right;?? ????}?else?{?? ????????root?=?right;? ????????root.p?=?RBTreeNode.nullNode;?? ????}?? ???? ????right.left?=?node;?? ????node.p?=?right;?? ????return?root;?? }?? ?? public?static?RBTreeNode?rightRotate(RBTreeNode?root,?RBTreeNode?node)?{?? ????if?(node.left?==?RBTreeNode.nullNode)?? ????????return?root;? ?????? ????RBTreeNode?left?=?node.left;?? ???? ????node.left?=?left.right;?? ????if?(node.left?!=?RBTreeNode.nullNode)?? ????????node.left.p?=?node;?? ???? ????if?(node.p?!=?RBTreeNode.nullNode)?{?? ????????left.p?=?node.p;?? ????????if?(node.p.left?==?node)?? ????????????node.p.left?=?left;?? ????????else??? ????????????node.p.right?=?left;?? ????}?else?{?? ????????root?=?left;?? ????????root.p?=?RBTreeNode.nullNode;?? ????}?? ???? ????left.right?=?node;?? ????node.p?=?left;?? ????return?root;?? }?? 4.插入 終于來到紅黑樹的第一個關鍵步驟了:插入操作。 對與插入操作我們利用如下思想解決:我們先把紅黑樹看成一個普通的二叉搜索樹,對其進行插入操作,插入完成后,我們把新加入的節點染成紅色,此時紅黑樹的紅黑性質被破壞,然后再通過特定的方法來維護紅黑樹的性質。 插入的Java代碼如下:
[java] ?view plaincopy
public?static?RBTreeNode?rbInsert(RBTreeNode?root,?RBTreeNode?insertNode)?{?? ????RBTreeNode?position?=?root,?parent?=?RBTreeNode.nullNode;? ????while?(position?!=?RBTreeNode.nullNode)?{?? ????????parent?=?position;?? ????????if?(insertNode.val?<?position.val)? ????????????position?=?position.left;?? ????????else? ????????????position?=?position.right;?? ????}?? ????insertNode.p?=?parent;?? ????if?(parent?==?RBTreeNode.nullNode)? ????????root?=?insertNode;?? ????else?if?(insertNode.val?<?parent.val)? ????????parent.left?=?insertNode;?? ????else? ????????parent.right?=?insertNode;?? ????insertNode.color?=?RBColor.RED;? ????return?rbInsertFixup(root,?insertNode);? }?? 好,終于來到重點了,紅黑樹的插入操作前半部分與一般二叉搜索樹別無二致,區別在于最后把新加入的節點染成紅色和恢復紅黑樹性質的部分。我們先來思考一下往紅黑樹插入一個紅節點會破壞紅黑樹的什么性質?首先性質1、3、5是不會受影響的,那么當我們插入的節點是紅黑樹的根結點時會影響性質2,根節點變成了紅色,此時我們把根節點染成黑色即可。當我們插入節點的父節點是紅色時會影響性質4,紅色節點有一個為紅色的子結點。對于以上這些影響我們分為3種情況來處理:
關鍵詞:叔節點 下面我們假設插入的節點為z(紅色),其父節點為x(紅色,為祖父節點的左節點,右節點情況鏡像處理即可),其叔節點為y(未知),祖父節點為w(黑色)
情況1:插入節點z的叔節點y為紅色 此時的情況如圖所示(下圖省略了部分不關鍵的子樹): 此時的處理方法很簡單,我們只需把祖父節點的黑色“扒”下來放到父節點X和叔節點Y即可,此時對于節點Z就保持了紅黑樹的性質4,然而進行了此操作后我們還需要對祖父節點W進行繼續遍歷,因為此時祖父節點有可能違反了紅黑樹的性質。當我們遍歷的祖父節點為根結點時,把根結點變為黑色即可。
情況2:插入節點z的叔節點y是黑色的,且z是一個右孩子 情況3:插入節點z的叔節點y是黑色的,且z是一個左孩子 這兩種情況可以放一起討論,因為我們會把情況2轉化為情況3,示意圖如下: 左上角為情況2,此時叔節點w為黑色,且插入節點z為父節點x的右孩子,此時我們對父節點x進行一次左旋,然后交換x和z的引用,即可轉換為右上角的情況3. 右上角為情況3,此時叔節點w為黑色,且插入節點z為父節點x的左孩子,此時我們進行如下操作即可恢復紅黑樹的性質:
交換父節點x和祖父節點w的顏色 對祖父節點w進行右旋 上面的操作既修正了對性質4的違反,也沒有引起對其他紅黑樹性質的違反,因此我們此時可以結束對紅黑樹的性質修復工作。 下面給出紅黑樹插入時性質修復的Java代碼:
[java] ?view plaincopy
public?static?RBTreeNode?rbInsertFixup(RBTreeNode?root,?RBTreeNode?node)?{?? ???? ????RBTreeNode?parent?=?node.p,?grandParent,?parentBorther;?? ????while(parent?!=?RBTreeNode.nullNode?&&?parent.color?==?RBColor.RED)?{?? ????????grandParent?=?parent.p;?? ????????if?(grandParent.left?==?parent)?{? ????????????parentBorther?=?grandParent.right;? ????????????if?(parentBorther?!=?RBTreeNode.nullNode?&&?parentBorther.color?==?RBColor.RED)?{? ????????????????grandParent.color?=?RBColor.RED;? ????????????????parent.color?=?RBColor.BLACK;? ????????????????parentBorther.color?=?RBColor.BLACK;?? ????????????????node?=?grandParent;? ????????????}?else?{?? ????????????????if?(parent.right?==?node)?{? ????????????????????root?=?leftRotate(root,?parent);? ???????????????????? ????????????????????RBTreeNode?temp?=?node;?? ????????????????????node?=?parent;?? ????????????????????parent?=?temp;?? ????????????????}?? ???????????????? ????????????????grandParent.color?=?RBColor.RED;? ????????????????parent.color?=?RBColor.BLACK;? ????????????????root?=?rightRotate(root,?grandParent);? ????????????????node?=?root;? ????????????}?? ????????}?else?{? ????????????parentBorther?=?grandParent.left;?? ????????????if?(parentBorther?!=?RBTreeNode.nullNode?&&?parentBorther.color?==?RBColor.RED)?{? ????????????????grandParent.color?=?RBColor.RED;?? ????????????????parent.color?=?RBColor.BLACK;?? ????????????????parentBorther.color?=?RBColor.BLACK;?? ????????????????node?=?grandParent;?? ????????????}?else?{?? ????????????????if?(parent.left?==?node)?{? ????????????????????root?=?rightRotate(root,?parent);?? ????????????????????RBTreeNode?temp?=?node;?? ????????????????????node?=?parent;?? ????????????????????parent?=?temp;?? ????????????????}?? ???????????????? ????????????????grandParent.color?=?RBColor.RED;?? ????????????????parent.color?=?RBColor.BLACK;?? ????????????????root?=?leftRotate(root,?grandParent);?? ????????????????node?=?root;?? ????????????}?? ????????}?? ????????parent?=?node.p;?? ????}?? ???? ????root.color?=?RBColor.BLACK;?? ????return?root;?? }?? 5.刪除 講完插入,我們來講講刪除操作。與插入類似,再刪除前我們先把紅黑樹當成是一顆普通的二叉搜索樹來處理刪除節點的操作。但在把節點刪除過后,由于刪除節點會帶走一種顏色,因此我們需要記錄下被刪除的顏色和刪除顏色的位置,最后我們再考慮如何修復樹的紅黑性質。二叉搜索樹刪除節點分為三種情況,這里簡單提一下:
刪除節點沒有子節點:直接把刪除節點的位置置空即可 刪除節點有一個子節點:用該子節點頂替刪除節點的位置 刪除節點有兩個子節點:這是比較復雜的情況,此時我們要從刪除節點的兩邊子樹中尋找一個節點來頂替其位置,我們可以找右子樹的最小節點或左子樹的最大節點,本文給出的代碼為尋找右子樹的最小節點。同時在代碼中我們把刪除節點的顏色賦給頂替節點,從而使實際刪除顏色的節點為頂替節點。 Java代碼如下:
[java] ?view plaincopy
public?static?RBTreeNode?rbDelete(RBTreeNode?root,?RBTreeNode?deleteNode)?{?? ????RBTreeNode?replaceNode,?fixNode?=?RBTreeNode.nullNode;? ????RBTreeNode?fixNodeParent?=?deleteNode.p;?? ????RBColor?deleteColor?=?deleteNode.color;? ????if?(deleteNode.left?==?RBTreeNode.nullNode?&&?deleteNode.right?==?RBTreeNode.nullNode)? ????????replaceNode?=?RBTreeNode.nullNode;?? ????else?if?(deleteNode.right?==?RBTreeNode.nullNode)?{? ????????replaceNode?=?deleteNode.left;?? ????????fixNode?=?replaceNode;?? ????}?else?if?(deleteNode.left?==?RBTreeNode.nullNode)?{? ????????replaceNode?=?deleteNode.right;?? ????????fixNode?=?replaceNode;?? ????}?else?{? ????????replaceNode?=?deleteNode.right;?? ????????while?(replaceNode.left?!=?RBTreeNode.nullNode)? ????????????replaceNode?=?replaceNode.left;?? ????????fixNode?=?replaceNode.right;? ????????if?(replaceNode.p?==?deleteNode)?{? ????????????if?(fixNode?!=?RBTreeNode.nullNode)? ????????????????fixNode.p?=?replaceNode;?? ????????????fixNodeParent?=?replaceNode;?? ????????}?else?{?? ????????????replaceNode.p.left?=?fixNode;? ????????????if?(fixNode?!=?RBTreeNode.nullNode)? ????????????????fixNode.p?=?replaceNode.p;?? ????????????fixNodeParent?=?replaceNode.p;?? ????????????replaceNode.right?=?deleteNode.right;?? ????????}?? ???????? ????????deleteColor?=?replaceNode.color;?? ????????replaceNode.color?=?deleteNode.color;??? ????????replaceNode.left?=?deleteNode.left;?? ????}?? ????if?(replaceNode?!=?RBTreeNode.nullNode)? ????????replaceNode.p?=?deleteNode.p;?? ????if?(deleteNode.p?==?RBTreeNode.nullNode)? ????????root?=?replaceNode;?? ????else?{? ????????if?(deleteNode.p.left?==?deleteNode)?? ????????????deleteNode.p.left?=?replaceNode;?? ????????else?? ????????????deleteNode.p.right?=?replaceNode;?? ????}?? ????if?(deleteColor?==?RBColor.BLACK)? ????????root?=?rbDeleteFixup(root,?fixNode,?fixNodeParent);?? ????return?root;?? }?? 接下來我們來考慮一下一上的刪除操作會影響紅黑樹的什么性質。 首先,如果刪除的節點顏色為紅色,則不會影響任何紅黑性質。但如果刪除的顏色是黑色,則可能影響性質2(根節點是黑色的),也可能影響性質4(紅色的節點的兩個子結點均為黑色),也可能影響性質5(對于每個節點,從該節點到其所有后代的簡單路徑上,均包含相同數目的黑色節點)。那么當刪除的節點顏色為黑色時,對于如何修復刪除后的紅黑性質,我們采用以下思考方式: 我們假設修復位置的節點具有兩種顏色,該節點原來的顏色,以及我們被刪除的黑色。那么:
如果該節點原來為紅色,那么我們被刪除的黑色可以直接覆蓋其顏色不影響任何紅黑性質 如果該節點是黑色同時他也是根節點,那么我們可以簡單的“消除”掉節點上面的一層黑色 如果該節點是黑色,但不是根節點,我們只能通過旋轉和重新著色的方法轉換修復的位置或退出循環 以下把修復刪除紅黑性質的工作分為4中情況,此處假設修復位置節點為A(黑色,此處假設為父節點的左節點,右節點請鏡像處理),其父節點為B,兄弟節點為C,兄弟節點的左子節點為D,兄弟節點的右子節點為E。
關鍵詞:兄弟節點 情況1:A的兄弟節點為紅色 如上圖所示,此時我們先交換父節點B和兄弟節點C的顏色,然后對父節點B進行左旋,以上操作并不會影響紅黑樹性質,而我們也把情況1轉化為了別的情況。
情況2:A的兄弟節點為黑色,其子節點均為黑色(下圖灰色代表未知顏色) 此時的處理方法很簡單,因為A節點和其兄弟節點C均為黑色,且C的子節點也均為黑色,因此我們可以把A節點和C節點的黑色上移到父節點B上,再把修復位置換為父節點B,針對父節點B繼續進行修復。(如果父節點B是紅色或根節點就可以停止修復了~)
情況3:A的兄弟節點為黑色,兄弟節點的左子節點為紅色,右子節點為黑色 此時我們首先交換兄弟節點C與其左子紅色節點D的顏色,然后對兄弟節點C進行右旋,把情況3轉化為情況4繼續處理。
情況4:A的兄弟節點為黑色,兄弟節點的右子節點為紅色 此時我們進行如下變換操作:
把父節點B和兄弟節點的右子節點E染成黑色,兄弟節點C染成父節點顏色 對父節點B進行左旋 以上操作在沒有破壞紅黑樹性質的情況下,消除了節點A的一重黑色,因此至此修復過程可以結束了。 刪除時修復過程的Java代碼如下:
[java] ?view plaincopy
public?static?RBTreeNode?rbDeleteFixup(RBTreeNode?root,?RBTreeNode?fixNode,?RBTreeNode?parent)?{?? ????RBTreeNode?brother;?? ????while?(root?!=?fixNode?&&?fixNode.color?==?RBColor.BLACK)?{?? ????????parent?=?fixNode.p?==?null???parent?:?fixNode.p;? ????????if?(fixNode?==?parent.left)?{? ????????????brother?=?parent.right;?? ????????????if?(brother.color?==?RBColor.RED)?{? ???????????????? ????????????????RBColor?temp?=?brother.color;?? ????????????????brother.color?=?parent.color;?? ????????????????parent.color?=?temp;?? ???????????????? ????????????????root?=?leftRotate(root,?parent);?? ????????????}?else?if?(brother?==?RBTreeNode.nullNode)?{? ???????????????? ????????????????fixNode?=?parent;?? ????????????}?else?if?(brother.left.color?==?RBColor.BLACK?&&?? ????????????????????brother.right.color?==?RBColor.BLACK)?{? ????????????????brother.color?=?RBColor.RED;?? ????????????????fixNode?=?parent;? ????????????}?else?{? ????????????????if?(brother.left.color?==?RBColor.RED?&&?? ????????????????????brother.right.color?==?RBColor.BLACK)?{? ???????????????????? ????????????????????brother.color?=?RBColor.RED;?? ????????????????????brother.left.color?=?RBColor.BLACK;?? ???????????????????? ????????????????????root?=?rightRotate(root,?brother);?? ????????????????????brother?=?brother.p;?? ????????????????}?? ???????????????? ???????????????? ????????????????brother.color?=?parent.color;?? ????????????????parent.color?=?RBColor.BLACK;?? ????????????????brother.right.color?=?RBColor.BLACK;?? ???????????????? ????????????????root?=?leftRotate(root,?parent);?? ????????????????break;?? ????????????}?? ????????}?else?{?? ????????????brother?=?parent.left;?? ????????????if?(brother.color?==?RBColor.RED)?{? ???????????????? ????????????????RBColor?temp?=?brother.color;?? ????????????????brother.color?=?parent.color;?? ????????????????parent.color?=?temp;?? ???????????????? ????????????????root?=?rightRotate(root,?parent);?? ????????????}?else?if?(brother?==?RBTreeNode.nullNode)?{? ???????????????? ????????????????fixNode?=?parent;?? ????????????}?else?if?(brother.left.color?==?RBColor.BLACK?&&?? ????????????????????brother.right.color?==?RBColor.BLACK)?{? ????????????????brother.color?=?RBColor.RED;?? ????????????????fixNode?=?parent;? ????????????}?else?{? ????????????????if?(brother.right.color?==?RBColor.RED?&&?? ????????????????????brother.left.color?==?RBColor.BLACK)?{? ???????????????????? ????????????????????brother.color?=?RBColor.RED;?? ????????????????????brother.right.color?=?RBColor.BLACK;?? ???????????????????? ????????????????????root?=?leftRotate(root,?brother);?? ????????????????????brother?=?brother.p;?? ????????????????}?? ???????????????? ???????????????? ????????????????brother.color?=?parent.color;?? ????????????????parent.color?=?RBColor.BLACK;?? ????????????????brother.left.color?=?RBColor.BLACK;?? ???????????????? ????????????????root?=?rightRotate(root,?parent);?? ????????????????break;?? ????????????}?? ????????}?? ????}?? ????fixNode.color?=?RBColor.BLACK;?? ????return?root;?? };?? 6.打印與測試函數 這里給出本人用來測試和打印紅黑樹的Java函數:
[java] ?view plaincopy
public?static?void?main(String[]?args)?{?? ????int?num[]?=?new?int[]{5,?4,?1,?6,?3,?2};?? ????List<RBTreeNode>?list?=?new?ArrayList<>();?? ????RBTreeNode?root?=?RBTreeNode.nullNode;?? ???? ????for?(int?i?=?0;?i?<?num.length;?i++)?{?? ????????list.add(new?RBTreeNode(num[i]));?? ????????root?=?rbInsert(root,?list.get(i));?? ????????printRBTree(root);?? ????????System.out.println("");?? ????}?? ???? ????for?(int?i?=?0;?i?<?num.length;?i++)?{?? ????????root?=?rbDelete(root,?list.get(0));?? ????????list.remove(0);?? ????????printRBTree(root);?? ????????System.out.println("");?? ????}?? }?? ?? public?static?void?printRBTree(RBTreeNode?root)?{?? ????if?(root?==?RBTreeNode.nullNode)?{?? ????????System.out.println("這是一顆空樹");?? ????????return;?? ????}?? ????Queue<RBTreeNode>?q?=?new?LinkedList<>();?? ????boolean?allNull?=?false;? ????q.add(root);?? ????while?(!allNull)?{? ????????allNull?=?true;?? ????????Queue<RBTreeNode>?rowQ?=?new?LinkedList<>();? ????????RBTreeNode?node;?? ????????while?(!q.isEmpty())?{?? ????????????node?=?q.poll();?? ????????????System.out.print(node);?? ????????????if?(node?!=?RBTreeNode.nullNode)?{? ????????????????if?(node.left?!=?RBTreeNode.nullNode)?{?? ????????????????????rowQ.add(node.left);?? ????????????????????allNull?=?false;?? ????????????????}?else?? ????????????????????rowQ.add(RBTreeNode.nullNode);?? ????????????????if?(node.right?!=?RBTreeNode.nullNode)?{?? ????????????????????rowQ.add(node.right);?? ????????????????????allNull?=?false;?? ????????????????}?else?? ????????????????????rowQ.add(RBTreeNode.nullNode);?? ????????????}?else?{? ????????????????rowQ.add(RBTreeNode.nullNode);?? ????????????????rowQ.add(RBTreeNode.nullNode);?? ????????????}?? ????????}?? ????????q?=?rowQ;?? ????????System.out.println("");?? ????}?? }?? 總結,沒寫不知道,一寫嚇一跳,用Java來實現紅黑樹還是有挺多麻煩點的:
在Java中不知道如何修改根引用,所以最后都在函數上補了返回值 剛開始沒考慮null葉節點其實是算黑色節點的情況,后來補充了一個靜態變量作為葉節點 用靜態變量當葉節點使得葉節點是共享的,不能修改葉節點的left,right,p指針,因此又再刪除時添加了fixParent變量 刪除時擁有兩個子樹,但右子樹沒有左節點的情況是個坑…… 總而言之,紅黑樹的5個性質,3種插入情況,4種刪除情況記住就大概沒什么問題了~
轉載于:https://www.cnblogs.com/KingIceMou/p/6984138.html
總結
以上是生活随笔 為你收集整理的算法导论之红黑树的学习 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。