Java數(shù)據(jù)結(jié)構(gòu)之紅黑樹(shù)的真正理解
真正的幫助大家理解紅黑樹(shù):
一、紅黑樹(shù)所處數(shù)據(jù)結(jié)構(gòu)的位置:
在JDK源碼中, 有treeMap和JDK8的HashMap都用到了紅黑樹(shù)去存儲(chǔ)
紅黑樹(shù)可以看成B樹(shù)的一種:
從二叉樹(shù)看,紅黑樹(shù)是一顆相對(duì)平衡的二叉樹(shù)
二叉樹(shù)-->搜索二叉樹(shù)-->平衡搜索二叉樹(shù)--> 紅黑樹(shù)
從N階樹(shù)看,紅黑樹(shù)就是一顆 2-3-4樹(shù)
N階樹(shù)-->B(B-)樹(shù)
故我提取出了紅黑樹(shù)部分的源碼,去說(shuō)明紅黑樹(shù)的理解
看之前,理解紅黑樹(shù)的幾個(gè)特性,后面的操作都是為了讓樹(shù)符合紅黑樹(shù)的這幾個(gè)特性,從而滿足對(duì)查找效率的O(logn)
二、紅黑樹(shù)特性,以及保持的手段
1.根和葉子節(jié)點(diǎn)都是黑色的
2.不能有有連續(xù)兩個(gè)紅色的節(jié)點(diǎn)
3.從任一節(jié)點(diǎn)到它所能到達(dá)得葉子節(jié)點(diǎn)的所有簡(jiǎn)單路徑都包含相同數(shù)目的黑色節(jié)點(diǎn)
這幾個(gè)特效,個(gè)人理解就是規(guī)定了紅黑樹(shù)是一顆2-3-4的B樹(shù)了,從而滿足了O(logn)查找效率
保持特性的手段,通過(guò)下面這些手段,讓紅黑樹(shù)滿足紅黑樹(shù)的特性,如果要嘗試?yán)斫?,可以?-3-4樹(shù)的向上增長(zhǎng),后面有詳細(xì)介紹
當(dāng)然,這些改變也都是在O(logn)內(nèi)完成的,主要改變方式有
1.改變顏色
2.左旋
3.右旋
三、從JDK源碼來(lái)理解
主要看我的注釋,邏輯的理解
先看TreeMap
//對(duì)treeMap的紅黑樹(shù)理解注解. 2017.02.16 by 何錦彬 JDK,1.7.51<br> <br>/** From CLR */ private void fixAfterInsertion(Entry<K, V> x) { //新加入紅黑樹(shù)的默認(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) { // 這種情況,對(duì)應(yīng)下面 圖:情況一 /** * G * P(RED) U(RED) * X */ //如果叔節(jié)點(diǎn)是紅色的(父節(jié)點(diǎn)有判斷是紅色). 即是雙紅色,比較好辦,通過(guò)改變顏色就行. 把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))) { // 這種情況,對(duì)應(yīng)下面 圖:情況二 /** * G * P(RED) U(BLACK) * X */ //如果X是右邊節(jié)點(diǎn) x = parentOf(x); // 進(jìn)行左旋 rotateLeft(x); } //左旋后,是這種情況了,對(duì)應(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))); } } } //紅黑樹(shù)的根節(jié)點(diǎn)始終是黑色 root.color = BLACK; }
再看看HashMap的實(shí)現(xiàn),在HashMap中,在JDK8后開(kāi)始用紅黑樹(shù)代替鏈表,查找由O(n) 變成了 O(Logn)
源碼分析如下:
for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); //JDK8 的hashmap,鏈表到了8就需要變成顆紅黑樹(shù)了 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; }
紅黑樹(shù)的維護(hù)代碼部分如下:
//hashmap的紅黑樹(shù)平衡 static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, TreeNode<K,V> x) { x.red = true; //死循環(huán)加變量定義,總感覺(jué)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) { //如果紅叔的情況 // 這種情況,對(duì)應(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)在右邊 這種 // 這種情況,對(duì)應(yīng)下面 圖:情況二 /** * G * P(RED) U(BLACK) * X */ //需要進(jìn)行左旋 root = rotateLeft(root, x = xp); xpp = (xp = x.parent) == null ? null : xp.parent; } //左旋后情況都是這種了,對(duì)應(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ù)的流程圖
可見(jiàn),其實(shí)處理邏輯實(shí)現(xiàn)都一樣的
三、個(gè)人對(duì)紅黑樹(shù)理解的方法
1. 如何理解紅黑樹(shù)的O(lgN)的特性?
從2-3-4樹(shù)去理解
紅黑樹(shù),其實(shí)是一顆 2-3-4的B樹(shù),B樹(shù)都是向上增長(zhǎng)的,如果不理解向上增長(zhǎng)可以先看看2-3樹(shù),這樣理解就能知道為什么能O(logn)的查找了
2.如何理解紅黑樹(shù)的紅黑節(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樹(shù)的 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樹(shù)是如何保持O(lgn)的復(fù)雜度的呢?
B+樹(shù)都是從底布開(kāi)始往上生長(zhǎng),自動(dòng)平衡,如 2-3-4樹(shù),當(dāng)節(jié)點(diǎn)達(dá)到了3個(gè)時(shí)晉升到上個(gè)節(jié)點(diǎn),所以不會(huì)產(chǎn)生單獨(dú)生長(zhǎng)一邊的情況,形成平衡。
留個(gè)問(wèn)題
4.數(shù)據(jù)庫(kù)里的索引為什么不用紅黑樹(shù)而是用B+樹(shù)(Mysql)呢?
后續(xù)解答
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot整合Scala構(gòu)建Web服務(wù)的方法
這篇文章主要介紹了SpringBoot整合Scala構(gòu)建Web服務(wù)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03Spring?Boot?整合?Fisco?Bcos的案例分析(區(qū)塊鏈)
本篇文章介紹的?Spring?Boot?整合?Fisco?Bcos的案例,是在阿里云服務(wù)器上部署驗(yàn)證的。大家可根據(jù)自己的電腦環(huán)境,對(duì)比該案例進(jìn)行開(kāi)發(fā)即可,具體案例代碼跟隨小編一起看看吧2022-01-01java中對(duì)象轉(zhuǎn)json字符串的幾種常用方式舉例
這篇文章主要給大家介紹了關(guān)于java中對(duì)象轉(zhuǎn)json字符串的幾種常用方式,在Java中可以使用許多庫(kù)將對(duì)象轉(zhuǎn)換為JSON字符串,其中最常用的是Jackson和Gson,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-10-10Java spring webmvc如何實(shí)現(xiàn)控制反轉(zhuǎn)
這篇文章主要介紹了Java spring webmvc如何實(shí)現(xiàn)控制反轉(zhuǎn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08淺談Springboot之于Spring的優(yōu)勢(shì)
這篇文章主要介紹了淺談Springboot之于Spring的優(yōu)勢(shì),簡(jiǎn)述了在Java EE開(kāi)發(fā)中遇到的問(wèn)題,言簡(jiǎn)意賅,需要的朋友可以參考下。2017-09-09啟動(dòng) Eclipse 彈出 Failed to load the JNI shared library jvm.dll
這篇文章主要介紹了有時(shí)候,新電腦上回碰到打開(kāi)Eclipse時(shí),彈出提示“Failed to load the JNI shared library jvm.dll”錯(cuò)誤,這里給大家分享解決方案2016-08-08Maven使用Nexus創(chuàng)建私服的實(shí)現(xiàn)
本文主要介紹了Maven使用Nexus創(chuàng)建私服的實(shí)現(xiàn),通過(guò)建立自己的私服,就可以降低中央倉(cāng)庫(kù)負(fù)荷、節(jié)省外網(wǎng)帶寬、加速M(fèi)aven構(gòu)建、自己部署構(gòu)件等,從而高效地使用Maven,感興趣的可以了解一下2024-04-04org.slf4j.Logger中info()方法的使用詳解
這篇文章主要介紹了org.slf4j.Logger中info()方法的使用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12