置頂 2016年04月13日 15:50:25 eson_15 閱讀數:52681 標籤: java數據結構算法紅黑樹 更多html
我的分類: ● 結構算法------【數據結構】java
所屬專欄: 數據結構和算法node
版權聲明:尊重博主原創文章,轉載請註明出處 https://blog.csdn.net/eson_15/article/details/51144079算法
【2018.6.2更新】我新搭建的博客系統上線了(使用SpringBoot搭建的),後面會在新系統中發表博客,這裏也會給出連接,歡迎各位朋友收藏交流哈~
博客地址:http://www.itcodai.com 數據結構
(友情提示,紅-黑樹是基於二叉搜索樹的,若是對二叉搜索樹不瞭解,能夠先看看:二叉搜索樹 ) 數據結構和算法
二叉搜索樹是個很好的數據結構,能夠快速地找到一個給定關鍵字的數據項,而且能夠快速地插入和刪除數據項。可是二叉搜索樹有個很麻煩的問題,若是樹中插入的是隨機數據,則執行效果很好,但若是插入的是有序或者逆序的數據,那麼二叉搜索樹的執行速度就變得很慢。由於當插入數值有序時,二叉樹就是非平衡的了,排在一條線上,其實就變成了一個鏈表……它的快速查找、插入和刪除指定數據項的能力就喪失了。函數
爲了能以較快的時間 O(logN) 來搜索一棵樹,須要保證樹老是平衡的(或者至少大部分是平衡的),這就是說對樹中的每一個節點在它左邊的後代數目和在它右邊的後代數目應該大體相等。紅-黑樹的就是這樣的一棵平衡樹,對一個要插入的數據項,插入例程要檢查會不會破壞樹的特徵,若是破壞了,程序就會進行糾正,根據須要改變樹的結構,從而保持樹的平衡。那麼紅-黑樹都有哪些特徵呢?post
它主要有兩個特徵: 1.節點都有顏色;2.在插入和刪除的過程當中,要遵循保持這些顏色的不一樣排列的規則。首先第一個特徵很好解決,在節點類中店家一個數據字段,例如 boolean 型變量,以此來表示節點的顏色信息。第二個特徵比較複雜,紅-黑樹有它的幾個規則,若是遵循這些規則,那麼樹就是平衡的。紅-黑樹的主要規則以下:性能
1.每一個節點不是紅色就是黑色的;測試
2.根節點老是黑色的;
3.若是節點是紅色的,則它的子節點必須是黑色的(反之不必定);
4.從根節點到葉節點或空子節點的每條路徑,必須包含相同數目的黑色節點(即相同的黑色高度)。
在紅-黑樹中插入的節點都是紅色的,這不是偶然的,由於插入一個紅色節點比插入一個黑色節點違背紅-黑規則的可能性更小。緣由是:插入黑色節點總會改變黑色高度(違背規則4),可是插入紅色節點只有一半的機會會違背規則3。另外違背規則3比違背規則4要更容易修正。當插入一個新的節點時,可能會破壞這種平衡性,那麼紅-黑樹是如何修正的呢?
紅-黑樹主要經過三種方式對平衡進行修正,改變節點顏色、左旋和右旋。這看起來有點抽象,咱們分別來介紹它們。
2.1 變色
改變節點顏色比較容易理解,由於它違背了規則3。假設如今有個節點E,而後插入節點A和節點S,節點A在左子節點,S在右子節點,目前是平衡的。若是此時再插一個節點,那麼就出現了不平衡了,由於紅色節點的子節點必須爲黑色,可是新插的節點是紅色的。因此這時候就必須改變節點顏色了。因此咱們將根的兩個子節點從紅色變爲黑色(至於爲何都要變,下面插入的時候會詳細介紹),將父節點會從黑色變成紅色。能夠用以下示意圖表示一下:
2.2 左旋
一般左旋操做用於將一個向右傾斜的紅色連接旋轉爲向左連接。示意圖以下:
左旋有個很萌萌噠的動態示意圖,能夠方便理解:
.3 右旋
右旋可左旋恰好相反,這裏再也不贅述,直接看示意圖:
固然咯,右旋也有個萌萌的動態圖:
這裏主要介紹了紅-黑樹對平衡的三種修正方式,你們有個感性的認識,那麼何時該修正呢?何時該用哪一種修正呢?這將是接下來咱們要探討的問題。
紅-黑樹的基本操做是添加、刪除和旋轉。對紅-黑樹進行添加或刪除後,可能會破壞其平衡性,會用到哪一種旋轉方式去修正呢?咱們首先對紅-黑樹的節點作一介紹,而後分別對左旋和右旋的具體實現作一分析,最後咱們探討下紅-黑樹的具體操做。
3.1 紅-黑樹的節點
紅-黑樹是對二叉搜索樹的改進,因此其節點與二叉搜索樹是差很少的,只不過在它基礎上增長了一個boolean型變量來表示節點的顏色,具體看RBNode類:
public class RBNode<T extends Comparable<T>>{
boolean color; //顏色
T key; //關鍵字(鍵值)
RBNode<T> left; //左子節點
RBNode<T> right; //右子節點
RBNode<T> parent; //父節點
public RBNode(T key, boolean color, RBNode<T> parent, RBNode<T> left, RBNode<T> right) {
this.key = key;
this.color = color;
this.parent = parent;
this.left = left;
this.right = right;
}
public T getKey() {
return key;
}
public String toString() {
return "" + key + (this.color == RED? "R" : "B");
}
}
3.2 左旋的具體實現
上面對左旋的概念已經有了感性的認識了,這裏就再也不贅述了,咱們從下面的代碼中結合上面的示意圖,探討一下左旋的具體實現:
/*************對紅黑樹節點x進行左旋操做 ******************/ /* * 左旋示意圖:對節點x進行左旋 * p p * / / * x y * / \ / \ * lx y -----> x ry * / \ / \ * ly ry lx ly * 左旋作了三件事: * 1. 將y的左子節點賦給x的右子節點,並將x賦給y左子節點的父節點(y左子節點非空時) * 2. 將x的父節點p(非空時)賦給y的父節點,同時更新p的子節點爲y(左或右) * 3. 將y的左子節點設爲x,將x的父節點設爲y */ private void leftRotate(RBNode<T> x) { //1. 將y的左子節點賦給x的右子節點,並將x賦給y左子節點的父節點(y左子節點非空時) RBNode<T> y = x.right; x.right = y.left; if(y.left != null) y.left.parent = x; //2. 將x的父節點p(非空時)賦給y的父節點,同時更新p的子節點爲y(左或右) y.parent = x.parent; if(x.parent == null) { this.root = y; //若是x的父節點爲空,則將y設爲父節點 } else { if(x == x.parent.left) //若是x是左子節點 x.parent.left = y; //則也將y設爲左子節點 else x.parent.right = y;//不然將y設爲右子節點 } //3. 將y的左子節點設爲x,將x的父節點設爲y y.left = x; x.parent = y; }
3.3 右旋具體實現
上面對右旋的概念已經有了感性的認識了,這裏也再也不贅述了,咱們從下面的代碼中結合上面的示意圖,探討一下右旋的具體實現:
/*************對紅黑樹節點y進行右旋操做 ******************/
/*
* 左旋示意圖:對節點y進行右旋
* p p
* / /
* y x
* / \ / \
* x ry -----> lx y
* / \ / \
* lx rx rx ry
* 右旋作了三件事:
* 1. 將x的右子節點賦給y的左子節點,並將y賦給x右子節點的父節點(x右子節點非空時)
* 2. 將y的父節點p(非空時)賦給x的父節點,同時更新p的子節點爲x(左或右)
* 3. 將x的右子節點設爲y,將y的父節點設爲x
*/
private void rightRotate(RBNode<T> y) {
//1. 將y的左子節點賦給x的右子節點,並將x賦給y左子節點的父節點(y左子節點非空時)
RBNode<T> x = y.left;
y.left = x.right;
if(x.right != null)
x.right.parent = y;
//2. 將x的父節點p(非空時)賦給y的父節點,同時更新p的子節點爲y(左或右)
x.parent = y.parent;
if(y.parent == null) {
this.root = x; //若是x的父節點爲空,則將y設爲父節點
} else {
if(y == y.parent.right) //若是x是左子節點
y.parent.right = x; //則也將y設爲左子節點
else
y.parent.left = x;//不然將y設爲右子節點
}
//3. 將y的左子節點設爲x,將x的父節點設爲y
x.right = y;
y.parent = x;
}
3.4 插入操做
分析完了紅-黑樹中主要的旋轉操做,接下來咱們開始分析常見的插入、刪除等操做了。這裏先分析插入操做。 因爲紅-黑樹是二叉搜索樹的改進,因此插入操做的前半工做時相同的,即先找到待插入的位置,再將節點插入,先來看看插入的前半段代碼:
/*********************** 向紅黑樹中插入節點 **********************/
public void insert(T key) {
RBNode<T> node = new RBNode<T>(key, RED, null, null, null);
if(node != null)
insert(node);
}
//將節點插入到紅黑樹中,這個過程與二叉搜索樹是同樣的
private void insert(RBNode<T> node) {
RBNode<T> current = null; //表示最後node的父節點
RBNode<T> x = this.root; //用來向下搜索用的
//1. 找到插入的位置
while(x != null) {
current = x;
int cmp = node.key.compareTo(x.key);
if(cmp < 0)
x = x.left;
else
x = x.right;
}
node.parent = current; //找到了位置,將當前current做爲node的父節點
//2. 接下來判斷node是插在左子節點仍是右子節點
if(current != null) {
int cmp = node.key.compareTo(current.key);
if(cmp < 0)
current.left = node;
else
current.right = node;
} else {
this.root = node;
}
//3. 將它從新修整爲一顆紅黑樹
insertFixUp(node);
}
這與二叉搜索樹中實現的思路如出一轍,這裏再也不贅述,主要看看方法裏面最後一步insertFixUp操做。由於插入後可能會致使樹的不平衡,insertFixUp方法裏主要是分狀況討論,分析什麼時候變色,什麼時候左旋,什麼時候右旋。咱們先從理論上分析具體的狀況,而後再看insertFixUp方法的具體實現。
若是是第一次插入,因爲原樹爲空,因此只會違反紅-黑樹的規則2,因此只要把根節點塗黑便可;若是插入節點的父節點是黑色的,那不會違背紅-黑樹的規則,什麼也不須要作;可是遇到以下三種狀況時,咱們就要開始變色和旋轉了:
1.插入節點的父節點和其叔叔節點(祖父節點的另外一個子節點)均爲紅色的;
2.插入節點的父節點是紅色,叔叔節點是黑色,且插入節點是其父節點的右子節點;
3.插入節點的父節點是紅色,叔叔節點是黑色,且插入節點是其父節點的左子節點。
下面咱們先挨個分析這三種狀況都須要如何操做,而後再給出實現代碼。
對於狀況1:插入節點的父節點和其叔叔節點(祖父節點的另外一個子節點)均爲紅色的。此時,確定存在祖父節點,可是不知道父節點是其左子節點仍是右子節點,可是因爲對稱性,咱們只要討論出一邊的狀況,另外一種狀況天然也與之對應。這裏考慮父節點是祖父節點的左子節點的狀況,以下左圖所示:
對於這種狀況,咱們要作的操做有:將當前節點(4)的父節點(5)和叔叔節點(8)塗黑,將祖父節點(7)塗紅,變成上右圖所示的狀況。再將當前節點指向其祖父節點,再次重新的當前節點開始算法(具體等下看下面的程序)。這樣上右圖就變成了狀況2了。
對於狀況2:插入節點的父節點是紅色,叔叔節點是黑色,且插入節點是其父節點的右子節點。咱們要作的操做有:將當前節點(7)的父節點(2)做爲新的節點,以新的當前節點爲支點作左旋操做。完成後如左下圖所示,這樣左下圖就變成狀況3了。
對於狀況3:插入節點的父節點是紅色,叔叔節點是黑色,且插入節點是其父節點的左子節點。咱們要作的操做有:將當前節點的父節點(7)塗黑,將祖父節點(11)塗紅,在祖父節點爲支點作右旋操做。最後把根節點塗黑,整個紅-黑樹從新恢復了平衡,如右上圖所示。至此,插入操做完成!
咱們能夠看出,若是是從狀況1開始發生的,必然會走完狀況2和3,也就是說這是一整個流程,固然咯,實際中可能不必定會從狀況1發生,若是從狀況2開始發生,那再走個狀況3便可完成調整,若是直接只要調整狀況3,那麼前兩種狀況均不須要調整了。故變色和旋轉之間的前後關係能夠表示爲:變色->左旋->右旋。
至此,咱們完成了所有的插入操做。下面咱們看看insertFixUp方法中的具體實現(能夠結合上面的分析圖,更加利與理解):
private void insertFixUp(RBNode<T> node) {
RBNode<T> parent, gparent; //定義父節點和祖父節點
//須要修整的條件:父節點存在,且父節點的顏色是紅色
while(((parent = parentOf(node)) != null) && isRed(parent)) {
gparent = parentOf(parent);//得到祖父節點
//若父節點是祖父節點的左子節點,下面else與其相反
if(parent == gparent.left) {
RBNode<T> uncle = gparent.right; //得到叔叔節點
//case1: 叔叔節點也是紅色
if(uncle != null && isRed(uncle)) {
setBlack(parent); //把父節點和叔叔節點塗黑
setBlack(uncle);
setRed(gparent); //把祖父節點塗紅
node = gparent; //將位置放到祖父節點處
continue; //繼續while,從新判斷
}
//case2: 叔叔節點是黑色,且當前節點是右子節點
if(node == parent.right) {
leftRotate(parent); //從父節點處左旋
RBNode<T> tmp = parent; //而後將父節點和本身調換一下,爲下面右旋作準備
parent = node;
node = tmp;
}
//case3: 叔叔節點是黑色,且當前節點是左子節點
setBlack(parent);
setRed(gparent);
rightRotate(gparent);
} else { //若父節點是祖父節點的右子節點,與上面的徹底相反,本質同樣的
RBNode<T> uncle = gparent.left;
//case1: 叔叔節點也是紅色
if(uncle != null & isRed(uncle)) {
setBlack(parent);
setBlack(uncle);
setRed(gparent);
node = gparent;
continue;
}
//case2: 叔叔節點是黑色的,且當前節點是左子節點
if(node == parent.left) {
rightRotate(parent);
RBNode<T> tmp = parent;
parent = node;
node = tmp;
}
//case3: 叔叔節點是黑色的,且當前節點是右子節點
setBlack(parent);
setRed(gparent);
leftRotate(gparent);
}
}
//將根節點設置爲黑色
setBlack(this.root);
}
終於分析完了插入和刪除操做的全部東西。另外,紅-黑樹還有一些其餘操做,好比:查找特定值、遍歷、返回最值、銷燬樹等操做我將放到源碼中給你們呈現出來,詳見下面紅-黑樹的完整代碼。
package tree; /** * @description implementation of Red-Black Tree by Java * @author eson_15 * @param <T> * @date 2016-4-18 19:27:28 */ public class RBTree<T extends Comparable<T>> { private RBNode<T> root; //根節點 private static final boolean RED = false; //定義紅黑樹標誌 private static final boolean BLACK = true; //內部類:節點類 public class RBNode<T extends Comparable<T>>{ boolean color; //顏色 T key; //關鍵字(鍵值) RBNode<T> left; //左子節點 RBNode<T> right; //右子節點 RBNode<T> parent; //父節點 public RBNode(T key, boolean color, RBNode<T> parent, RBNode<T> left, RBNode<T> right) { this.key = key; this.color = color; this.parent = parent; this.left = left; this.right = right; } public T getKey() { return key; } public String toString() { return "" + key + (this.color == RED? "R" : "B"); } } public RBTree() { root = null; } public RBNode<T> parentOf(RBNode<T> node) { //得到父節點 return node != null? node.parent : null; } public void setParent(RBNode<T> node, RBNode<T> parent) { //設置父節點 if(node != null) node.parent = parent; } public boolean colorOf(RBNode<T> node) { //得到節點的顏色 return node != null? node.color : BLACK; } public boolean isRed(RBNode<T> node) { //判斷節點的顏色 return (node != null)&&(node.color == RED)? true : false; } public boolean isBlack(RBNode<T> node) { return !isRed(node); } public void setRed(RBNode<T> node) { //設置節點的顏色 if(node != null) node.color = RED; } public void setBlack(RBNode<T> node) { if(node != null) { node.color = BLACK; } } public void setColor(RBNode<T> node, boolean color) { if(node != null) node.color = color; } /***************** 前序遍歷紅黑樹 *********************/ public void preOrder() { preOrder(root); } private void preOrder(RBNode<T> tree) { if(tree != null) { System.out.print(tree.key + " "); preOrder(tree.left); preOrder(tree.right); } } /***************** 中序遍歷紅黑樹 *********************/ public void inOrder() { inOrder(root); } private void inOrder(RBNode<T> tree) { if(tree != null) { preOrder(tree.left); System.out.print(tree.key + " "); preOrder(tree.right); } } /***************** 後序遍歷紅黑樹 *********************/ public void postOrder() { postOrder(root); } private void postOrder(RBNode<T> tree) { if(tree != null) { preOrder(tree.left); preOrder(tree.right); System.out.print(tree.key + " "); } } /**************** 查找紅黑樹中鍵值爲key的節點 ***************/ public RBNode<T> search(T key) { return search(root, key); // return search2(root, key); //使用遞歸的方法,本質同樣的 } private RBNode<T> search(RBNode<T> x, T key) { while(x != null) { int cmp = key.compareTo(x.key); if(cmp < 0) x = x.left; else if(cmp > 0) x = x.right; else return x; } return x; } //使用遞歸 private RBNode<T> search2(RBNode<T> x, T key) { if(x == null) return x; int cmp = key.compareTo(x.key); if(cmp < 0) return search2(x.left, key); else if(cmp > 0) return search2(x.right, key); else return x; } /**************** 查找最小節點的值 **********************/ public T minValue() { RBNode<T> node = minNode(root); if(node != null) return node.key; return null; } private RBNode<T> minNode(RBNode<T> tree) { if(tree == null) return null; while(tree.left != null) { tree = tree.left; } return tree; } /******************** 查找最大節點的值 *******************/ public T maxValue() { RBNode<T> node = maxNode(root); if(node != null) return node.key; return null; } private RBNode<T> maxNode(RBNode<T> tree) { if(tree == null) return null; while(tree.right != null) tree = tree.right; return tree; } /********* 查找節點x的後繼節點,即大於節點x的最小節點 ***********/ public RBNode<T> successor(RBNode<T> x) { //若是x有右子節點,那麼後繼節點爲「以右子節點爲根的子樹的最小節點」 if(x.right != null) return minNode(x.right); //若是x沒有右子節點,會出現如下兩種狀況: //1. x是其父節點的左子節點,則x的後繼節點爲它的父節點 //2. x是其父節點的右子節點,則先查找x的父節點p,而後對p再次進行這兩個條件的判斷 RBNode<T> p = x.parent; while((p != null) && (x == p.right)) { //對應狀況2 x = p; p = x.parent; } return p; //對應狀況1 } /********* 查找節點x的前驅節點,即小於節點x的最大節點 ************/ public RBNode<T> predecessor(RBNode<T> x) { //若是x有左子節點,那麼前驅結點爲「左子節點爲根的子樹的最大節點」 if(x.left != null) return maxNode(x.left); //若是x沒有左子節點,會出現如下兩種狀況: //1. x是其父節點的右子節點,則x的前驅節點是它的父節點 //2. x是其父節點的左子節點,則先查找x的父節點p,而後對p再次進行這兩個條件的判斷 RBNode<T> p = x.parent; while((p != null) && (x == p.left)) { //對應狀況2 x = p; p = x.parent; } return p; //對應狀況1 } /*************對紅黑樹節點x進行左旋操做 ******************/ /* * 左旋示意圖:對節點x進行左旋 * p p * / / * x y * / \ / \ * lx y -----> x ry * / \ / \ * ly ry lx ly * 左旋作了三件事: * 1. 將y的左子節點賦給x的右子節點,並將x賦給y左子節點的父節點(y左子節點非空時) * 2. 將x的父節點p(非空時)賦給y的父節點,同時更新p的子節點爲y(左或右) * 3. 將y的左子節點設爲x,將x的父節點設爲y */ private void leftRotate(RBNode<T> x) { //1. 將y的左子節點賦給x的右子節點,並將x賦給y左子節點的父節點(y左子節點非空時) RBNode<T> y = x.right; x.right = y.left; if(y.left != null) y.left.parent = x; //2. 將x的父節點p(非空時)賦給y的父節點,同時更新p的子節點爲y(左或右) y.parent = x.parent; if(x.parent == null) { this.root = y; //若是x的父節點爲空,則將y設爲父節點 } else { if(x == x.parent.left) //若是x是左子節點 x.parent.left = y; //則也將y設爲左子節點 else x.parent.right = y;//不然將y設爲右子節點 } //3. 將y的左子節點設爲x,將x的父節點設爲y y.left = x; x.parent = y; } /*************對紅黑樹節點y進行右旋操做 ******************/ /* * 左旋示意圖:對節點y進行右旋 * p p * / / * y x * / \ / \ * x ry -----> lx y * / \ / \ * lx rx rx ry * 右旋作了三件事: * 1. 將x的右子節點賦給y的左子節點,並將y賦給x右子節點的父節點(x右子節點非空時) * 2. 將y的父節點p(非空時)賦給x的父節點,同時更新p的子節點爲x(左或右) * 3. 將x的右子節點設爲y,將y的父節點設爲x */ private void rightRotate(RBNode<T> y) { //1. 將y的左子節點賦給x的右子節點,並將x賦給y左子節點的父節點(y左子節點非空時) RBNode<T> x = y.left; y.left = x.right; if(x.right != null) x.right.parent = y; //2. 將x的父節點p(非空時)賦給y的父節點,同時更新p的子節點爲y(左或右) x.parent = y.parent; if(y.parent == null) { this.root = x; //若是x的父節點爲空,則將y設爲父節點 } else { if(y == y.parent.right) //若是x是左子節點 y.parent.right = x; //則也將y設爲左子節點 else y.parent.left = x;//不然將y設爲右子節點 } //3. 將y的左子節點設爲x,將x的父節點設爲y x.right = y; y.parent = x; } /*********************** 向紅黑樹中插入節點 **********************/ public void insert(T key) { RBNode<T> node = new RBNode<T>(key, RED, null, null, null); if(node != null) insert(node); } //將節點插入到紅黑樹中,這個過程與二叉搜索樹是同樣的 private void insert(RBNode<T> node) { RBNode<T> current = null; //表示最後node的父節點 RBNode<T> x = this.root; //用來向下搜索用的 //1. 找到插入的位置 while(x != null) { current = x; int cmp = node.key.compareTo(x.key); if(cmp < 0) x = x.left; else x = x.right; } node.parent = current; //找到了位置,將當前current做爲node的父節點 //2. 接下來判斷node是插在左子節點仍是右子節點 if(current != null) { int cmp = node.key.compareTo(current.key); if(cmp < 0) current.left = node; else current.right = node; } else { this.root = node; } //3. 將它從新修整爲一顆紅黑樹 insertFixUp(node); } private void insertFixUp(RBNode<T> node) { RBNode<T> parent, gparent; //定義父節點和祖父節點 //須要修整的條件:父節點存在,且父節點的顏色是紅色 while(((parent = parentOf(node)) != null) && isRed(parent)) { gparent = parentOf(parent);//得到祖父節點 //若父節點是祖父節點的左子節點,下面else與其相反 if(parent == gparent.left) { RBNode<T> uncle = gparent.right; //得到叔叔節點 //case1: 叔叔節點也是紅色 if(uncle != null && isRed(uncle)) { setBlack(parent); //把父節點和叔叔節點塗黑 setBlack(uncle); setRed(gparent); //把祖父節點塗紅 node = gparent; //將位置放到祖父節點處 continue; //繼續while,從新判斷 } //case2: 叔叔節點是黑色,且當前節點是右子節點 if(node == parent.right) { leftRotate(parent); //從父節點處左旋 RBNode<T> tmp = parent; //而後將父節點和本身調換一下,爲下面右旋作準備 parent = node; node = tmp; } //case3: 叔叔節點是黑色,且當前節點是左子節點 setBlack(parent); setRed(gparent); rightRotate(gparent); } else { //若父節點是祖父節點的右子節點,與上面的徹底相反,本質同樣的 RBNode<T> uncle = gparent.left; //case1: 叔叔節點也是紅色 if(uncle != null & isRed(uncle)) { setBlack(parent); setBlack(uncle); setRed(gparent); node = gparent; continue; } //case2: 叔叔節點是黑色的,且當前節點是左子節點 if(node == parent.left) { rightRotate(parent); RBNode<T> tmp = parent; parent = node; node = tmp; } //case3: 叔叔節點是黑色的,且當前節點是右子節點 setBlack(parent); setRed(gparent); leftRotate(gparent); } } //將根節點設置爲黑色 setBlack(this.root); } /*********************** 刪除紅黑樹中的節點 **********************/ public void remove(T key) { RBNode<T> node; if((node = search(root, key)) != null) remove(node); } private void remove(RBNode<T> node) { RBNode<T> child, parent; boolean color; //1. 被刪除的節點「左右子節點都不爲空」的狀況 if((node.left != null) && (node.right != null)) { //先找到被刪除節點的後繼節點,用它來取代被刪除節點的位置 RBNode<T> replace = node; // 1). 獲取後繼節點 replace = replace.right; while(replace.left != null) replace = replace.left; // 2). 處理「後繼節點」和「被刪除節點的父節點」之間的關係 if(parentOf(node) != null) { //要刪除的節點不是根節點 if(node == parentOf(node).left) parentOf(node).left = replace; else parentOf(node).right = replace; } else { //不然 this.root = replace; } // 3). 處理「後繼節點的子節點」和「被刪除節點的子節點」之間的關係 child = replace.right; //後繼節點確定不存在左子節點! parent = parentOf(replace); color = colorOf(replace);//保存後繼節點的顏色 if(parent == node) { //後繼節點是被刪除節點的子節點 parent = replace; } else { //不然 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) { //4. 若是移走的後繼節點顏色是黑色,從新修整紅黑樹 removeFixUp(child, parent);//將後繼節點的child和parent傳進去 } node = null; return; } } //node表示待修正的節點,即後繼節點的子節點(由於後繼節點被挪到刪除節點的位置去了) private void removeFixUp(RBNode<T> node, RBNode<T> parent) { RBNode<T> other; while((node == null || isBlack(node)) && (node != this.root)) { if(parent.left == node) { //node是左子節點,下面else與這裏的恰好相反 other = parent.right; //node的兄弟節點 if(isRed(other)) { //case1: node的兄弟節點other是紅色的 setBlack(other); setRed(parent); leftRotate(parent); other = parent.right; } //case2: node的兄弟節點other是黑色的,且other的兩個子節點也都是黑色的 if((other.left == null || isBlack(other.left)) && (other.right == null || isBlack(other.right))) { setRed(other); node = parent; parent = parentOf(node); } else { //case3: node的兄弟節點other是黑色的,且other的左子節點是紅色,右子節點是黑色 if(other.right == null || isBlack(other.right)) { setBlack(other.left); setRed(other); rightRotate(other); other = parent.right; } //case4: node的兄弟節點other是黑色的,且other的右子節點是紅色,左子節點任意顏色 setColor(other, colorOf(parent)); setBlack(parent); setBlack(other.right); leftRotate(parent); node = this.root; break; } } else { //與上面的對稱 other = parent.left; if (isRed(other)) { // Case 1: node的兄弟other是紅色的 setBlack(other); setRed(parent); rightRotate(parent); other = parent.left; } if ((other.left==null || isBlack(other.left)) && (other.right==null || isBlack(other.right))) { // Case 2: node的兄弟other是黑色,且other的倆個子節點都是黑色的 setRed(other); node = parent; parent = parentOf(node); } else { if (other.left==null || isBlack(other.left)) { // Case 3: node的兄弟other是黑色的,而且other的左子節點是紅色,右子節點爲黑色。 setBlack(other.right); setRed(other); leftRotate(other); other = parent.left; } // Case 4: node的兄弟other是黑色的;而且other的左子節點是紅色的,右子節點任意顏色 setColor(other, colorOf(parent)); setBlack(parent); setBlack(other.left); rightRotate(parent); node = this.root; break; } } } if (node!=null) setBlack(node); } /****************** 銷燬紅黑樹 *********************/ public void clear() { destroy(root); root = null; } private void destroy(RBNode<T> tree) { if(tree == null) return; if(tree.left != null) destroy(tree.left); if(tree.right != null) destroy(tree.right); tree = null; } /******************* 打印紅黑樹 *********************/ public void print() { if(root != null) { print(root, root.key, 0); } } /* * key---節點的鍵值 * direction--- 0:表示該節點是根節點 * 1:表示該節點是它的父節點的左子節點 * 2:表示該節點是它的父節點的右子節點 */ private void print(RBNode<T> tree, T key, int direction) { if(tree != null) { if(0 == direction) System.out.printf("%2d(B) is root\n", tree.key); else System.out.printf("%2d(%s) is %2d's %6s child\n", tree.key, isRed(tree)?"R":"b", key, direction == 1?"right":"left"); print(tree.left, tree.key, -1); print(tree.right, tree.key, 1); } } }
下面附上測試程序吧:
package test; import tree.RBTree; public class RBTreeTest { private static final int a[] = {10, 40, 30, 60, 90, 70, 20, 50, 80}; private static final boolean mDebugInsert = true; // "插入"動做的檢測開關(false,關閉;true,打開) private static final boolean mDebugDelete = true; // "刪除"動做的檢測開關(false,關閉;true,打開) public static void main(String[] args) { int i, ilen = a.length; RBTree<Integer> tree = new RBTree<Integer>(); System.out.printf("== 原始數據: "); for(i=0; i<ilen; i++) System.out.printf("%d ", a[i]); System.out.printf("\n"); for(i=0; i<ilen; i++) { tree.insert(a[i]); // 設置mDebugInsert=true,測試"添加函數" if (mDebugInsert) { System.out.printf("== 添加節點: %d\n", a[i]); System.out.printf("== 樹的詳細信息: \n"); tree.print(); System.out.printf("\n"); } } System.out.printf("== 前序遍歷: "); tree.preOrder(); System.out.printf("\n== 中序遍歷: "); tree.inOrder(); System.out.printf("\n== 後序遍歷: "); tree.postOrder(); System.out.printf("\n"); System.out.printf("== 最小值: %s\n", tree.minValue()); System.out.printf("== 最大值: %s\n", tree.maxValue()); System.out.printf("== 樹的詳細信息: \n"); tree.print(); System.out.printf("\n"); // 設置mDebugDelete=true,測試"刪除函數" if (mDebugDelete) { for(i=0; i<ilen; i++) { tree.remove(a[i]); System.out.printf("== 刪除節點: %d\n", a[i]); System.out.printf("== 樹的詳細信息: \n"); tree.print(); System.out.printf("\n"); } } } }
前面也說了,當數據以升序或降序插入時,二叉搜索樹的性能就會降低到最低,可是紅-黑樹的自我修復功能保證了即便在最壞的狀況下,也能保證時間複雜度在O(logN)的級別上。
至此,紅-黑樹的全部內容基本上討論完了,若有錯誤之處,歡迎留言指正~
【正在看本人博客的這位童鞋,我看你氣度不凡,談吐間隱隱有王者之氣,往後必有一番做爲!下面有個「頂」字,你就順手把它點了吧~相的準,我分文不收;相不許,你也好回來找我~嘎嘎嘎】
_____________________________________________________________________________________________________________________________________________________
-----樂於分享,共同進步!
-----本文動態圖出自:http://www.cnblogs.com/yangecnu/p/Introduce-Red-Black-Tree.html
-----本文部分參考於博客專家July的這篇文章:http://blog.csdn.net/v_july_v/article/details/6105630
-----更多文章請看:http://blog.csdn.net/eson_15