R-B Tree,全稱是Red-Black Tree,又稱爲「紅黑樹」,它一種特殊的二叉查找樹。紅黑樹的每一個節點上都有存儲位表示節點的顏色,能夠是紅(Red)或黑(Black)。 R-B Tree,全稱是Red-Black Tree,又稱爲「紅黑樹」,它一種特殊的二叉查找樹。紅黑樹的每一個節點上都有存儲位表示節點的顏色,能夠是紅(Red)或黑(Black)。java
紅黑樹的特性:node
(1)每一個節點或者是黑色,或者是紅色。算法
(2)根節點是黑色。函數
(3)每一個葉子節點(NIL)是黑色。 [注意:這裏葉子節點,是指爲空(NIL或NULL)的葉子節點!]post
(4)若是一個節點是紅色的,則它的子節點必須是黑色的。測試
(5)從一個節點到該節點的子孫節點的全部路徑上包含相同數目的黑節點。this
注意:3d
(01) 特性(3)中的葉子節點,是隻爲空(NIL或null)的節點。code
(02) 特性(5),確保沒有一條路徑會比其餘路徑長出倆倍。於是,紅黑樹是相對是接近平衡的二叉樹。blog
紅黑樹的時間複雜度爲: O(lgn)
下面經過「數學概括法」對紅黑樹的時間複雜度進行證實。
定理:一棵含有n個節點的紅黑樹的高度至多爲2log(n+1).
證實: "一棵含有n個節點的紅黑樹的高度至多爲2log(n+1)" 的逆否命題是 "高度爲h的紅黑樹,它的包含的內節點個數至少爲 2^{h/2}-1個"。 咱們只須要證實逆否命題,便可證實原命題爲真;即只需證實 "高度爲h的紅黑樹,它的包含的內節點個數至少爲 2^{h/2}-1個"。 從某個節點x出發(不包括該節點)到達一個葉節點的任意一條路徑上,黑色節點的個數稱爲該節點的黑高度,記爲bh(x)。 由紅黑樹的"特性(4)"可知 bh(x)>=h/2;進而,咱們只需證實 "高度爲h的紅黑樹,它的包含的內節點個數至少爲 2^bh(x)-1個"便可。 到這裏,咱們將須要證實的定理已經由"一棵含有n個節點的紅黑樹的高度至多爲2log(n+1)" 轉變成只須要證實"高度爲h的紅黑樹,它的包含的內節點個數至少爲 2^bh(x)-1個"。
下面經過"數學概括法"開始論證高度爲h的紅黑樹,它的包含的內節點個數至少爲 2^bh(x)-1個"。
(01) 當樹的高度h=0時,內節點個數是0,bh(x) 爲0,2^bh(x)-1 也爲 0。顯然,原命題成立。
(02) 當h>0,且樹的高度爲 h-1 時,它包含的節點個數至少爲 2^{bh(x)-1}-1。這個是根據(01)推斷出來的! 下面,由樹的高度爲 h-1 的已知條件推出「樹的高度爲 h 時,它所包含的節點樹爲 2^bh(x)-1」。 當樹的高度爲 h 時, 對於節點x(x爲根節點),其黑高度爲bh(x)。 對於節點x的左右子樹,它們黑高度爲 bh(x) 或者 bh(x)-1。 根據(02)的已知條件,咱們已知 "x的左右子樹,即高度爲 h-1 的節點,它包含的節點至少爲 2^{bh(x)-1}-1 個"; 因此,節點x所包含的節點至少爲 ( 2^{bh(x)-1}-1 ) + ( 2^{bh(x)-1}-1 ) + 1 = 2^{bh(x)-1}。即節點x所包含的節點至少爲 2^{bh(x)-1} 。 所以,原命題成立。 由(01)、(02)得出,"高度爲h的紅黑樹,它的包含的內節點個數至少爲 2^bh(x)-1個"。所以,「一棵含有n個節點的紅黑樹的高度至多爲2log(n+1)」。
紅-黑樹主要經過三種方式對平衡進行修正,改變節點顏色、左旋和右旋。這看起來有點抽象,咱們分別來介紹它們。
改變節點顏色比較容易理解,由於它違背了規則3。假設如今有個節點E,而後插入節點A和節點S,節點A在左子節點,S在右子節點,目前是平衡的。若是此時再插一個節點,那麼就出現了不平衡了,由於紅色節點的子節點必須爲黑色,可是新插的節點是紅色的。因此這時候就必須改變節點顏色了。因此咱們將根的兩個子節點從紅色變爲黑色(至於爲何都要變,下面插入的時候會詳細介紹),將父節點會從黑色變成紅色。能夠用以下示意圖表示一下:
一般左旋操做用於將一個向右傾斜的紅色連接旋轉爲向左連接。示意圖以下:
左旋有個很萌萌噠的動態示意圖,能夠方便理解:
右旋可左旋恰好相反,這裏再也不贅述,直接看示意圖:
固然咯,右旋也有個萌萌的動態圖:
這裏主要介紹了紅-黑樹對平衡的三種修正方式,你們有個感性的認識,那麼何時該修正呢?何時該用哪一種修正呢?這將是接下來咱們要探討的問題。
紅-黑樹的基本操做是添加、刪除和旋轉。對紅-黑樹進行添加或刪除後,可能會破壞其平衡性,會用到哪一種旋轉方式去修正呢?咱們首先對紅-黑樹的節點作一介紹,而後分別對左旋和右旋的具體實現作一分析,最後咱們探討下紅-黑樹的具體操做。
紅-黑樹是對二叉搜索樹的改進,因此其節點與二叉搜索樹是差很少的,只不過在它基礎上增長了一個boolean型變量來表示節點的顏色,具體看RBNode<T>類:
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"); } }
//算法導論僞代碼 LEFT-ROTATE(T, x) 01 y ← right[x] // 前提:這裏假設x的右孩子爲y。下面開始正式操做 02 right[x] ← left[y] // 將 「y的左孩子」 設爲 「x的右孩子」,即 將β設爲x的右孩子 03 p[left[y]] ← x // 將 「x」 設爲 「y的左孩子的父親」,即 將β的父親設爲x 04 p[y] ← p[x] // 將 「x的父親」 設爲 「y的父親」 05 if p[x] = nil[T] 06 then root[T] ← y // 狀況1:若是 「x的父親」 是空節點,則將y設爲根節點 07 else if x = left[p[x]] 08 then left[p[x]] ← y // 狀況2:若是 x是它父節點的左孩子,則將y設爲「x的父節點的左孩子」 09 else right[p[x]] ← y // 狀況3:(x是它父節點的右孩子) 將y設爲「x的父節點的右孩子」 10 left[y] ← x // 將 「x」 設爲 「y的左孩子」 11 p[x] ← y // 將 「x的父節點」 設爲 「y」
/*************對紅黑樹節點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; }
RIGHT-ROTATE(T, y) 01 x ← left[y] // 前提:這裏假設y的左孩子爲x。下面開始正式操做 02 left[y] ← right[x] // 將 「x的右孩子」 設爲 「y的左孩子」,即 將β設爲y的左孩子 03 p[right[x]] ← y // 將 「y」 設爲 「x的右孩子的父親」,即 將β的父親設爲y 04 p[x] ← p[y] // 將 「y的父親」 設爲 「x的父親」 05 if p[y] = nil[T] 06 then root[T] ← x // 狀況1:若是 「y的父親」 是空節點,則將x設爲根節點 07 else if y = right[p[y]] 08 then right[p[y]] ← x // 狀況2:若是 y是它父節點的右孩子,則將x設爲「y的父節點的左孩子」 09 else left[p[y]] ← x // 狀況3:(y是它父節點的左孩子) 將x設爲「y的父節點的左孩子」 10 right[x] ← y // 將 「y」 設爲 「x的右孩子」 11 p[y] ← x // 將 「y的父節點」 設爲 「x」
/*************對紅黑樹節點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; }
第一步: 將紅黑樹看成一顆二叉查找樹,將節點插入。 紅黑樹自己就是一顆二叉查找樹,將節點插入後,該樹仍然是一顆二叉查找樹。也就意味着,樹的鍵值仍然是有序的。此外,不管是左旋仍是右旋,若旋轉以前這棵樹是二叉查找樹,旋轉以後它必定仍是二叉查找樹。這也就意味着,任何的旋轉和從新着色操做,都不會改變它仍然是一顆二叉查找樹的事實。 第二步:將插入的節點着色爲"紅色"。 將插入的節點着色爲紅色,不會違背"特性(5)"!少違背一條特性,就意味着咱們須要處理的狀況越少。接下來,就要努力的讓這棵樹知足其它性質便可;知足了的話,它就又是一顆紅黑樹了。o(∩∩)o...哈哈
第三步: 經過一系列的旋轉或着色等操做,使之從新成爲一顆紅黑樹。
根據被插入節點的父節點的狀況,能夠將"當節點z被着色爲紅色節點,並插入二叉樹"劃分爲三種狀況來處理。 根據被插入節點的父節點的狀況,能夠將"當節點z被着色爲紅色節點,並插入二叉樹"劃分爲三種狀況來處理。
① 狀況說明:被插入的節點是根節點。 處理方法:直接把此節點塗爲黑色。
② 狀況說明:被插入的節點的父節點是黑色。 處理方法:什麼也不須要作。節點被插入後,仍然是紅黑樹。
③ 狀況說明:被插入的節點的父節點是紅色。 處理方法:那麼,該狀況與紅黑樹的「特性(5)」相沖突。這種狀況下,被插入節點是必定存在非空祖父節點的;進一步的講,被插入節點也必定存在叔叔節點(即便叔叔節點爲空,咱們也視之爲存在,空節點自己就是黑色節點)。理解這點以後,咱們依據"叔叔節點的狀況",將這種狀況進一步劃分爲3種狀況(Case)。
分析完了紅-黑樹中主要的旋轉操做,接下來咱們開始分析常見的插入、刪除等操做了。這裏先分析插入操做。 因爲紅-黑樹是二叉搜索樹的改進,因此插入操做的前半工做時相同的,即先找到待插入的位置,再將節點插入,先來看看插入的前半段代碼:
RB-INSERT(T, z) y ← nil[T] // 新建節點「y」,將y設爲空節點。 x ← root[T] // 設「紅黑樹T」的根節點爲「x」 while x ≠ nil[T] // 找出要插入的節點「z」在二叉樹T中的位置「y」 do y ← x if key[z] < key[x] then x ← left[x] else x ← right[x] p[z] ← y // 設置 「z的父親」 爲 「y」 if y = nil[T] then root[T] ← z // 狀況1:若y是空節點,則將z設爲根 else if key[z] < key[y] then left[y] ← z // 狀況2:若「z所包含的值」 < 「y所包含的值」,則將z設爲「y的左孩子」 else right[y] ← z // 狀況3:(「z所包含的值」 >= 「y所包含的值」)將z設爲「y的右孩子」 left[z] ← nil[T] // z的左孩子設爲空 right[z] ← nil[T] // z的右孩子設爲空。至此,已經完成將「節點z插入到二叉樹」中了。 color[z] ← RED // 將z着色爲「紅色」 RB-INSERT-FIXUP(T, z) // 經過RB-INSERT-FIXUP對紅黑樹的節點進行顏色修改以及旋轉,讓樹T仍然是一顆紅黑樹
結合僞代碼以及爲代碼上面的說明,先理解RB-INSERT。理解了RB-INSERT以後,咱們接着對 RB-INSERT-FIXUP的僞代碼進行說明。
添加修正操做的僞代碼《算法導論》
RB-INSERT-FIXUP(T, z) while color[p[z]] = RED // 若「當前節點(z)的父節點是紅色」,則進行如下處理。 do if p[z] = left[p[p[z]]] // 若「z的父節點」是「z的祖父節點的左孩子」,則進行如下處理。 then y ← right[p[p[z]]] // 將y設置爲「z的叔叔節點(z的祖父節點的右孩子)」 if color[y] = RED // Case 1條件:叔叔是紅色 then color[p[z]] ← BLACK ▹ Case 1 // (01) 將「父節點」設爲黑色。 color[y] ← BLACK ▹ Case 1 // (02) 將「叔叔節點」設爲黑色。 color[p[p[z]]] ← RED ▹ Case 1 // (03) 將「祖父節點」設爲「紅色」。 z ← p[p[z]] ▹ Case 1 // (04) 將「祖父節點」設爲「當前節點」(紅色節點) else if z = right[p[z]] // Case 2條件:叔叔是黑色,且當前節點是右孩子 then z ← p[z] ▹ Case 2 // (01) 將「父節點」做爲「新的當前節點」。 LEFT-ROTATE(T, z) ▹ Case 2 // (02) 以「新的當前節點」爲支點進行左旋。 color[p[z]] ← BLACK ▹ Case 3 // Case 3條件:叔叔是黑色,且當前節點是左孩子。(01) 將「父節點」設爲「黑色」。 color[p[p[z]]] ← RED ▹ Case 3 // (02) 將「祖父節點」設爲「紅色」。 RIGHT-ROTATE(T, p[p[z]]) ▹ Case 3 // (03) 以「祖父節點」爲支點進行右旋。 else (same as then clause with "right" and "left" exchanged) // 若「z的父節點」是「z的祖父節點的右孩子」,將上面的操做中「right」和「left」交換位置,而後依次執行。 color[root[T]] ← BLACK
/*********************** 向紅黑樹中插入節點 **********************/ 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,因此只要把根節點塗黑便可;若是插入節點的父節點是黑色的,那不會違背紅-黑樹的規則,什麼也不須要作;可是遇到以下三種狀況時,咱們就要開始變色和旋轉了:
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; } }
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"); } } } }