【數據結構和算法05】 紅-黑樹(轉發)

2019獨角獸企業重金招聘Python工程師標準>>> hot3.png

【數據結構和算法05】 紅-黑樹(看完包懂~)

置頂?2016年04月13日 15:50:25?eson_15?閱讀數:52681?標簽:?java數據結構算法紅黑樹?更多

個人分類:?● 結構算法------【數據結構】

所屬專欄:?數據結構和算法

?版權聲明:尊重博主原創文章,轉載請注明出處 https://blog.csdn.net/eson_15/article/details/51144079

【2018.6.2更新】我新搭建的博客系統上線了(使用SpringBoot搭建的),后面會在新系統中發表博客,這里也會給出鏈接,歡迎各位朋友收藏交流哈~?
博客地址:http://www.itcodai.com? ? ? ?

?(友情提示,紅-黑樹是基于二叉搜索樹的,如果對二叉搜索樹不了解,可以先看看:二叉搜索樹?)? ? ? ?

二叉搜索樹是個很好的數據結構,可以快速地找到一個給定關鍵字的數據項,并且可以快速地插入和刪除數據項。但是二叉搜索樹有個很麻煩的問題,如果樹中插入的是隨機數據,則執行效果很好,但如果插入的是有序或者逆序的數據,那么二叉搜索樹的執行速度就變得很慢。因為當插入數值有序時,二叉樹就是非平衡的了,排在一條線上,其實就變成了一個鏈表……它的快速查找、插入和刪除指定數據項的能力就喪失了。

為了能以較快的時間 O(logN) 來搜索一棵樹,需要保證樹總是平衡的(或者至少大部分是平衡的),這就是說對樹中的每個節點在它左邊的后代數目和在它右邊的后代數目應該大致相等。紅-黑樹的就是這樣的一棵平衡樹,對一個要插入的數據項,插入例程要檢查會不會破壞樹的特征,如果破壞了,程序就會進行糾正,根據需要改變樹的結構,從而保持樹的平衡。那么紅-黑樹都有哪些特征呢?

1. 紅-黑樹的特征

它主要有兩個特征: 1.節點都有顏色;2.在插入和刪除的過程中,要遵循保持這些顏色的不同排列的規則。首先第一個特征很好解決,在節點類中店家一個數據字段,例如 boolean 型變量,以此來表示節點的顏色信息。第二個特征比較復雜,紅-黑樹有它的幾個規則,如果遵循這些規則,那么樹就是平衡的。紅-黑樹的主要規則如下:

  • 1.每個節點不是紅色就是黑色的;

  • 2.根節點總是黑色的;

  • 3.如果節點是紅色的,則它的子節點必須是黑色的(反之不一定);

  • 4.從根節點到葉節點或空子節點的每條路徑,必須包含相同數目的黑色節點(即相同的黑色高度)。

在紅-黑樹中插入的節點都是紅色的,這不是偶然的,因為插入一個紅色節點比插入一個黑色節點違背紅-黑規則的可能性更小。原因是:插入黑色節點總會改變黑色高度(違背規則4),但是插入紅色節點只有一半的機會會違背規則3。另外違背規則3比違背規則4要更容易修正。當插入一個新的節點時,可能會破壞這種平衡性,那么紅-黑樹是如何修正的呢?

2. 平衡性的修正

紅-黑樹主要通過三種方式對平衡進行修正,改變節點顏色、左旋和右旋。這看起來有點抽象,我們分別來介紹它們。

2.1 變色

改變節點顏色比較容易理解,因為它違背了規則3。假設現在有個節點E,然后插入節點A和節點S,節點A在左子節點,S在右子節點,目前是平衡的。如果此時再插一個節點,那么就出現了不平衡了,因為紅色節點的子節點必須為黑色,但是新插的節點是紅色的。所以這時候就必須改變節點顏色了。所以我們將根的兩個子節點從紅色變為黑色(至于為什么都要變,下面插入的時候會詳細介紹),將父節點會從黑色變成紅色。可以用如下示意圖表示一下:

2.2 左旋

通常左旋操作用于將一個向右傾斜的紅色鏈接旋轉為向左鏈接。示意圖如下:

左旋有個很萌萌噠的動態示意圖,可以方便理解:

.3 右旋

右旋可左旋剛好相反,這里不再贅述,直接看示意圖:

當然咯,右旋也有個萌萌的動態圖:

這里主要介紹了紅-黑樹對平衡的三種修正方式,大家有個感性的認識,那么什么時候該修正呢?什么時候該用哪種修正呢?這將是接下來我們要探討的問題。

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設為左子節點elsex.parent.right = y;//否則將y設為右子節點}//3. 將y的左子節點設為x,將x的父節點設為yy.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設為左子節點elsey.parent.left = x;//否則將y設為右子節點}//3. 將y的左子節點設為x,將x的父節點設為yx.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;elsex = 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;elsecurrent.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); ?
} ?

