概要
前面分別介紹紅黑樹的理論知識、紅黑樹的C語言和C++的實現。本章介紹紅黑樹的Java實現,若讀者對紅黑樹的理論知識不熟悉,建立先學習紅黑樹的理論知識,再來學習本章。還是那句老話,紅黑樹的C/C++/Java實現,原理一樣,擇其一了解即可。
目錄
1. 紅黑樹的介紹
2. 紅黑樹的Java實現(代碼說明)
3. 紅黑樹的Java實現(完整源碼)
4. 紅黑樹的Java測試程序
轉載請注明出處:
更多內容:數據結構與算法系列 目錄
(01) 紅黑樹(一)之 原理和算法詳細介紹
(02) 紅黑樹(二)之 C語言的實現
(03) 紅黑樹(三)之 Linux內核中紅黑樹的經典實現
(04) 紅黑樹(四)之 C++的實現
(05) 紅黑樹(五)之 Java的實現
(06) 紅黑樹(六)之 參考資料
紅黑樹的介紹
紅黑樹(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)

1 /** 2 * Java 語言: 紅黑樹 3 * 4 * @author skywang 5 * @date 2013/11/07 6 */ 7 8 public class RBTree<T extends Comparable<T>> { 9 10 private RBTNode<T> mRoot; // 根結點 11 12 private static final boolean RED = false; 13 private static final boolean BLACK = true; 14 15 public class RBTNode<T extends Comparable<T>> { 16 boolean color; // 顏色 17 T key; // 關鍵字(鍵值) 18 RBTNode<T> left; // 左孩子 19 RBTNode<T> right; // 右孩子 20 RBTNode<T> parent; // 父結點 21 22 public RBTNode(T key, boolean color, RBTNode<T> parent, RBTNode<T> left, RBTNode<T> right) { 23 this.key = key; 24 this.color = color; 25 this.parent = parent; 26 this.left = left; 27 this.right = right; 28 } 29 30 public T getKey() { 31 return key; 32 } 33 34 public String toString() { 35 return ""+key+(this.color==RED?"(R)":"B"); 36 } 37 } 38 39 public RBTree() { 40 mRoot=null; 41 } 42 43 private RBTNode<T> parentOf(RBTNode<T> node) { 44 return node!=null ? node.parent : null; 45 } 46 private boolean colorOf(RBTNode<T> node) { 47 return node!=null ? node.color : BLACK; 48 } 49 private boolean isRed(RBTNode<T> node) { 50 return ((node!=null)&&(node.color==RED)) ? true : false; 51 } 52 private boolean isBlack(RBTNode<T> node) { 53 return !isRed(node); 54 } 55 private void setBlack(RBTNode<T> node) { 56 if (node!=null) 57 node.color = BLACK; 58 } 59 private void setRed(RBTNode<T> node) { 60 if (node!=null) 61 node.color = RED; 62 } 63 private void setParent(RBTNode<T> node, RBTNode<T> parent) { 64 if (node!=null) 65 node.parent = parent; 66 } 67 private void setColor(RBTNode<T> node, boolean color) { 68 if (node!=null) 69 node.color = color; 70 } 71 72 /* 73 * 前序遍歷"紅黑樹" 74 */ 75 private void preOrder(RBTNode<T> tree) { 76 if(tree != null) { 77 System.out.print(tree.key+" "); 78 preOrder(tree.left); 79 preOrder(tree.right); 80 } 81 } 82 83 public void preOrder() { 84 preOrder(mRoot); 85 } 86 87 /* 88 * 中序遍歷"紅黑樹" 89 */ 90 private void inOrder(RBTNode<T> tree) { 91 if(tree != null) { 92 inOrder(tree.left); 93 System.out.print(tree.key+" "); 94 inOrder(tree.right); 95 } 96 } 97 98 public void inOrder() { 99 inOrder(mRoot); 100 } 101 102 103 /* 104 * 后序遍歷"紅黑樹" 105 */ 106 private void postOrder(RBTNode<T> tree) { 107 if(tree != null) 108 { 109 postOrder(tree.left); 110 postOrder(tree.right); 111 System.out.print(tree.key+" "); 112 } 113 } 114 115 public void postOrder() { 116 postOrder(mRoot); 117 } 118 119 120 /* 121 * (遞歸實現)查找"紅黑樹x"中鍵值為key的節點 122 */ 123 private RBTNode<T> search(RBTNode<T> x, T key) { 124 if (x==null) 125 return x; 126 127 int cmp = key.compareTo(x.key); 128 if (cmp < 0) 129 return search(x.left, key); 130 else if (cmp > 0) 131 return search(x.right, key); 132 else 133 return x; 134 } 135 136 public RBTNode<T> search(T key) { 137 return search(mRoot, key); 138 } 139 140 /* 141 * (非遞歸實現)查找"紅黑樹x"中鍵值為key的節點 142 */ 143 private RBTNode<T> iterativeSearch(RBTNode<T> x, T key) { 144 while (x!=null) { 145 int cmp = key.compareTo(x.key); 146 147 if (cmp < 0) 148 x = x.left; 149 else if (cmp > 0) 150 x = x.right; 151 else 152 return x; 153 } 154 155 return x; 156 } 157 158 public RBTNode<T> iterativeSearch(T key) { 159 return iterativeSearch(mRoot, key); 160 } 161 162 /* 163 * 查找最小結點:返回tree為根結點的紅黑樹的最小結點。 164 */ 165 private RBTNode<T> minimum(RBTNode<T> tree) { 166 if (tree == null) 167 return null; 168 169 while(tree.left != null) 170 tree = tree.left; 171 return tree; 172 } 173 174 public T minimum() { 175 RBTNode<T> p = minimum(mRoot); 176 if (p != null) 177 return p.key; 178 179 return null; 180 } 181 182 /* 183 * 查找最大結點:返回tree為根結點的紅黑樹的最大結點。 184 */ 185 private RBTNode<T> maximum(RBTNode<T> tree) { 186 if (tree == null) 187 return null; 188 189 while(tree.right != null) 190 tree = tree.right; 191 return tree; 192 } 193 194 public T maximum() { 195 RBTNode<T> p = maximum(mRoot); 196 if (p != null) 197 return p.key; 198 199 return null; 200 } 201 202 /* 203 * 找結點(x)的后繼結點。即,查找"紅黑樹中數據值大於該結點"的"最小結點"。 204 */ 205 public RBTNode<T> successor(RBTNode<T> x) { 206 // 如果x存在右孩子,則"x的后繼結點"為 "以其右孩子為根的子樹的最小結點"。 207 if (x.right != null) 208 return minimum(x.right); 209 210 // 如果x沒有右孩子。則x有以下兩種可能: 211 // (01) x是"一個左孩子",則"x的后繼結點"為 "它的父結點"。 212 // (02) x是"一個右孩子",則查找"x的最低的父結點,並且該父結點要具有左孩子",找到的這個"最低的父結點"就是"x的后繼結點"。 213 RBTNode<T> y = x.parent; 214 while ((y!=null) && (x==y.right)) { 215 x = y; 216 y = y.parent; 217 } 218 219 return y; 220 } 221 222 /* 223 * 找結點(x)的前驅結點。即,查找"紅黑樹中數據值小於該結點"的"最大結點"。 224 */ 225 public RBTNode<T> predecessor(RBTNode<T> x) { 226 // 如果x存在左孩子,則"x的前驅結點"為 "以其左孩子為根的子樹的最大結點"。 227 if (x.left != null) 228 return maximum(x.left); 229 230 // 如果x沒有左孩子。則x有以下兩種可能: 231 // (01) x是"一個右孩子",則"x的前驅結點"為 "它的父結點"。 232 // (01) x是"一個左孩子",則查找"x的最低的父結點,並且該父結點要具有右孩子",找到的這個"最低的父結點"就是"x的前驅結點"。 233 RBTNode<T> y = x.parent; 234 while ((y!=null) && (x==y.left)) { 235 x = y; 236 y = y.parent; 237 } 238 239 return y; 240 } 241 242 /* 243 * 對紅黑樹的節點(x)進行左旋轉 244 * 245 * 左旋示意圖(對節點x進行左旋): 246 * px px 247 * / / 248 * x y 249 * / \ --(左旋)-. / \ # 250 * lx y x ry 251 * / \ / \ 252 * ly ry lx ly 253 * 254 * 255 */ 256 private void leftRotate(RBTNode<T> x) { 257 // 設置x的右孩子為y 258 RBTNode<T> y = x.right; 259 260 // 將 “y的左孩子” 設為 “x的右孩子”; 261 // 如果y的左孩子非空,將 “x” 設為 “y的左孩子的父親” 262 x.right = y.left; 263 if (y.left != null) 264 y.left.parent = x; 265 266 // 將 “x的父親” 設為 “y的父親” 267 y.parent = x.parent; 268 269 if (x.parent == null) { 270 this.mRoot = y; // 如果 “x的父親” 是空節點,則將y設為根節點 271 } else { 272 if (x.parent.left == x) 273 x.parent.left = y; // 如果 x是它父節點的左孩子,則將y設為“x的父節點的左孩子” 274 else 275 x.parent.right = y; // 如果 x是它父節點的左孩子,則將y設為“x的父節點的左孩子” 276 } 277 278 // 將 “x” 設為 “y的左孩子” 279 y.left = x; 280 // 將 “x的父節點” 設為 “y” 281 x.parent = y; 282 } 283 284 /* 285 * 對紅黑樹的節點(y)進行右旋轉 286 * 287 * 右旋示意圖(對節點y進行左旋): 288 * py py 289 * / / 290 * y x 291 * / \ --(右旋)-. / \ # 292 * x ry lx y 293 * / \ / \ # 294 * lx rx rx ry 295 * 296 */ 297 private void rightRotate(RBTNode<T> y) { 298 // 設置x是當前節點的左孩子。 299 RBTNode<T> x = y.left; 300 301 // 將 “x的右孩子” 設為 “y的左孩子”; 302 // 如果"x的右孩子"不為空的話,將 “y” 設為 “x的右孩子的父親” 303 y.left = x.right; 304 if (x.right != null) 305 x.right.parent = y; 306 307 // 將 “y的父親” 設為 “x的父親” 308 x.parent = y.parent; 309 310 if (y.parent == null) { 311 this.mRoot = x; // 如果 “y的父親” 是空節點,則將x設為根節點 312 } else { 313 if (y == y.parent.right) 314 y.parent.right = x; // 如果 y是它父節點的右孩子,則將x設為“y的父節點的右孩子” 315 else 316 y.parent.left = x; // (y是它父節點的左孩子) 將x設為“x的父節點的左孩子” 317 } 318 319 // 將 “y” 設為 “x的右孩子” 320 x.right = y; 321 322 // 將 “y的父節點” 設為 “x” 323 y.parent = x; 324 } 325 326 /* 327 * 紅黑樹插入修正函數 328 * 329 * 在向紅黑樹中插入節點之后(失去平衡),再調用該函數; 330 * 目的是將它重新塑造成一顆紅黑樹。 331 * 332 * 參數說明: 333 * node 插入的結點 // 對應《算法導論》中的z 334 */ 335 private void insertFixUp(RBTNode<T> node) { 336 RBTNode<T> parent, gparent; 337 338 // 若“父節點存在,並且父節點的顏色是紅色” 339 while (((parent = parentOf(node))!=null) && isRed(parent)) { 340 gparent = parentOf(parent); 341 342 //若“父節點”是“祖父節點的左孩子” 343 if (parent == gparent.left) { 344 // Case 1條件:叔叔節點是紅色 345 RBTNode<T> uncle = gparent.right; 346 if ((uncle!=null) && isRed(uncle)) { 347 setBlack(uncle); 348 setBlack(parent); 349 setRed(gparent); 350 node = gparent; 351 continue; 352 } 353 354 // Case 2條件:叔叔是黑色,且當前節點是右孩子 355 if (parent.right == node) { 356 RBTNode<T> tmp; 357 leftRotate(parent); 358 tmp = parent; 359 parent = node; 360 node = tmp; 361 } 362 363 // Case 3條件:叔叔是黑色,且當前節點是左孩子。 364 setBlack(parent); 365 setRed(gparent); 366 rightRotate(gparent); 367 } else { //若“z的父節點”是“z的祖父節點的右孩子” 368 // Case 1條件:叔叔節點是紅色 369 RBTNode<T> uncle = gparent.left; 370 if ((uncle!=null) && isRed(uncle)) { 371 setBlack(uncle); 372 setBlack(parent); 373 setRed(gparent); 374 node = gparent; 375 continue; 376 } 377 378 // Case 2條件:叔叔是黑色,且當前節點是左孩子 379 if (parent.left == node) { 380 RBTNode<T> tmp; 381 rightRotate(parent); 382 tmp = parent; 383 parent = node; 384 node = tmp; 385 } 386 387 // Case 3條件:叔叔是黑色,且當前節點是右孩子。 388 setBlack(parent); 389 setRed(gparent); 390 leftRotate(gparent); 391 } 392 } 393 394 // 將根節點設為黑色 395 setBlack(this.mRoot); 396 } 397 398 /* 399 * 將結點插入到紅黑樹中 400 * 401 * 參數說明: 402 * node 插入的結點 // 對應《算法導論》中的node 403 */ 404 private void insert(RBTNode<T> node) { 405 int cmp; 406 RBTNode<T> y = null; 407 RBTNode<T> x = this.mRoot; 408 409 // 1. 將紅黑樹當作一顆二叉查找樹,將節點添加到二叉查找樹中。 410 while (x != null) { 411 y = x; 412 cmp = node.key.compareTo(x.key); 413 if (cmp < 0) 414 x = x.left; 415 else 416 x = x.right; 417 } 418 419 node.parent = y; 420 if (y!=null) { 421 cmp = node.key.compareTo(y.key); 422 if (cmp < 0) 423 y.left = node; 424 else 425 y.right = node; 426 } else { 427 this.mRoot = node; 428 } 429 430 // 2. 設置節點的顏色為紅色 431 node.color = RED; 432 433 // 3. 將它重新修正為一顆二叉查找樹 434 insertFixUp(node); 435 } 436 437 /* 438 * 新建結點(key),並將其插入到紅黑樹中 439 * 440 * 參數說明: 441 * key 插入結點的鍵值 442 */ 443 public void insert(T key) { 444 RBTNode<T> node=new RBTNode<T>(key,BLACK,null,null,null); 445 446 // 如果新建結點失敗,則返回。 447 if (node != null) 448 insert(node); 449 } 450 451 452 /* 453 * 紅黑樹刪除修正函數 454 * 455 * 在從紅黑樹中刪除插入節點之后(紅黑樹失去平衡),再調用該函數; 456 * 目的是將它重新塑造成一顆紅黑樹。 457 * 458 * 參數說明: 459 * node 待修正的節點 460 */ 461 private void removeFixUp(RBTNode<T> node, RBTNode<T> parent) { 462 RBTNode<T> other; 463 464 while ((node==null || isBlack(node)) && (node != this.mRoot)) { 465 if (parent.left == node) { 466 other = parent.right; 467 if (isRed(other)) { 468 // Case 1: x的兄弟w是紅色的 469 setBlack(other); 470 setRed(parent); 471 leftRotate(parent); 472 other = parent.right; 473 } 474 475 if ((other.left==null || isBlack(other.left)) && 476 (other.right==null || isBlack(other.right))) { 477 // Case 2: x的兄弟w是黑色,且w的倆個孩子也都是黑色的 478 setRed(other); 479 node = parent; 480 parent = parentOf(node); 481 } else { 482 483 if (other.right==null || isBlack(other.right)) { 484 // Case 3: x的兄弟w是黑色的,並且w的左孩子是紅色,右孩子為黑色。 485 setBlack(other.left); 486 setRed(other); 487 rightRotate(other); 488 other = parent.right; 489 } 490 // Case 4: x的兄弟w是黑色的;並且w的右孩子是紅色的,左孩子任意顏色。 491 setColor(other, colorOf(parent)); 492 setBlack(parent); 493 setBlack(other.right); 494 leftRotate(parent); 495 node = this.mRoot; 496 break; 497 } 498 } else { 499 500 other = parent.left; 501 if (isRed(other)) { 502 // Case 1: x的兄弟w是紅色的 503 setBlack(other); 504 setRed(parent); 505 rightRotate(parent); 506 other = parent.left; 507 } 508 509 if ((other.left==null || isBlack(other.left)) && 510 (other.right==null || isBlack(other.right))) { 511 // Case 2: x的兄弟w是黑色,且w的倆個孩子也都是黑色的 512 setRed(other); 513 node = parent; 514 parent = parentOf(node); 515 } else { 516 517 if (other.left==null || isBlack(other.left)) { 518 // Case 3: x的兄弟w是黑色的,並且w的左孩子是紅色,右孩子為黑色。 519 setBlack(other.right); 520 setRed(other); 521 leftRotate(other); 522 other = parent.left; 523 } 524 525 // Case 4: x的兄弟w是黑色的;並且w的右孩子是紅色的,左孩子任意顏色。 526 setColor(other, colorOf(parent)); 527 setBlack(parent); 528 setBlack(other.left); 529 rightRotate(parent); 530 node = this.mRoot; 531 break; 532 } 533 } 534 } 535 536 if (node!=null) 537 setBlack(node); 538 } 539 540 /* 541 * 刪除結點(node),並返回被刪除的結點 542 * 543 * 參數說明: 544 * node 刪除的結點 545 */ 546 private void remove(RBTNode<T> node) { 547 RBTNode<T> child, parent; 548 boolean color; 549 550 // 被刪除節點的"左右孩子都不為空"的情況。 551 if ( (node.left!=null) && (node.right!=null) ) { 552 // 被刪節點的后繼節點。(稱為"取代節點") 553 // 用它來取代"被刪節點"的位置,然后再將"被刪節點"去掉。 554 RBTNode<T> replace = node; 555 556 // 獲取后繼節點 557 replace = replace.right; 558 while (replace.left != null) 559 replace = replace.left; 560 561 // "node節點"不是根節點(只有根節點不存在父節點) 562 if (parentOf(node)!=null) { 563 if (parentOf(node).left == node) 564 parentOf(node).left = replace; 565 else 566 parentOf(node).right = replace; 567 } else { 568 // "node節點"是根節點,更新根節點。 569 this.mRoot = replace; 570 } 571 572 // child是"取代節點"的右孩子,也是需要"調整的節點"。 573 // "取代節點"肯定不存在左孩子!因為它是一個后繼節點。 574 child = replace.right; 575 parent = parentOf(replace); 576 // 保存"取代節點"的顏色 577 color = colorOf(replace); 578 579 // "被刪除節點"是"它的后繼節點的父節點" 580 if (parent == node) { 581 parent = replace; 582 } else { 583 // child不為空 584 if (child!=null) 585 setParent(child, parent); 586 parent.left = child; 587 588 replace.right = node.right; 589 setParent(node.right, replace); 590 } 591 592 replace.parent = node.parent; 593 replace.color = node.color; 594 replace.left = node.left; 595 node.left.parent = replace; 596 597 if (color == BLACK) 598 removeFixUp(child, parent); 599 600 node = null; 601 return ; 602 } 603 604 if (node.left !=null) { 605 child = node.left; 606 } else { 607 child = node.right; 608 } 609 610 parent = node.parent; 611 // 保存"取代節點"的顏色 612 color = node.color; 613 614 if (child!=null) 615 child.parent = parent; 616 617 // "node節點"不是根節點 618 if (parent!=null) { 619 if (parent.left == node) 620 parent.left = child; 621 else 622 parent.right = child; 623 } else { 624 this.mRoot = child; 625 } 626 627 if (color == BLACK) 628 removeFixUp(child, parent); 629 node = null; 630 } 631 632 /* 633 * 刪除結點(z),並返回被刪除的結點 634 * 635 * 參數說明: 636 * tree 紅黑樹的根結點 637 * z 刪除的結點 638 */ 639 public void remove(T key) { 640 RBTNode<T> node; 641 642 if ((node = search(mRoot, key)) != null) 643 remove(node); 644 } 645 646 /* 647 * 銷毀紅黑樹 648 */ 649 private void destroy(RBTNode<T> tree) { 650 if (tree==null) 651 return ; 652 653 if (tree.left != null) 654 destroy(tree.left); 655 if (tree.right != null) 656 destroy(tree.right); 657 658 tree=null; 659 } 660 661 public void clear() { 662 destroy(mRoot); 663 mRoot = null; 664 } 665 666 /* 667 * 打印"紅黑樹" 668 * 669 * key -- 節點的鍵值 670 * direction -- 0,表示該節點是根節點; 671 * -1,表示該節點是它的父結點的左孩子; 672 * 1,表示該節點是它的父結點的右孩子。 673 */ 674 private void print(RBTNode<T> tree, T key, int direction) { 675 676 if(tree != null) { 677 678 if(direction==0) // tree是根節點 679 System.out.printf("%2d(B) is root\n", tree.key); 680 else // tree是分支節點 681 System.out.printf("%2d(%s) is %2d's %6s child\n", tree.key, isRed(tree)?"R":"B", key, direction==1?"right" : "left"); 682 683 print(tree.left, tree.key, -1); 684 print(tree.right,tree.key, 1); 685 } 686 } 687 688 public void print() { 689 if (mRoot != null) 690 print(mRoot, mRoot.key, 0); 691 } 692 }
紅黑樹的測試文件(RBTreeTest.java)

1 /** 2 * Java 語言: 二叉查找樹 3 * 4 * @author skywang 5 * @date 2013/11/07 6 */ 7 public class RBTreeTest { 8 9 private static final int a[] = {10, 40, 30, 60, 90, 70, 20, 50, 80}; 10 private static final boolean mDebugInsert = false; // "插入"動作的檢測開關(false,關閉;true,打開) 11 private static final boolean mDebugDelete = false; // "刪除"動作的檢測開關(false,關閉;true,打開) 12 13 public static void main(String[] args) { 14 int i, ilen = a.length; 15 RBTree<Integer> tree=new RBTree<Integer>(); 16 17 System.out.printf("== 原始數據: "); 18 for(i=0; i<ilen; i++) 19 System.out.printf("%d ", a[i]); 20 System.out.printf("\n"); 21 22 for(i=0; i<ilen; i++) { 23 tree.insert(a[i]); 24 // 設置mDebugInsert=true,測試"添加函數" 25 if (mDebugInsert) { 26 System.out.printf("== 添加節點: %d\n", a[i]); 27 System.out.printf("== 樹的詳細信息: \n"); 28 tree.print(); 29 System.out.printf("\n"); 30 } 31 } 32 33 System.out.printf("== 前序遍歷: "); 34 tree.preOrder(); 35 36 System.out.printf("\n== 中序遍歷: "); 37 tree.inOrder(); 38 39 System.out.printf("\n== 后序遍歷: "); 40 tree.postOrder(); 41 System.out.printf("\n"); 42 43 System.out.printf("== 最小值: %s\n", tree.minimum()); 44 System.out.printf("== 最大值: %s\n", tree.maximum()); 45 System.out.printf("== 樹的詳細信息: \n"); 46 tree.print(); 47 System.out.printf("\n"); 48 49 // 設置mDebugDelete=true,測試"刪除函數" 50 if (mDebugDelete) { 51 for(i=0; i<ilen; i++) 52 { 53 tree.remove(a[i]); 54 55 System.out.printf("== 刪除節點: %d\n", a[i]); 56 System.out.printf("== 樹的詳細信息: \n"); 57 tree.print(); 58 System.out.printf("\n"); 59 } 60 } 61 62 // 銷毀二叉樹 63 tree.clear(); 64 } 65 }
紅黑樹的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 root 10(B) is 30's left child 20(R) is 10's right child 60(R) is 30's right child 40(B) is 60's left child 50(R) is 40's right child 80(B) is 60's right child 70(R) is 80's left child 90(R) is 80's right child