您好,登錄后才能下訂單哦!
《Java集合詳解系列》是我在完成夯實(shí)Java基礎(chǔ)篇的系列博客后準(zhǔn)備開始寫的新系列。
這些文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內(nèi)容請(qǐng)到我的倉庫里查看
https://github.com/h3pl/Java-Tutorial
喜歡的話麻煩點(diǎn)下Star、fork哈
文章首發(fā)于我的個(gè)人博客:
www.how2playlife.com
首先,什么是紅黑樹呢? 紅黑樹是一種“平衡的”二叉查找樹,它是一種經(jīng)典高效的算法,能夠保證在最壞的情況下動(dòng)態(tài)集合操作的時(shí)間為O(lgn)。紅黑樹每個(gè)節(jié)點(diǎn)包含5個(gè)域,分別為color,key,left,right和p。 color是在每個(gè)節(jié)點(diǎn)上增加的一個(gè)存儲(chǔ)位表示節(jié)點(diǎn)的顏色,可以是RED或者BLACK。key為結(jié)點(diǎn)中的value值,left,right為該結(jié)點(diǎn)的左右孩子指針,沒有的話為NIL,p是一個(gè)指針,是指向該節(jié)的父節(jié)點(diǎn)。如下圖(來自維基百科)表示就是一顆紅黑樹,NIL為指向外結(jié)點(diǎn)的指針。(外結(jié)點(diǎn)視為沒有key的結(jié)點(diǎn))
?????? 紅黑樹有什么性質(zhì)呢?一般稱為紅黑性質(zhì),有以下五點(diǎn):
???? 1)每個(gè)結(jié)點(diǎn)或者是紅的或者是黑的;
???? 2)根結(jié)點(diǎn)是黑的;
???? 3)每個(gè)葉結(jié)點(diǎn)(NIL)是黑的;
???? 4)如果一個(gè)結(jié)點(diǎn)是紅的,則它的兩個(gè)孩子都是黑的;
???? 5)對(duì)每個(gè)結(jié)點(diǎn),從該結(jié)點(diǎn)到其他其子孫結(jié)點(diǎn)的所有路徑上包含相同數(shù)目的黑結(jié)點(diǎn)。
???????為了后面的分析,我們還得知道以下知識(shí)點(diǎn)。
????(1)黑高度:從某個(gè)結(jié)點(diǎn)x出發(fā)(不包括該結(jié)點(diǎn))到達(dá)一個(gè)葉結(jié)點(diǎn)的任意一條路徑上,黑色結(jié)點(diǎn)的個(gè)數(shù)稱為該結(jié)點(diǎn)x的黑高度。
????(2)一顆有n個(gè)內(nèi)結(jié)點(diǎn)的紅黑樹的高度至多為2lg(n+1)。?? (內(nèi)結(jié)點(diǎn)視為紅黑樹中帶關(guān)鍵字的結(jié)點(diǎn))
??? (3)包含n個(gè)內(nèi)部節(jié)點(diǎn)的紅黑樹的高度是 O(log(n))。
紅黑樹是特殊的二叉查找樹,又名R-B樹(RED-BLACK-TREE),由于紅黑樹是特殊的二叉查找樹,即紅黑樹具有了二叉查找樹的特性,而且紅黑樹還具有以下特性:
1.每個(gè)節(jié)點(diǎn)要么是黑色要么是紅色
2.根節(jié)點(diǎn)是黑色
3.每個(gè)葉子節(jié)點(diǎn)是黑色,并且為空節(jié)點(diǎn)(還有另外一種說法就是,每個(gè)葉子結(jié)點(diǎn)都帶有兩個(gè)空的黑色結(jié)點(diǎn)(被稱為黑哨兵),如果一個(gè)結(jié)點(diǎn)n的只有一個(gè)左孩子,那么n的右孩子是一個(gè)黑哨兵;如果結(jié)點(diǎn)n只有一個(gè)右孩子,那么n的左孩子是一個(gè)黑哨兵。)
4.如果一個(gè)節(jié)點(diǎn)是紅色,則它的子節(jié)點(diǎn)必須是黑色
有幾點(diǎn)需要注意的是:
1.特性3中指定紅黑樹的每個(gè)葉子節(jié)點(diǎn)都是空節(jié)點(diǎn),但是在Java實(shí)現(xiàn)中紅黑樹將使用null代表空節(jié)點(diǎn),因此遍歷紅黑樹時(shí)看不到黑色的葉子節(jié)點(diǎn),反而見到的葉子節(jié)點(diǎn)是紅色的
2.特性4保證了從根節(jié)點(diǎn)到葉子節(jié)點(diǎn)的最長路徑的長度不會(huì)超過任何其他路徑的兩倍,例如黑色高度為3的紅黑樹,其最短路徑(路徑指的是根節(jié)點(diǎn)到葉子節(jié)點(diǎn))是2(黑節(jié)點(diǎn)-黑節(jié)點(diǎn)-黑節(jié)點(diǎn)),其最長路徑為4(黑節(jié)點(diǎn)-紅節(jié)點(diǎn)-黑節(jié)點(diǎn)-紅節(jié)點(diǎn)-黑節(jié)點(diǎn))。
首先紅黑樹在插入節(jié)點(diǎn)的時(shí),我們?cè)O(shè)定插入節(jié)點(diǎn)的顏色為紅色,如果插入的是黑色節(jié)點(diǎn),必然會(huì)違背特性5,即改變了紅黑樹的黑高度,如下插入紅色結(jié)點(diǎn)又存在著幾種情況:
1.黑父
如圖所示,這種情況不會(huì)破壞紅黑樹的特性,即不需要任何處理
2.紅父
當(dāng)其父親為紅色時(shí)又會(huì)存在以下的情況
紅叔的情況,其實(shí)相對(duì)來說比較簡單的,如下圖所示,只需要通過修改父、叔的顏色為黑色,祖的顏色為紅色,而且回去遞歸的檢查祖節(jié)點(diǎn)即可
黑叔的情況有如下幾種,這幾種情況下是不能夠通過修改顏色達(dá)到平衡的效果,因此會(huì)通過旋轉(zhuǎn)的操作,紅黑樹種有兩種旋轉(zhuǎn)操作,左旋和右旋(現(xiàn)在存在的疑問,什么時(shí)候使用到左旋,什么時(shí)候使用到右旋)
以上就是紅黑樹新增節(jié)點(diǎn)所有可能的操作,下面會(huì)介紹紅黑樹中的刪除操作
刪除操作相比于插入操作情況更加復(fù)雜,刪除一個(gè)節(jié)點(diǎn)可以大致分為三種情況:
1.刪除的節(jié)點(diǎn)沒有孩子節(jié)點(diǎn),即當(dāng)前節(jié)點(diǎn)為葉子節(jié)點(diǎn),這種可以直接刪除
2.刪除的節(jié)點(diǎn)有一個(gè)孩子節(jié)點(diǎn),這種需要?jiǎng)h除當(dāng)前節(jié)點(diǎn),并使用其孩子節(jié)點(diǎn)頂替上來
在講述修復(fù)操作之前,首先需要明白幾點(diǎn),
1.對(duì)于紅黑樹而言,單支節(jié)點(diǎn)的情況只有如下圖所示的一種情況,即為當(dāng)前節(jié)點(diǎn)為黑色,其孩子節(jié)點(diǎn)為紅色,(1.假設(shè)當(dāng)前節(jié)點(diǎn)為紅色,其兩個(gè)孩子節(jié)點(diǎn)必須為黑色,2.若有孫子節(jié)點(diǎn),則必為黑色,導(dǎo)致黑子數(shù)量不等,而紅黑樹不平衡)
2.由于紅黑樹是特殊的二叉查找樹,它的刪除和二叉查找樹類型,真正的刪除點(diǎn)即為刪除點(diǎn)A的中序遍歷的后繼(前繼也可以),通過紅黑樹的特性可知這個(gè)后繼必然最多只能有一個(gè)孩子,其這個(gè)孩子節(jié)點(diǎn)必然是右孩子節(jié)點(diǎn),從而為單支情況(即這個(gè)后繼節(jié)點(diǎn)只能有一個(gè)紅色孩子或沒有孩子)
下面將詳細(xì)介紹,在執(zhí)行刪除節(jié)點(diǎn)操作之后,將通過修復(fù)操作使得紅黑樹達(dá)到平衡的情況。
Case 3:被刪除的節(jié)點(diǎn)是黑色,其子節(jié)點(diǎn)也是黑色,將其子節(jié)點(diǎn)頂替上來,變成了雙黑的問題,此時(shí)有以下情況
從圖中可以看出,操作之后紅黑樹并未達(dá)到平衡狀態(tài),而是變成的黑兄的情況
Case 2:新節(jié)點(diǎn)的兄弟節(jié)點(diǎn)為黑色,此時(shí)可能有如下情況
情況一:新節(jié)點(diǎn)在右子樹,紅侄在兄弟節(jié)點(diǎn)左子樹,此時(shí)的操作為右旋,并將兄弟節(jié)點(diǎn)變?yōu)楦赣H的顏色,父親節(jié)點(diǎn)變?yōu)楹谏?,侄?jié)點(diǎn)變?yōu)楹谏缦聢D所示
情況二:新節(jié)點(diǎn)在右子樹,紅侄在兄弟節(jié)點(diǎn)右子樹,此時(shí)的操作為先左旋,后右旋并將侄節(jié)點(diǎn)變?yōu)楦赣H的顏色,父節(jié)點(diǎn)變?yōu)楹谏?,如下圖所示
情況三:新節(jié)點(diǎn)在左子樹,紅侄在兄弟節(jié)點(diǎn)左子樹,此時(shí)的操作為先右旋在左旋并將侄節(jié)點(diǎn)變?yōu)楦赣H的顏色,父親節(jié)點(diǎn)變?yōu)楹谏?,如下圖所示
情況四:新節(jié)點(diǎn)在右子樹,紅侄在兄弟節(jié)點(diǎn)右子樹,此時(shí)的操作為左旋,并將兄弟節(jié)點(diǎn)變?yōu)楦腹?jié)點(diǎn)的顏色,父親節(jié)點(diǎn)變?yōu)楹谏?,侄?jié)點(diǎn)變?yōu)楹谏?,如下圖所示
如下是使用JAVA代碼實(shí)現(xiàn)紅黑樹的過程,主要包括了插入、刪除、左旋、右旋、遍歷等操作
/* 插入一個(gè)節(jié)點(diǎn)
* @param node
*/
private void insert(RBTreeNode<T> node){
int cmp;
RBTreeNode<T> root = this.rootNode;
RBTreeNode<T> parent = null;
//定位節(jié)點(diǎn)添加到哪個(gè)父節(jié)點(diǎn)下
while(null != root){
parent = root;
cmp = node.key.compareTo(root.key);
if (cmp < 0){
root = root.left;
} else {
root = root.right;
}
}
node.parent = parent;
//表示當(dāng)前沒一個(gè)節(jié)點(diǎn),那么就當(dāng)新增的節(jié)點(diǎn)為根節(jié)點(diǎn)
if (null == parent){
this.rootNode = node;
} else {
//找出在當(dāng)前父節(jié)點(diǎn)下新增節(jié)點(diǎn)的位置
cmp = node.key.compareTo(parent.key);
if (cmp < 0){
parent.left = node;
} else {
parent.right = node;
}
}
//設(shè)置插入節(jié)點(diǎn)的顏色為紅色
node.color = COLOR_RED;
//修正為紅黑樹
insertFixUp(node);
}
/**
* 紅黑樹插入修正
* @param node
*/
private void insertFixUp(RBTreeNode<T> node){
RBTreeNode<T> parent,gparent;
//節(jié)點(diǎn)的父節(jié)點(diǎn)存在并且為紅色
while( ((parent = getParent(node)) != null) && isRed(parent)){
gparent = getParent(parent);
//如果其祖父節(jié)點(diǎn)是空怎么處理
// 若父節(jié)點(diǎn)是祖父節(jié)點(diǎn)的左孩子
if(parent == gparent.left){
RBTreeNode<T> uncle = gparent.right;
if ((null != uncle) && isRed(uncle)){
setColorBlack(uncle);
setColorBlack(parent);
setColorRed(gparent);
node = gparent;
continue;
}
if (parent.right == node){
RBTreeNode<T> tmp;
leftRotate(parent);
tmp = parent;
parent = node;
node = tmp;
}
setColorBlack(parent);
setColorRed(gparent);
rightRotate(gparent);
} else {
RBTreeNode<T> uncle = gparent.left;
if ((null != uncle) && isRed(uncle)){
setColorBlack(uncle);
setColorBlack(parent);
setColorRed(gparent);
node = gparent;
continue;
}
if (parent.left == node){
RBTreeNode<T> tmp;
rightRotate(parent);
tmp = parent;
parent = node;
node = tmp;
}
setColorBlack(parent);
setColorRed(gparent);
leftRotate(gparent);
}
}
setColorBlack(this.rootNode);
}
插入節(jié)點(diǎn)的操作主要分為以下幾步:
1.定位:即遍歷整理紅黑樹,確定添加的位置,如上代碼中insert方法中就是在找到添加的位置
如下為刪除節(jié)點(diǎn)的代碼
private void remove(RBTreeNode<T> node){
RBTreeNode<T> child,parent;
boolean color;
//被刪除節(jié)點(diǎn)左右孩子都不為空的情況
if ((null != node.left) && (null != node.right)){
//獲取到被刪除節(jié)點(diǎn)的后繼節(jié)點(diǎn)
RBTreeNode<T> replace = node;
replace = replace.right;
while(null != replace.left){
replace = replace.left;
}
//node節(jié)點(diǎn)不是根節(jié)點(diǎn)
if (null != getParent(node)){
//node是左節(jié)點(diǎn)
if (getParent(node).left == node){
getParent(node).left = replace;
} else {
getParent(node).right = replace;
}
} else {
this.rootNode = replace;
}
child = replace.right;
parent = getParent(replace);
color = getColor(replace);
if (parent == node){
parent = replace;
} else {
if (null != child){
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 == COLOR_BLACK){
removeFixUp(child,parent);
}
node = null;
return;
}
if (null != node.left){
child = node.left;
} else {
child = node.right;
}
parent = node.parent;
color = node.color;
if (null != child){
child.parent = parent;
}
if (null != parent){
if (parent.left == node){
parent.left = child;
} else {
parent.right = child;
}
} else {
this.rootNode = child;
}
if (color == COLOR_BLACK){
removeFixUp(child, parent);
}
node = null;
}
/**
* 刪除修復(fù)
* @param node
* @param parent
*/
private void removeFixUp(RBTreeNode<T> node, RBTreeNode<T> parent){
RBTreeNode<T> other;
//node不為空且為黑色,并且不為根節(jié)點(diǎn)
while ((null == node || isBlack(node)) && (node != this.rootNode) ){
//node是父節(jié)點(diǎn)的左孩子
if (node == parent.left){
//獲取到其右孩子
other = parent.right;
//node節(jié)點(diǎn)的兄弟節(jié)點(diǎn)是紅色
if (isRed(other)){
setColorBlack(other);
setColorRed(parent);
leftRotate(parent);
other = parent.right;
}
//node節(jié)點(diǎn)的兄弟節(jié)點(diǎn)是黑色,且兄弟節(jié)點(diǎn)的兩個(gè)孩子節(jié)點(diǎn)也是黑色
if ((other.left == null || isBlack(other.left)) &&
(other.right == null || isBlack(other.right))){
setColorRed(other);
node = parent;
parent = getParent(node);
} else {
//node節(jié)點(diǎn)的兄弟節(jié)點(diǎn)是黑色,且兄弟節(jié)點(diǎn)的右孩子是紅色
if (null == other.right || isBlack(other.right)){
setColorBlack(other.left);
setColorRed(other);
rightRotate(other);
other = parent.right;
}
//node節(jié)點(diǎn)的兄弟節(jié)點(diǎn)是黑色,且兄弟節(jié)點(diǎn)的右孩子是紅色,左孩子是任意顏色
setColor(other, getColor(parent));
setColorBlack(parent);
setColorBlack(other.right);
leftRotate(parent);
node = this.rootNode;
break;
}
} else {
other = parent.left;
if (isRed(other)){
setColorBlack(other);
setColorRed(parent);
rightRotate(parent);
other = parent.left;
}
if ((null == other.left || isBlack(other.left)) &&
(null == other.right || isBlack(other.right))){
setColorRed(other);
node = parent;
parent = getParent(node);
} else {
if (null == other.left || isBlack(other.left)){
setColorBlack(other.right);
setColorRed(other);
leftRotate(other);
other = parent.left;
}
setColor(other,getColor(parent));
setColorBlack(parent);
setColorBlack(other.left);
rightRotate(parent);
node = this.rootNode;
break;
}
}
}
if (node!=null)
setColorBlack(node);
}
刪除節(jié)點(diǎn)主要分為幾種情況去做對(duì)應(yīng)的處理:
以上主要介紹了紅黑樹的一些特性,包括一些操作詳細(xì)的解析了里面的過程,寫的時(shí)間比較長,感覺確實(shí)比較難理清楚。后面會(huì)持續(xù)的理解更深入,若有存在問題的地方,請(qǐng)指正。
紅黑樹(五)之 Java的實(shí)現(xiàn)
通過分析 JDK 源代碼研究 TreeMap 紅黑樹算法實(shí)現(xiàn)
紅黑樹
(圖解)紅黑樹的插入和刪除
紅黑樹深入剖析及Java實(shí)現(xiàn)
?
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。