?

4.完整源碼

??????? 終于分析完了插入和刪除操作的所有東西。另外,紅-黑樹還有一些其他操作,比如:查找特定值、遍歷、返回最值、銷毀樹等操作我將放到源碼中給大家呈現出來,詳見下面紅-黑樹的完整代碼。

?

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);elsereturn 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)) { //對應情況2x = 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)) { //對應情況2x = 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設為左子節點elsex.parent.right = y;//否則將y設為右子節點}//3. 將y的左子節點設為x,將x的父節點設為yy.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設為左子節點elsey.parent.left = x;//否則將y設為右子節點}//3. 將y的左子節點設為x,將x的父節點設為yx.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;elsex = 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;elsecurrent.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;elseparentOf(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);elseSystem.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");}}}}

?

5.紅-黑樹的復雜度

????????前面也說了,當數據以升序或降序插入時,二叉搜索樹的性能就會下降到最低,但是紅-黑樹的自我修復功能保證了即使在最壞的情況下,也能保證時間復雜度在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

轉載于:https://my.oschina.net/u/3425573/blog/3008174

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/537007.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/537007.shtml
英文地址,請注明出處:http://en.pswp.cn/news/537007.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

數據結構與算法——二叉樹、堆、優先隊列

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 七、樹 7.1 樹 7.1.1 樹的定義 樹是我們計算機中非常重要的一種數據結構&#xff0c;同時使用樹這種數據結構&#xff0c;可以描述現實生活…

android組建之間通信_Android組件化(三)組件之間的通信

介紹在組件化開發的時候&#xff0c;組件之間是相互獨立的沒有依賴關系&#xff0c;我們不能在使用顯示調用來跳轉頁面了&#xff0c;因為我們組件化的目的之一就是解決模塊間的強依賴問題&#xff0c;假如現在要從A業務組件跳轉到業務B組件&#xff0c;并且要攜帶參數跳轉&…

繼牛津大學后,加大伯克利分校等多家美國高校終止與華為合作

文&#xff0f;AI財經社 唐煜編&#xff0f;嵇國華據 Nature News 報道&#xff0c;在美國相關部門的壓力之下&#xff0c;加州大學伯克利分校&#xff08;UC Berkeley&#xff09;近日宣布不再與華為簽署新的研究合作&#xff1b;德州大學奧斯丁分校也正在審查自身與華為的關系…

為什么varchar字段長度最好是2的n次方-1

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 計算機是二進制計算的&#xff0c;1 bytes 8 bit ,一個字節最多可以代表的數據長度是2的8次方 11111111 在計算機中也就是-128到127。 而var…

運籌學狀態轉移方程例子_強化學習第4期:H-J-B方程

在上一篇文章中&#xff0c;我們介紹了一種最簡單的MDP——s與a都是有限的MDP的求解方法。其中&#xff0c;我們用到了動態規劃的思想&#xff0c;并且推出了“策略迭代”、“值迭代”這樣的方法。今天&#xff0c;我們要來講更加一般的最優控制問題——t、a與s都是連續的問題。…

Python之celery的簡介與使用

celery的簡介 celery是一個基于分布式消息傳輸的異步任務隊列&#xff0c;它專注于實時處理&#xff0c;同時也支持任務調度。它的執行單元為任務&#xff08;task&#xff09;&#xff0c;利用多線程&#xff0c;如Eventlet&#xff0c;gevent等&#xff0c;它們能被并發地執行…

不使用比較運算符如何比較兩個數的大小

分享一波:程序員賺外快-必看的巔峰干貨 前言 今天在水群的過程中看到有位群員談論到這個話題&#xff0c;是他找工作過程中某家公司的面試題&#xff08;到底是哪家公司才會出這種沒營養的題目刁難別人&#xff09;&#xff0c;有點興趣&#xff0c;就開始寫了。 開搞 想了一…

java占位符填充_Java使用freemark生成word

1、制作模板先用office word做一個模板word文檔&#xff0c;${usrName}、${nowDate}占位符 可以使用 office 或者 wps 先創建一個模板表格 &#xff08;替換$部分可以在 模板格式改變之后 在替換xml 格式改了后有些原本的字符會分開&#xff09;2、用office word將模板word另存…

Java中如何使用非阻塞異步編程——CompletableFuture

分享一波:程序員賺外快-必看的巔峰干貨 對于Node開發者來說&#xff0c;非阻塞異步編程是他們引以為傲的地方。而在JDK8中&#xff0c;也引入了非阻塞異步編程的概念。所謂非阻塞異步編程&#xff0c;就是一種不需要等待返回結果的多線程的回調方法的封裝。使用非阻塞異步編程…

城市運行一網統管_【宣傳活動】持續開展城市運行“一網統管”建設宣傳活動...

為進一步推進本鎮城市運行“一網統管”建設工作&#xff0c;提高城市治理能力和治理水平&#xff0c;提升社會各界的知曉度和參與度&#xff0c;激發職能部門人員、黨員、群眾參與“一網統管”工作的熱情。9月10日&#xff0c;鎮網格中心于福泉居委會議室開展“推進城市運行‘一…

Java如何只使用位運算實現加減乘除

分享一波:程序員賺外快-必看的巔峰干貨 前言 接前面一篇博客&#xff0c;這又是某個公司的奇葩面試題&#xff08;都說了到底是哪家公司才會出這種沒營養的面試題&#xff09;。不過吐槽歸吐槽&#xff0c;這個題目還是有點學問的&#xff0c;比前面那個 不使用比較運算符如何…

Netweaver里某個software component和C4C的版本

有同事問如何通過代碼的方式獲得Netweaver里某個Software component的版本信息&#xff0c;以及Cloud for Customer&#xff08;C4C&#xff09;的版本信息。 Netweaver 點了Detail按鈕后&#xff1a; 這些版本信息存在表CVERS里&#xff1a; C4C C4C的版本號在Help->About …

pmc訂單表格_復工了,讀一則“如何提升訂單準交率和生產效率”的真實故事

故事發生在中國南方小鎮上一個做辦公家具的公司……家具公司創建于1995年&#xff0c;是一家集研發、生產、銷售、服務為一體的現代辦公家具、酒店家具制造企業。主要產品有實木班臺系列、會議臺系列、職員桌系列、屏風系列、沙發系列、辦公座椅、酒店家具系列。在省外還有兩個…

GET和POST請求到底有什么區別?

分享一波:程序員賺外快-必看的巔峰干貨 看到這個標題&#xff0c;想必大部分人都已經想關掉這篇博客了。先別急&#xff0c;你真的知道這兩個的區別嗎&#xff1f; 做過WEB開發的朋友可能很熟悉&#xff0c;看到這個問題能立馬脫口而出二者的區別。 GET在瀏覽器回退時是無害的…

有贊電商云應用框架設計

背景 有贊是 SaaS 公司&#xff0c;向商家提供了全方位的軟件服務&#xff0c;支撐商家進行采購、店鋪、商品、營銷、訂單、物流等等管理服務。 在這個軟件服務里&#xff0c;能夠滿足大部分的商家&#xff0c;為商家保駕護航。 但是很多大商家往往會有自己的特殊需求&#xff…

vivado 如何創建工程模式_基于Vivado的FPGA高性能開發研修班2019年8月30日上海舉行...

一、課程介紹&#xff1a;從7系列FPGA開始&#xff0c;Xilinx提出了Vivado Design Suite設計軟件&#xff0c;提供全新構建的SoC 增強型、以 IP 和系統為中心的下一代開發環境&#xff0c;以解決系統級集成和實現的生產力瓶頸。同時&#xff0c;Xilinx專門針對Vivado推出了Ultr…

程序員的自我修養——遠離“外包思維”

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 在我們做開發的日子里&#xff0c;不免會進行跳槽&#xff0c;跳來跳去公司無非就分成兩大類——互聯網公司、外包公司。當然我們本次討論的并…

英特爾為 Kubernetes 推出分布式深度學習平臺:Nauta

2019獨角獸企業重金招聘Python工程師標準>>> 隨著人工智能的發展&#xff0c;深度學習的價值不斷增長&#xff0c;但實現它可能是一個復雜耗時的過程。英特爾(Intel)正尋求通過其在 Kubernetes 進行分布式深度學習的新開源平臺來改變這一狀況&#xff0c;該深度學習…

pytorch梯度下降函數_Pytorch中常用的四種優化器SGD、Momentum、RMSProp、Adam

來源&#xff1a;AINLPer微信公眾號編輯: ShuYini校稿: ShuYini時間: 2019-8-16 引言很多人在使用pytorch的時候都會遇到優化器選擇的問題&#xff0c;今天就給大家介紹對比一下pytorch中常用的四種優化器。SGD、Momentum、RMSProp、Adam。隨機梯度下降法&#xff08;SGD&#…

2019/02/11-分布式數據庫概述

分布式數據庫類型&#xff08;1&#xff09;同構同質型&#xff1a;各場地都是同一種類型的數據庫&#xff0c;如都是關系型數據庫&#xff0c;且都是同一型號的數據庫管理系統&#xff08;2&#xff09;同構異質型&#xff1a;各場地是同一種類型的數據庫&#xff0c;但是數據…