您好,登錄后才能下訂單哦!
小編給大家分享一下Java數據結構之紅黑樹的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
一、紅黑樹所處數據結構的位置:
在JDK源碼中, 有treeMap和JDK8的HashMap都用到了紅黑樹去存儲
紅黑樹可以看成B樹的一種:
從二叉樹看,紅黑樹是一顆相對平衡的二叉樹
二叉樹-->搜索二叉樹-->平衡搜索二叉樹--> 紅黑樹
從N階樹看,紅黑樹就是一顆 2-3-4樹
N階樹-->B(B-)樹
故我提取出了紅黑樹部分的源碼,去說明紅黑樹的理解
看之前,理解紅黑樹的幾個特性,后面的操作都是為了讓樹符合紅黑樹的這幾個特性,從而滿足對查找效率的O(logn)
二、紅黑樹特性,以及保持的手段
1.根和葉子節點都是黑色的
2.不能有有連續兩個紅色的節點
3.從任一節點到它所能到達得葉子節點的所有簡單路徑都包含相同數目的黑色節點
這幾個特效,個人理解就是規定了紅黑樹是一顆2-3-4的B樹了,從而滿足了O(logn)查找效率
保持特性的手段,通過下面這些手段,讓紅黑樹滿足紅黑樹的特性,如果要嘗試理解,可以從2-3-4樹的向上增長,后面有詳細介紹
當然,這些改變也都是在O(logn)內完成的,主要改變方式有
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) { //新加入紅黑樹的默認節點就是紅色 x.color = RED; /** * 1. 如為根節點直接跳出 */ while (x != null && x != root && x.parent.color == RED) { if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { //如果X的父節點(P)是其父節點的父節點(G)的左節點 //即 下面這種情況 /** * G * P(RED) U */ //獲取其叔(U)節點 Entry<K, V> y = rightOf(parentOf(parentOf(x))); if (colorOf(y) == RED) { // 這種情況,對應下面 圖:情況一 /** * G * P(RED) U(RED) * X */ //如果叔節點是紅色的(父節點有判斷是紅色). 即是雙紅色,比較好辦,通過改變顏色就行. 把P和U都設置成黑色然后,X加到P節點。 G節點當作新加入節點繼續迭代 setColor(parentOf(x), BLACK); setColor(y, BLACK); setColor(parentOf(parentOf(x)), RED); x = parentOf(parentOf(x)); } else { //處理紅父,黑叔的情況 if (x == rightOf(parentOf(x))) { // 這種情況,對應下面 圖:情況二 /** * G * P(RED) U(BLACK) * X */ //如果X是右邊節點 x = parentOf(x); // 進行左旋 rotateLeft(x); } //左旋后,是這種情況了,對應下面 圖:情況三 /** * G * P(RED) U(BLACK) * X */ // 到這,X只能是左節點了,而且P是紅色,U是黑色的情況 //把P改成黑色,G改成紅色,以G為節點進行右旋 setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); rotateRight(parentOf(parentOf(x))); } } else { //父節點在右邊的 /** * 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當作新插入的節點繼續進行迭代 x = parentOf(parentOf(x)); } else { //紅父黑叔,并且是右父的情況 /** * G * U(RED) P(RED) */ if (x == leftOf(parentOf(x))) { //如果插入的X是左節點 /** * G * U(BLACK) P(RED) * X */ x = parentOf(x); //以P為節點進行右旋 rotateRight(x); } //右旋后 /** * G * U(BLACK) P(RED) * X */ setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); //以G為節點進行左旋 rotateLeft(parentOf(parentOf(x))); } } } //紅黑樹的根節點始終是黑色 root.color = BLACK; }
再看看HashMap的實現,在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; }
紅黑樹的維護代碼部分如下:
//hashmap的紅黑樹平衡 static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, TreeNode<K,V> x) { x.red = true; //死循環加變量定義,總感覺JAVA很少這樣寫代碼 哈 for (TreeNode<K,V> xp, xpp, xppl, xppr;;) { //xp X父節點, XPP X的祖父節點, XPPL 祖父左節點 XXPR 祖父右節點 if ((xp = x.parent) == null) { x.red = false; return x; } // 如果父節點是黑色, 或者XP父節點是空,直接返回 else if (!xp.red || (xpp = xp.parent) == null) return root; // 下面的代碼就和上面的很treeMap像了, if (xp == (xppl = xpp.left)) { // 第一種情況, 賦值xppl //父節點是左節點的情況,下面這種 /** * G * P(RED) U */ if ((xppr = xpp.right) != null && xppr.red) { //如果紅叔的情況 // 這種情況,對應下面 圖:情況一 /** * 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) { //如果插入節點在右邊 這種 // 這種情況,對應下面 圖:情況二 /** * G * P(RED) U(BLACK) * X */ //需要進行左旋 root = rotateLeft(root, x = xp); xpp = (xp = x.parent) == null ? null : xp.parent; } //左旋后情況都是這種了,對應下面 圖:情況三 /** * G * P(RED) U(BLACK) * X */ // 到這,X只能是左節點了,而且P是紅色,U是黑色的情況 if (xp != null) { //把P改成黑色,G改成紅色, 以G為節點進行右旋 xp.red = false; if (xpp != null) { xpp.red = true; root = rotateRight(root, xpp); } } } } else { //父節點在右邊的 /** * 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是右節點 /** * 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節點左旋 root = rotateLeft(root, xpp); } } } } }
情況圖如下
情況1
情況2
情況3
JDK源碼處理紅黑樹的流程圖
可見,其實處理邏輯實現都一樣的
三、個人對紅黑樹理解的方法
1. 如何理解紅黑樹的O(lgN)的特性?
從2-3-4樹去理解
紅黑樹,其實是一顆 2-3-4的B樹,B樹都是向上增長的,如果不理解向上增長可以先看看2-3樹,這樣理解就能知道為什么能O(logn)的查找了
2.如何理解紅黑樹的紅黑節點意義?
可以把紅色節點看成是連接父節點的組成的一個大節點(2個或3個或4個節點組成的一個key),如下:
(此圖轉自網上)
紅色的就是和父節點組成了大節點,
比如
節點7和6,6是紅色節點組成,故和它父節點7組成了一個大節點,即 2-3-4樹的 6, 7節點
又如
節點 9和10和11,9和10為紅色節點,故和10組成了一個2-3-4的3階節點, 9,10,11(注意順序有的關性)
3.B樹是如何保持O(lgn)的復雜度的呢?
B+樹都是從底布開始往上生長,自動平衡,如 2-3-4樹,當節點達到了3個時晉升到上個節點,所以不會產生單獨生長一邊的情況,形成平衡。
以上是“Java數據結構之紅黑樹的示例分析”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。