Java數(shù)據(jù)結(jié)構(gòu)之紅黑樹的真正理解
真正的幫助大家理解紅黑樹:
一、紅黑樹所處數(shù)據(jù)結(jié)構(gòu)的位置:
在JDK源碼中, 有treeMap和JDK8的HashMap都用到了紅黑樹去存儲
紅黑樹可以看成B樹的一種:
從二叉樹看,紅黑樹是一顆相對平衡的二叉樹
二叉樹-->搜索二叉樹-->平衡搜索二叉樹--> 紅黑樹
從N階樹看,紅黑樹就是一顆 2-3-4樹
N階樹-->B(B-)樹
故我提取出了紅黑樹部分的源碼,去說明紅黑樹的理解
看之前,理解紅黑樹的幾個(gè)特性,后面的操作都是為了讓樹符合紅黑樹的這幾個(gè)特性,從而滿足對查找效率的O(logn)
二、紅黑樹特性,以及保持的手段
1.根和葉子節(jié)點(diǎn)都是黑色的
2.不能有有連續(xù)兩個(gè)紅色的節(jié)點(diǎn)
3.從任一節(jié)點(diǎn)到它所能到達(dá)得葉子節(jié)點(diǎn)的所有簡單路徑都包含相同數(shù)目的黑色節(jié)點(diǎn)
這幾個(gè)特效,個(gè)人理解就是規(guī)定了紅黑樹是一顆2-3-4的B樹了,從而滿足了O(logn)查找效率
保持特性的手段,通過下面這些手段,讓紅黑樹滿足紅黑樹的特性,如果要嘗試?yán)斫猓梢詮?-3-4樹的向上增長,后面有詳細(xì)介紹
當(dāng)然,這些改變也都是在O(logn)內(nèi)完成的,主要改變方式有
1.改變顏色
2.左旋
3.右旋
三、從JDK源碼來理解
主要看我的注釋,邏輯的理解
先看TreeMap
//對treeMap的紅黑樹理解注解. 2017.02.16 by 何錦彬 JDK,1.7.51<br> <br>/** From CLR */
private void fixAfterInsertion(Entry<K, V> x) {
//新加入紅黑樹的默認(rèn)節(jié)點(diǎn)就是紅色
x.color = RED;
/**
* 1. 如為根節(jié)點(diǎn)直接跳出
*/
while (x != null && x != root && x.parent.color == RED) {
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
//如果X的父節(jié)點(diǎn)(P)是其父節(jié)點(diǎn)的父節(jié)點(diǎn)(G)的左節(jié)點(diǎn)
//即 下面這種情況
/**
* G
* P(RED) U
*/
//獲取其叔(U)節(jié)點(diǎn)
Entry<K, V> y = rightOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
// 這種情況,對應(yīng)下面 圖:情況一
/**
* G
* P(RED) U(RED)
* X
*/
//如果叔節(jié)點(diǎn)是紅色的(父節(jié)點(diǎn)有判斷是紅色). 即是雙紅色,比較好辦,通過改變顏色就行. 把P和U都設(shè)置成黑色然后,X加到P節(jié)點(diǎn)。 G節(jié)點(diǎn)當(dāng)作新加入節(jié)點(diǎn)繼續(xù)迭代
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
//處理紅父,黑叔的情況
if (x == rightOf(parentOf(x))) {
// 這種情況,對應(yīng)下面 圖:情況二
/**
* G
* P(RED) U(BLACK)
* X
*/
//如果X是右邊節(jié)點(diǎn)
x = parentOf(x);
// 進(jìn)行左旋
rotateLeft(x);
}
//左旋后,是這種情況了,對應(yīng)下面 圖:情況三
/**
* G
* P(RED) U(BLACK)
* X
*/
// 到這,X只能是左節(jié)點(diǎn)了,而且P是紅色,U是黑色的情況
//把P改成黑色,G改成紅色,以G為節(jié)點(diǎn)進(jìn)行右旋
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateRight(parentOf(parentOf(x)));
}
} else {
//父節(jié)點(diǎn)在右邊的
/**
* G
* U P(RED)
*/
//獲取U
Entry<K, V> y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
//紅父紅叔的情況
/**
* G
* U(RED) P(RED)
*/
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
//把G當(dāng)作新插入的節(jié)點(diǎn)繼續(xù)進(jìn)行迭代
x = parentOf(parentOf(x));
} else {
//紅父黑叔,并且是右父的情況
/**
* G
* U(RED) P(RED)
*/
if (x == leftOf(parentOf(x))) {
//如果插入的X是左節(jié)點(diǎn)
/**
* G
* U(BLACK) P(RED)
* X
*/
x = parentOf(x);
//以P為節(jié)點(diǎn)進(jìn)行右旋
rotateRight(x);
}
//右旋后
/**
* G
* U(BLACK) P(RED)
* X
*/
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
//以G為節(jié)點(diǎn)進(jìn)行左旋
rotateLeft(parentOf(parentOf(x)));
}
}
}
//紅黑樹的根節(jié)點(diǎn)始終是黑色
root.color = BLACK;
}
再看看HashMap的實(shí)現(xiàn),在HashMap中,在JDK8后開始用紅黑樹代替鏈表,查找由O(n) 變成了 O(Logn)
源碼分析如下:
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//JDK8 的hashmap,鏈表到了8就需要變成顆紅黑樹了
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
紅黑樹的維護(hù)代碼部分如下:
//hashmap的紅黑樹平衡
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
TreeNode<K,V> x) {
x.red = true;
//死循環(huán)加變量定義,總感覺JAVA很少這樣寫代碼 哈
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
//xp X父節(jié)點(diǎn), XPP X的祖父節(jié)點(diǎn), XPPL 祖父左節(jié)點(diǎn) XXPR 祖父右節(jié)點(diǎn)
if ((xp = x.parent) == null) {
x.red = false;
return x;
}
// 如果父節(jié)點(diǎn)是黑色, 或者XP父節(jié)點(diǎn)是空,直接返回
else if (!xp.red || (xpp = xp.parent) == null)
return root;
// 下面的代碼就和上面的很treeMap像了,
if (xp == (xppl = xpp.left)) {
// 第一種情況, 賦值xppl
//父節(jié)點(diǎn)是左節(jié)點(diǎn)的情況,下面這種
/**
* G
* P(RED) U
*/
if ((xppr = xpp.right) != null && xppr.red) {
//如果紅叔的情況
// 這種情況,對應(yīng)下面 圖:情況一
/**
* G
* P(RED) U(RED)
* X
*/
//改變其顏色,
xppr.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else {
// 黑叔的情況
// 這種情況
/**
* G
* P(RED) U(BLACK)
*/
if (x == xp.right) {
//如果插入節(jié)點(diǎn)在右邊 這種
// 這種情況,對應(yīng)下面 圖:情況二
/**
* G
* P(RED) U(BLACK)
* X
*/
//需要進(jìn)行左旋
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
//左旋后情況都是這種了,對應(yīng)下面 圖:情況三
/**
* G
* P(RED) U(BLACK)
* X
*/
// 到這,X只能是左節(jié)點(diǎn)了,而且P是紅色,U是黑色的情況
if (xp != null) {
//把P改成黑色,G改成紅色, 以G為節(jié)點(diǎn)進(jìn)行右旋
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateRight(root, xpp);
}
}
}
}
else {
//父節(jié)點(diǎn)在右邊的
/**
* G
* U P(RED)
*/
//獲取U
if (xppl != null && xppl.red) {
//紅父紅叔的情況
/**
* G
* U(RED) P(RED)
*/
xppl.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else {
if (x == xp.left) {
//如果插入的X是右節(jié)點(diǎn)
/**
* G
* U(BLACK) P(RED)
* X
*/
root = rotateRight(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
//右旋后
/**
* G
* U(BLACK) P(RED)
* X
*/
if (xp != null) {
//把P改成黑色,G改成紅色,
xp.red = false;
if (xpp != null) {
xpp.red = true;
//以G節(jié)點(diǎn)左旋
root = rotateLeft(root, xpp);
}
}
}
}
}
情況圖如下

情況1

情況2

情況3
JDK源碼處理紅黑樹的流程圖

可見,其實(shí)處理邏輯實(shí)現(xiàn)都一樣的
三、個(gè)人對紅黑樹理解的方法
1. 如何理解紅黑樹的O(lgN)的特性?
從2-3-4樹去理解
紅黑樹,其實(shí)是一顆 2-3-4的B樹,B樹都是向上增長的,如果不理解向上增長可以先看看2-3樹,這樣理解就能知道為什么能O(logn)的查找了
2.如何理解紅黑樹的紅黑節(jié)點(diǎn)意義?
可以把紅色節(jié)點(diǎn)看成是連接父節(jié)點(diǎn)的組成的一個(gè)大節(jié)點(diǎn)(2個(gè)或3個(gè)或4個(gè)節(jié)點(diǎn)組成的一個(gè)key),如下:

(此圖轉(zhuǎn)自網(wǎng)上)
紅色的就是和父節(jié)點(diǎn)組成了大節(jié)點(diǎn),
比如
節(jié)點(diǎn)7和6,6是紅色節(jié)點(diǎn)組成,故和它父節(jié)點(diǎn)7組成了一個(gè)大節(jié)點(diǎn),即 2-3-4樹的 6, 7節(jié)點(diǎn)
又如
節(jié)點(diǎn) 9和10和11,9和10為紅色節(jié)點(diǎn),故和10組成了一個(gè)2-3-4的3階節(jié)點(diǎn), 9,10,11(注意順序有的關(guān)性)
3.B樹是如何保持O(lgn)的復(fù)雜度的呢?
B+樹都是從底布開始往上生長,自動平衡,如 2-3-4樹,當(dāng)節(jié)點(diǎn)達(dá)到了3個(gè)時(shí)晉升到上個(gè)節(jié)點(diǎn),所以不會產(chǎn)生單獨(dú)生長一邊的情況,形成平衡。
留個(gè)問題
4.數(shù)據(jù)庫里的索引為什么不用紅黑樹而是用B+樹(Mysql)呢?
后續(xù)解答
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
基于HTML5+js+Java實(shí)現(xiàn)單文件文件上傳到服務(wù)器功能
應(yīng)公司要求,在HTML5頁面上實(shí)現(xiàn)上傳文件到服務(wù)器功能,對于我這樣的菜鳥,真是把我難住了,最后還是請教大神搞定的,下面小編把例子分享到腳本之家平臺,供大家參考2017-08-08
Java中實(shí)現(xiàn)分布式定時(shí)任務(wù)的方法
這篇文章主要介紹了Java中實(shí)現(xiàn)分布式定時(shí)任務(wù),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
怎樣通過反射獲取非靜態(tài)內(nèi)部類實(shí)例
這篇文章主要介紹了怎樣通過反射獲取非靜態(tài)內(nèi)部類實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
Java 如何實(shí)現(xiàn)一個(gè)http服務(wù)器
這篇文章主要介紹了Java 如何實(shí)現(xiàn)一個(gè)http服務(wù)器,幫助大家更好的理解和學(xué)習(xí)Java,感興趣的朋友可以了解下2020-11-11
Spring Boot優(yōu)雅使用RocketMQ的方法實(shí)例
這篇文章主要給大家介紹了關(guān)于Spring Boot優(yōu)雅使用RocketMQ的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Spring Boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
SpringCloud Alibaba Seata (收藏版)
Seata是一款開源的分布式事務(wù)解決方案,致力于在微服務(wù)架構(gòu)在提供高性能和簡單一樣的分布式事務(wù)服務(wù)。這篇文章主要介紹了SpringCloud Alibaba Seata 的相關(guān)知識,需要的朋友可以參考下2020-10-10
Java實(shí)現(xiàn)異步執(zhí)行的8種方式總結(jié)
這篇文章主要給大家介紹了關(guān)于Java實(shí)現(xiàn)異步執(zhí)行的8種方式,異步編程不會阻塞程序的執(zhí)行,它將耗時(shí)的操作提交給后臺線程或其他執(zhí)行環(huán)境,并立即返回,使得程序可以繼續(xù)執(zhí)行其他任務(wù),需要的朋友可以參考下2023-09-09
MyBatis主鍵生成策略中useGeneratedKeys和<selectKey>的區(qū)別
本文主要介紹了MyBatis主鍵生成策略中useGeneratedKeys和<selectKey>的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-01-01

