【演算法】紅黑樹插入資料的情況與實現(三)
大家如果有玩魔方,我相信是可以理解我說的東西的,轉魔方就是先把第一面轉出來,然後把第一面作為底面,然後根據遇見的情況來轉魔方(是有公式的)
該系列到現在暫只有3篇文章:
【演算法】紅黑樹(二叉樹)概念與查詢(一):https://blog.csdn.net/lsr40/article/details/85230703
【演算法】紅黑樹插入資料(變色,左旋、右旋)(二):https://blog.csdn.net/lsr40/article/details/85245027
【演算法】紅黑樹插入資料的情況與實現(三):https://blog.csdn.net/lsr40/article/details/85266069
紅黑樹也是一樣,我們也會遇到不同的情況,也會遇到不同情況中的不同階段,我們對於每一種情況的處理(變色和旋轉),都需要讓區域性滿足規則5,然後再迴圈的向上判斷剛剛的變換是否破壞了上層結構的平衡(一般就是破壞了上層的規則4)
1、會遇到的情況:
這裡我總結為4種大情況,在第4種情況中,有階段1和階段2!
情況1:如果是根節點,直接插入就完事了(插入還是固定為紅色,然後在程式碼的最後把根目錄設定為黑色)
情況2:插入節點的父親,為黑色,也一樣,插入就完事了,不用做任何的改動
情況3:插入節點的父親為紅色,叔叔節點(插入節點的爺爺的另一個子節點)的顏色也是紅色
情況4:插入節點的父親為紅色,叔叔節點節點為黑色(這種情況最麻煩,因為需要再做一次判斷)
我先來描述下:這裡可能會出現4種情況(別怕,四種情況只有2種處理方式),看圖:
(爺爺節點我用G表示,爸爸:F,叔叔:U,插入節點:M)
請注意!!!你發現如下的4張圖U這個節點都為NIL(也就是一個不存在的空節點),不存在的空節點預設也為黑色,我強行畫了出來只是為了便於理解,大家可以去:https://sandbox.runjs.cn/show/2nngvn8w 網站上自己插入試試,遇到的情況4,叔叔節點都是空的,另外我發現很多講紅黑樹的文章,也把叔叔節點畫成一個黑色的節點,可能會讓看官產生疑惑(因為如果U是一個存在的節點,那本身就不符合規則5,然後你會發現一頓旋轉操作結束,也不能符合規則5,接著就一臉懵逼),所以我這裡特地標明!
圖從左往右,從上到下看
圖1:父節點是爺爺節點的左節點,插入節點是父節點的右節點
圖2:父節點是爺爺節點的左節點,插入節點是父節點的左節點
圖3:父節點是爺爺節點的右節點,插入節點是父節點的左節點
圖2:父節點是爺爺節點的右節點,插入節點是父節點的右節點
是不是被繞暈了,其實就是爺爺和爸爸和插入節點要是否三點一線,如果不是三點一線,就符合情況4的第1階段
如果是三點一線,就符合情況4的第2階段!為什麼是階段1和階段2呢?因為階段1的處理方式,就是經過旋轉變成階段2,然後再做階段2應該做的旋轉!
2、對於不同情況的整理:
情況1:這棵樹沒有任何節點,插入的點為根節點,一開始插入紅色,然後直接轉為黑色
情況2:父節點是黑色,直接插入,不做任何的換色和旋轉
情況3:父節點是紅色,且叔叔節點也是紅色,直接把叔叔和爸爸變成黑色,然後把爺爺變成和自己一樣的紅色,繼續迭代(因為這樣可能會出現爺爺和太爺爺的都是紅色的情況,那麼就要繼續判斷是哪種情況)
情況4:父節點是紅色,且叔叔節點也是紅色,要先判斷在哪個階段
-1.如果符合階段1,就是圖1和圖3
圖1就對F節點做左旋,圖3就對F節點做右旋,把圖形的樣子旋轉成階段2
-2.如果符合階段2,就是圖2和圖4
圖2就對G節點做右旋,然後將G變紅色,F變黑色,如下圖
圖4就對G節點做左旋,然後G變紅色,F變黑色,如下圖
3、程式碼實現
我們一起來思考下應該怎麼實現?
1、外層要有迴圈
所以我們發現,一套變換下來,實現了局部滿足紅黑樹的所有規則的,但是情況3將爺爺節點變成了紅色,那就有可能爺爺和太爺爺變成了兩個紅色,相互衝突(違反規則4),所以程式碼應該外面有一層迴圈。
2、判斷父節點是爺爺節點的哪邊的子節點
因為情況4中,需要判斷新插入的節點和父親節點和爺爺節點是否三點一線,如果三點一線就直接進行階段2的變換,否則要先進行階段1,再進行階段2。
3、其他設定
插入的節點必須為紅色,程式碼的最後根節點必須設定為黑色,最好封裝好左旋和右旋的方法,獲取父節點,獲取左右子節點的方法等待呼叫
如果上面三點大家能夠想明白的話,我們再一起來看看TreeMap的put和fixAfterInsertion的原始碼!
/**
* 我相信put的程式碼,我就不需要過多的解釋了
* put就是判斷是否有比較器,然後從根節點一層層往下遍歷
* 最後插入到最底下的節點中,關鍵是插入完之後
* 呼叫了fixAfterInsertion方法(用來使插入節點後的樹重新滿足紅黑樹的性質)
*/
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
private void fixAfterInsertion(Entry<K,V> x) {
//將插入的新節點,顏色設定為紅色
x.color = RED;
/**
* 當插入的節點x不為空,不是根節點,並且父節點是紅色的
* 因為如果父節點是黑色的,直接插入紅色的新節點是不會影響紅黑樹的性質的,不用做任何操作
* 所以到這裡已經把情況1和情況2都過濾掉了,下面就是對情況3和情況4的處理
* 並不是處理一次就能得到最後的結果,大家還需要注意x的重新賦值,進入下一次迴圈
*/
while (x != null && x != root && x.parent.color == RED) {
//如果x的祖父節點的左節點是x的父節點,這裡的判斷就是為了情況4的三點一線
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
//那設定x的祖父節點的右節點為y(就是x的叔叔節點為y)
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
//如果叔叔y顏色是紅色,那就是情況3
if (colorOf(y) == RED) {
//那麼就設定x的父節點為黑色
setColor(parentOf(x), BLACK);
//叔叔節點y也設定為黑色
setColor(y, BLACK);
//設定祖父節點為紅色
setColor(parentOf(parentOf(x)), RED);
//把x設定為祖父節點,繼續往上游迴圈
x = parentOf(parentOf(x));
//否則叔叔y的顏色是黑色,情況4
} else {
//如果x是父節點的右節點,三點不一線,先做情況4的階段1
if (x == rightOf(parentOf(x))) {
//把x設定為父節點
x = parentOf(x);
//基於剛剛新設定的x節點進行左旋
rotateLeft(x);
}
//情況4的階段2
//設定x節點為黑色
setColor(parentOf(x), BLACK);
//祖父節點為紅色
setColor(parentOf(parentOf(x)), RED);
//基於祖父節點進行右旋
rotateRight(parentOf(parentOf(x)));
}
//否則就是情況4的另外兩種,父節點是爺爺節點的右子節點
} else {
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
//如果叔叔y是紅色,那就是情況3
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
//把x設定為祖父節點,繼續往上游迴圈
x = parentOf(parentOf(x));
} else {
//否則叔叔y是黑色,就是情況4,先判斷是不是情況4的階段1,
if (x == leftOf(parentOf(x))) {
//階段1會把插入的節點轉到上面去,所以我需要重新設定插入的節點為原來的父節點
x = parentOf(x);
rotateRight(x);
}
//執行情況4的階段2
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;
}