資料結構---紅黑樹
紅黑樹怎麼來?歷史緣由
先說二叉樹,父節點劈叉出兩個節點,就兩個,不可以多。這樣一層一層下去。
再說二叉查詢樹(基於二分查詢),同樣有上面的劈叉屬性,但是呢左中右有大小順序,左邊小於中間,中間小於右邊。
接著就是紅黑樹,幹嘛上面下來就紅黑,二叉樹如果不加以控制就成單邊樹了。因為二叉樹沒規定不可以單邊開叉,而且一直往下開叉,5左單邊4再左單邊3,再左單邊2,類似的就失去了二叉查詢樹的意義了。這下紅黑就出來了。
紅黑為了達到樓上的特點就列出了五個特點
一、節點必須紅黑兩種顏色。treemap就用了boolean值分別表示
二、根節點必須黑色
三、沒個尾節點下面是黑色節點(感覺沒啥意義)
四、紅色節點下面或者上面不可以連續兩個出現
五、任意節點到他子節點經過的黑色節點個數一樣多。
那這玩意搞著五個條件無非就是限制插入,刪除。有人破壞樹的情況下,限制的。
例如,插入一個節點,首先二分查詢節點,再fix。fix就為了滿足這五個規則。一般都是不符合後面這兩條。紅色不可以連著,任意節點到子節點黑色同樣個數。
jdk經典案例,treeset、treemap。上酒,小二。
就treemap原始碼分析。
一、treemap內部類子節點除了map中的key和value,還有左右節點和父節點,最後特有顏色(boolean值),遍歷都是一樣的。
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left = null;
Entry<K,V> right = null;
Entry<K,V> parent;
boolean color = BLACK;
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
二、put查找出要插入的位置,也就是父節點。 fixAfterInsertion(e);這裡是重點。
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();
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;
}
三、重點 fixAfterInsertion(e);
private void fixAfterInsertion(Entry<K,V> x) {
//直接紅色,就要搞破壞,黑色就沒啥了
x.color = RED;
//新增節點非空,且不是根節點,且新增節點(自己,以下稱呼自己)父親是紅色,如果父親是黑色也不搞事。
while (x != null && x != root && x.parent.color == RED) {
//父親跟父親的父親的左節點equal?也就是父親是否為祖父的左節點
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
//祖父右節點,叔叔
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
//叔叔顏色紅色,跟父親紅色一樣,省事,父親和叔叔一起搞成黑色,祖父搞成黑色,再以祖父為自己,繼續迴圈遍歷
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));//以祖父為自己,遍歷
} else {
//叔叔顏色為黑色
//自己是父親的右節點要左轉
if (x == rightOf(parentOf(x))) {
x = parentOf(x);
rotateLeft(x);
}
//變換父親(黑色)和祖父顏色(紅色)
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
//右轉祖父
rotateRight(parentOf(parentOf(x)));
}
} else {
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
//叔叔為紅色跟樓上一樣,做同樣操作,說明無論父親是左,還是右,叔叔跟父親要是同樣顏色,一樣變色就可以。
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
//父親是祖父的右,自己是父親的左,右轉。樓上跟這個就是反著來。父親是祖父的左,自己是父親的右,左轉
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
//跟上面的操作一樣。父親黑色,祖父紅色
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
//左轉祖父(發現都是左右轉交替)
rotateLeft(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;
}
不強求記,畢竟像紅黑樹,外面大把的實現。
可以畫圖,進一步分析為啥,等有空再搞