標籤:前序走訪 參考 ota 後繼節點 str image 哪些 nal 開關
轉自:http://www.cnblogs.com/skywang12345/p/3624343.html紅/黑樹狀結構的介紹
紅/黑樹狀結構(Red-Black Tree,簡稱R-B Tree),它一種特殊的二叉尋找樹。
紅/黑樹狀結構是特殊的二叉尋找樹,意味著它滿足二叉尋找樹的特徵:任意一個節點所包含的索引值,大於等於左孩子的索引值,小於等於右孩子的索引值。
除了具備該特性之外,紅/黑樹狀結構還包括許多額外的資訊。
紅/黑樹狀結構的每個節點上都有儲存位表示節點的顏色,顏色是紅(Red)或黑(Black)。
紅/黑樹狀結構的特性:
(1) 每個節點或者是黑色,或者是紅色。
(2) 根節點是黑色。
(3) 每個葉子節點是黑色。 [注意:這裡葉子節點,是指為空白的葉子節點!]
(4) 如果一個節點是紅色的,則它的子節點必須是黑色的。
(5) 從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。
關於它的特性,需要注意的是:
第一,特性(3)中的葉子節點,是只為空白(NIL或null)的節點。
第二,特性(5),確保沒有一條路徑會比其他路徑長出倆倍。因而,紅/黑樹狀結構是相對是接近平衡的二叉樹。
紅/黑樹狀結構如下:
紅/黑樹狀結構的Java實現(代碼說明)
紅/黑樹狀結構的基本操作是添加、刪除和旋轉。在對紅/黑樹狀結構進行添加或刪除後,會用到旋轉方法。為什麼呢?道理很簡單,添加或刪除紅/黑樹狀結構中的節點之後,紅/黑樹狀結構就發生了變化,可能不滿足紅/黑樹狀結構的5條性質,也就不再是一顆紅/黑樹狀結構了,而是一顆普通的樹。而通過旋轉,可以使這顆樹重新成為紅/黑樹狀結構。簡單點說,旋轉的目的是讓樹保持紅/黑樹狀結構的特性。
旋轉包括兩種:左旋 和 右旋。下面分別對紅/黑樹狀結構的基本操作進行介紹。
1. 基本定義
public class RBTree<T extends Comparable<T>> { private RBTNode<T> mRoot; // 根結點 private static final boolean RED = false; private static final boolean BLACK = true; public class RBTNode<T extends Comparable<T>> { boolean color; // 顏色 T key; // 關鍵字(索引值) RBTNode<T> left; // 左孩子 RBTNode<T> right; // 右孩子 RBTNode<T> parent; // 父結點 public RBTNode(T key, boolean color, RBTNode<T> parent, RBTNode<T> left, RBTNode<T> right) { this.key = key; this.color = color; this.parent = parent; this.left = left; this.right = right; } } ...}
RBTree是紅/黑樹狀結構對應的類,RBTNode是紅/黑樹狀結構的節點類。在RBTree中包含了根節點mRoot和紅/黑樹狀結構的相關API。
注意:在實現紅/黑樹狀結構API的過程中,我重載了許多函數。重載的原因,一是因為有的API是內部介面,有的是外部介面;二是為了讓結構更加清晰。
2. 左旋
對x進行左旋,意味著"將x變成一個左節點"。
左旋的實現代碼(Java語言)
/* * 對紅/黑樹狀結構的節點(x)進行左旋轉 * * 左旋(對節點x進行左旋): * px px * / / * x y * / \ --(左旋)-. / \ # * lx y x ry * / \ / * ly ry lx ly * * */private void leftRotate(RBTNode<T> x) { // 設定x的右孩子為y RBTNode<T> y = x.right; // 將 “y的左孩子” 設為 “x的右孩子”; // 如果y的左孩子非空,將 “x” 設為 “y的左孩子的父親” x.right = y.left; if (y.left != null) y.left.parent = x; // 將 “x的父親” 設為 “y的父親” y.parent = x.parent; if (x.parent == null) { this.mRoot = y; // 如果 “x的父親” 是空節點,則將y設為根節點 } else { if (x.parent.left == x) x.parent.left = y; // 如果 x是它父節點的左孩子,則將y設為“x的父節點的左孩子” else x.parent.right = y; // 如果 x是它父節點的左孩子,則將y設為“x的父節點的左孩子” } // 將 “x” 設為 “y的左孩子” y.left = x; // 將 “x的父節點” 設為 “y” x.parent = y;}
3. 右旋
對y進行左旋,意味著"將y變成一個右節點"。
右旋的實現代碼(Java語言)
/* * 對紅/黑樹狀結構的節點(y)進行右旋轉 * * 右旋(對節點y進行左旋): * py py * / / * y x * / \ --(右旋)-. / \ # * x ry lx y * / \ / \ # * lx rx rx ry * */private void rightRotate(RBTNode<T> y) { // 設定x是當前節點的左孩子。 RBTNode<T> x = y.left; // 將 “x的右孩子” 設為 “y的左孩子”; // 如果"x的右孩子"不為空白的話,將 “y” 設為 “x的右孩子的父親” y.left = x.right; if (x.right != null) x.right.parent = y; // 將 “y的父親” 設為 “x的父親” x.parent = y.parent; if (y.parent == null) { this.mRoot = x; // 如果 “y的父親” 是空節點,則將x設為根節點 } else { if (y == y.parent.right) y.parent.right = x; // 如果 y是它父節點的右孩子,則將x設為“y的父節點的右孩子” else y.parent.left = x; // (y是它父節點的左孩子) 將x設為“x的父節點的左孩子” } // 將 “y” 設為 “x的右孩子” x.right = y; // 將 “y的父節點” 設為 “x” y.parent = x;}
4. 添加
將一個節點插入到紅/黑樹狀結構中,需要執行哪些步驟呢?首先,將紅/黑樹狀結構當作一顆二叉尋找樹,將節點插入;然後,將節點著色為紅色;最後,通過"旋轉和重新著色"等一系列操作來修正該樹,使之重新成為一顆紅/黑樹狀結構。詳細描述如下:
第一步: 將紅/黑樹狀結構當作一顆二叉尋找樹,將節點插入。
紅/黑樹狀結構本身就是一顆二叉尋找樹,將節點插入後,該樹仍然是一顆二叉尋找樹。也就意味著,樹的索引值仍然是有序的。此外,無論是左旋還是右旋,若旋轉之前這棵樹是二叉尋找樹,旋轉之後它一定還是二叉尋找樹。這也就意味著,任何的旋轉和重新著色操作,都不會改變它仍然是一顆二叉尋找樹的事實。
好吧?那接下來,我們就來想方設法的旋轉以及重新著色,使這顆樹重新成為紅/黑樹狀結構!
第二步:將插入的節點著色為"紅色"。
為什麼著色成紅色,而不是黑色呢?為什麼呢?在回答之前,我們需要重新溫習一下紅/黑樹狀結構的特性:
(1) 每個節點或者是黑色,或者是紅色。
(2) 根節點是黑色。
(3) 每個葉子節點是黑色。 [注意:這裡葉子節點,是指為空白的葉子節點!]
(4) 如果一個節點是紅色的,則它的子節點必須是黑色的。
(5) 從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。
將插入的節點著色為紅色,不會違背"特性(5)"!少違背一條特性,就意味著我們需要處理的情況越少。接下來,就要努力的讓這棵樹滿足其它性質即可;滿足了的話,它就又是一顆紅/黑樹狀結構了。o(∩∩)o...哈哈
第三步: 通過一系列的旋轉或著色等操作,使之重新成為一顆紅/黑樹狀結構。
第二步中,將插入節點著色為"紅色"之後,不會違背"特性(5)"。那它到底會違背哪些特性呢?
對於"特性(1)",顯然不會違背了。因為我們已經將它塗成紅色了。
對於"特性(2)",顯然也不會違背。在第一步中,我們是將紅/黑樹狀結構當作二叉尋找樹,然後執行的插入操作。而根據二叉尋找數的特點,插入操作不會改變根節點。所以,根節點仍然是黑色。
對於"特性(3)",顯然不會違背了。這裡的葉子節點是指的空葉子節點,插入非空節點並不會對它們造成影響。
對於"特性(4)",是有可能違背的!
那接下來,想辦法使之"滿足特性(4)",就可以將樹重新構造成紅/黑樹狀結構了。
添加操作的實現代碼(Java語言)
/* * 將結點插入到紅/黑樹狀結構中 * * 參數說明: * node 插入的結點 // 對應《演算法導論》中的node */private void insert(RBTNode<T> node) { int cmp; RBTNode<T> y = null; RBTNode<T> x = this.mRoot; // 1. 將紅/黑樹狀結構當作一顆二叉尋找樹,將節點添加到二叉尋找樹中。 while (x != null) { y = x; cmp = node.key.compareTo(x.key); if (cmp < 0) x = x.left; else x = x.right; } node.parent = y; if (y!=null) { cmp = node.key.compareTo(y.key); if (cmp < 0) y.left = node; else y.right = node; } else { this.mRoot = node; } // 2. 設定節點的顏色為紅色 node.color = RED; // 3. 將它重新修正為一顆二叉尋找樹 insertFixUp(node);}/* * 建立結點(key),並將其插入到紅/黑樹狀結構中 * * 參數說明: * key 插入結點的索引值 */public void insert(T key) { RBTNode<T> node=new RBTNode<T>(key,BLACK,null,null,null); // 如果建立結點失敗,則返回。 if (node != null) insert(node);}
內部介面 -- insert(node)的作用是將"node"節點插入到紅/黑樹狀結構中。
外部介面 -- insert(key)的作用是將"key"添加到紅/黑樹狀結構中。
添加修正操作的實現代碼(Java語言)
/* * 紅/黑樹狀結構插入修正函數 * * 在向紅/黑樹狀結構中插入節點之後(失去平衡),再調用該函數; * 目的是將它重新塑造成一顆紅/黑樹狀結構。 * * 參數說明: * node 插入的結點 // 對應《演算法導論》中的z */private void insertFixUp(RBTNode<T> node) { RBTNode<T> parent, gparent; // 若“父節點存在,並且父節點的顏色是紅色” while (((parent = parentOf(node))!=null) && isRed(parent)) { gparent = parentOf(parent); //若“父節點”是“祖父節點的左孩子” if (parent == gparent.left) { // Case 1條件:叔叔節點是紅色 RBTNode<T> uncle = gparent.right; if ((uncle!=null) && isRed(uncle)) { setBlack(uncle); setBlack(parent); setRed(gparent); node = gparent; continue; } // Case 2條件:叔叔是黑色,且當前節點是右孩子 if (parent.right == node) { RBTNode<T> tmp; leftRotate(parent); tmp = parent; parent = node; node = tmp; } // Case 3條件:叔叔是黑色,且當前節點是左孩子。 setBlack(parent); setRed(gparent); rightRotate(gparent); } else { //若“z的父節點”是“z的祖父節點的右孩子” // Case 1條件:叔叔節點是紅色 RBTNode<T> uncle = gparent.left; if ((uncle!=null) && isRed(uncle)) { setBlack(uncle); setBlack(parent); setRed(gparent); node = gparent; continue; } // Case 2條件:叔叔是黑色,且當前節點是左孩子 if (parent.left == node) { RBTNode<T> tmp; rightRotate(parent); tmp = parent; parent = node; node = tmp; } // Case 3條件:叔叔是黑色,且當前節點是右孩子。 setBlack(parent); setRed(gparent); leftRotate(gparent); } } // 將根節點設為黑色 setBlack(this.mRoot);}
insertFixUp(node)的作用是對應"上面所講的第三步"。它是一個內部介面。
5. 刪除操作
將紅/黑樹狀結構內的某一個節點刪除。需要執行的操作依次是:首先,將紅/黑樹狀結構當作一顆二叉尋找樹,將該節點從二叉尋找樹中刪除;然後,通過"旋轉和重新著色"等一系列來修正該樹,使之重新成為一棵紅/黑樹狀結構。詳細描述如下:
第一步:將紅/黑樹狀結構當作一顆二叉尋找樹,將節點刪除。
這和"刪除常規二叉尋找樹中刪除節點的方法是一樣的"。分3種情況:
① 被刪除節點沒有兒子,即為分葉節點。那麼,直接將該節點刪除就OK了。
② 被刪除節點只有一個兒子。那麼,直接刪除該節點,並用該節點的唯一子節點頂替它的位置。
③ 被刪除節點有兩個兒子。那麼,先找出它的後繼節點;然後把“它的後繼節點的內容”複製給“該節點的內容”;之後,刪除“它的後繼節點”。在這裡,後繼節點相當於替身,在將後繼節點的內容複寫給"被刪除節點"之後,再將後繼節點刪除。這樣就巧妙的將問題轉換為"刪除後繼節點"的情況了,下面就考慮後繼節點。 在"被刪除節點"有兩個非空子節點的情況下,它的後繼節點不可能是雙子非空。既然"的後繼節點"不可能雙子都非空,就意味著"該節點的後繼節點"要麼沒有兒子,要麼只有一個兒子。若沒有兒子,則按"情況① "進行處理;若只有一個兒子,則按"情況② "進行處理。
第二步:通過"旋轉和重新著色"等一系列來修正該樹,使之重新成為一棵紅/黑樹狀結構。
因為"第一步"中刪除節點之後,可能會違背紅/黑樹狀結構的特性。所以需要通過"旋轉和重新著色"來修正該樹,使之重新成為一棵紅/黑樹狀結構。
刪除操作的實現代碼(Java語言)
/* * 刪除結點(node),並返回被刪除的結點 * * 參數說明: * node 刪除的結點 */private void remove(RBTNode<T> node) { RBTNode<T> child, parent; boolean color; // 被刪除節點的"左右孩子都不為空白"的情況。 if ( (node.left!=null) && (node.right!=null) ) { // 被刪節點的後繼節點。(稱為"取代節點") // 用它來取代"被刪節點"的位置,然後再將"被刪節點"去掉。 RBTNode<T> replace = node; // 擷取後繼節點 replace = replace.right; while (replace.left != null) replace = replace.left; // "node節點"不是根節點(只有根節點不存在父節點) if (parentOf(node)!=null) { if (parentOf(node).left == node) parentOf(node).left = replace; else parentOf(node).right = replace; } else { // "node節點"是根節點,更新根節點。 this.mRoot = replace; } // child是"取代節點"的右孩子,也是需要"調整的節點"。 // "取代節點"肯定不存在左孩子!因為它是一個後繼節點。 child = replace.right; parent = parentOf(replace); // 儲存"取代節點"的顏色 color = colorOf(replace); // "被刪除節點"是"它的後繼節點的父節點" if (parent == node) { parent = replace; } else { // child不為空白 if (child!=null) setParent(child, parent); parent.left = child; replace.right = node.right; setParent(node.right, replace); } replace.parent = node.parent; replace.color = node.color; replace.left = node.left; node.left.parent = replace; if (color == BLACK) removeFixUp(child, parent); node = null; return ; } if (node.left !=null) { child = node.left; } else { child = node.right; } parent = node.parent; // 儲存"取代節點"的顏色 color = node.color; if (child!=null) child.parent = parent; // "node節點"不是根節點 if (parent!=null) { if (parent.left == node) parent.left = child; else parent.right = child; } else { this.mRoot = child; } if (color == BLACK) removeFixUp(child, parent); node = null;}/* * 刪除結點(z),並返回被刪除的結點 * * 參數說明: * tree 紅/黑樹狀結構的根結點 * z 刪除的結點 */public void remove(T key) { RBTNode<T> node; if ((node = search(mRoot, key)) != null) remove(node);}
內部介面 -- remove(node)的作用是將"node"節點插入到紅/黑樹狀結構中。
外部介面 -- remove(key)刪除紅/黑樹狀結構中索引值為key的節點。
刪除修正操作的實現代碼(Java語言)
/* * 紅/黑樹狀結構刪除修正函數 * * 在從紅/黑樹狀結構中刪除插入節點之後(紅/黑樹狀結構失去平衡),再調用該函數; * 目的是將它重新塑造成一顆紅/黑樹狀結構。 * * 參數說明: * node 待修正的節點 */private void removeFixUp(RBTNode<T> node, RBTNode<T> parent) { RBTNode<T> other; while ((node==null || isBlack(node)) && (node != this.mRoot)) { if (parent.left == node) { other = parent.right; if (isRed(other)) { // Case 1: x的兄弟w是紅色的 setBlack(other); setRed(parent); leftRotate(parent); other = parent.right; } if ((other.left==null || isBlack(other.left)) && (other.right==null || isBlack(other.right))) { // Case 2: x的兄弟w是黑色,且w的倆個孩子也都是黑色的 setRed(other); node = parent; parent = parentOf(node); } else { if (other.right==null || isBlack(other.right)) { // Case 3: x的兄弟w是黑色的,並且w的左孩子是紅色,右孩子為黑色。 setBlack(other.left); setRed(other); rightRotate(other); other = parent.right; } // Case 4: x的兄弟w是黑色的;並且w的右孩子是紅色的,左孩子任意顏色。 setColor(other, colorOf(parent)); setBlack(parent); setBlack(other.right); leftRotate(parent); node = this.mRoot; break; } } else { other = parent.left; if (isRed(other)) { // Case 1: x的兄弟w是紅色的 setBlack(other); setRed(parent); rightRotate(parent); other = parent.left; } if ((other.left==null || isBlack(other.left)) && (other.right==null || isBlack(other.right))) { // Case 2: x的兄弟w是黑色,且w的倆個孩子也都是黑色的 setRed(other); node = parent; parent = parentOf(node); } else { if (other.left==null || isBlack(other.left)) { // Case 3: x的兄弟w是黑色的,並且w的左孩子是紅色,右孩子為黑色。 setBlack(other.right); setRed(other); leftRotate(other); other = parent.left; } // Case 4: x的兄弟w是黑色的;並且w的右孩子是紅色的,左孩子任意顏色。 setColor(other, colorOf(parent)); setBlack(parent); setBlack(other.left); rightRotate(parent); node = this.mRoot; break; } } } if (node!=null) setBlack(node);}
removeFixup(node, parent)是對應"上面所講的第三步"。它是一個內部介面。
紅/黑樹狀結構的Java實現(完整源碼)
下面是紅/黑樹狀結構實現的完整代碼和相應的測試程式。
(1) 除了上面所說的"左旋"、"右旋"、"添加"、"刪除"等基本操作之後,還實現了"遍曆"、"尋找"、"列印"、"最小值"、"最大值"、"建立"、"銷毀"等介面。
(2) 函數介面大多分為內部介面和外部介面。內部介面是private函數,外部介面則是public函數。
(3) 測試代碼中提供了"插入"和"刪除"動作的檢測開關。預設是關閉的,開啟方法可以參考"代碼中的說明"。建議在開啟開關後,在草稿上自己動手繪製一下紅/黑樹狀結構。
紅/黑樹狀結構的實現檔案(RBTree.java)
View Code
紅/黑樹狀結構的測試檔案(RBTreeTest.java)
View Code
紅/黑樹狀結構的Java測試程式
前面已經給出了紅/黑樹狀結構的測試代碼(RBTreeTest.java),這裡就不再重複說明。下面是測試程式的運行結果:
== 未經處理資料: 10 40 30 60 90 70 20 50 80 == 前序走訪: 30 10 20 60 40 50 80 70 90 == 中序遍曆: 10 20 30 40 50 60 70 80 90 == 後序遍曆: 20 10 50 40 70 90 80 60 30 == 最小值: 10== 最大值: 90== 樹的詳細資料: 30(B) is root10(B) is 30‘s left child20(R) is 10‘s right child60(R) is 30‘s right child40(B) is 60‘s left child50(R) is 40‘s right child80(B) is 60‘s right child70(R) is 80‘s left child90(R) is 80‘s right child
Java實現紅/黑樹狀結構