【演算法4】3.3.紅黑樹
阿新 • • 發佈:2022-06-05
平衡查詢樹
理想情況下,我們希望能夠保持二分查詢樹的平衡性。
在一棵含有 N 個結點的樹中,我們希望樹高為 \(log_2 N\)。
2-3 樹
2-3 樹由兩種結點組成:
- 2- 結點:有一個鍵和兩條連結
- 3- 結點:有兩個鍵和三條連結
向一棵 2-3 樹插入一個鍵值對:
- 如果查詢結束於一個 2- 結點,那麼直接將鍵值對插入該結點形成一個 3- 結點即可
- 如果查詢結束於一個 3- 結點,那麼需要臨時構造一個 4- 結點,再將中鍵推入父節點。遞迴此過程直到遇到一個 2- 結點(樹的高度不變)或分解根結點(樹高度 +1)
紅黑樹
雖然 2-3 樹能夠在動態插入保持平衡性,但是其直接實現需要維護兩種型別的結點、在不同型別結點間進行轉換和複製資訊、
在結點中對每個鍵進行比較等,這會增加程式碼的複雜度和開銷。
紅黑樹是一棵二叉查詢樹,它使用紅色左連線及其連線的兩個結點來表示 2-3 樹的 3- 結點。
紅黑樹的另一種定義是含有紅黑連線並滿足以下條件的二叉查詢樹:
- 紅色連線均為左連線
- 沒有任何一個結點同時和兩條紅連線相連
- 該樹是完美黑色平衡的,即任意空連結到根結點的路徑上的黑鏈數量相同
紅黑樹實現
旋轉和顏色變換
左旋轉:將帶有紅色右連線的根結點轉變成帶有紅色左連線的根結點
右旋轉:將帶有紅色左連線的根結點轉變成帶有紅色右連線的根結點
插入新結點時,我們總是使用紅色連線將新結點與父結點連線。
向一個 2- 結點插入新鍵:
- 如果插入到左連線,則會直接形成一個 3- 結點
- 如果插入到右連線,則需要先進行左旋轉才會形成一個正確的 3- 結點
向一個 3- 結點插入新鍵:
- 如果新鍵大於原樹中的兩個結點,則將新鍵插入到右連線。此時需要分解此臨時的 4- 結點,即將兩條紅色連線變成黑色並將父結點的連線變成紅色(對應到 2-3 將中鍵推入父結點並生成兩個 2- 子結點)。
- 如果新鍵小於原樹中的兩個結點,則將新鍵連線到左子結點的左連線。此時會出現兩條連續的紅連線,需要對上層連線進行右旋轉,得到第一種情況
- 如果新鍵介於原樹種的兩個結點,則將新增連線到左子結點的右連線。對下層連線進行左旋轉會得到第二種情況
進行顏色變換後,根結點上的連線可能會變成紅色,但根結點上不會再有其他結點,每次插入後需要將根結點上的連線重置成黑色。
對應到程式碼實現,查詢會結束於一個 2- 結點,我們會用紅色左/右連線插入新結點,然後依次執行以下步驟:
- 如果左連線是黑色且右連線是紅色,進行左旋轉(全紅是要進行顏色變換)
- 如果左連線是紅色且左子結點的左連線也是紅色,進行右旋轉
- 如果兩個連線都是紅色,執行顏色變換
程式碼實現
結點定義:
private static final boolean RED = true;
private static final boolean BLACK = false;
private class Node {
private K key;
private V value;
private boolean color; // 指向該結點連線的顏色
private Node left;
private Node right;
public Node(K key, V value, boolean color) {
this.key = key;
this.value = value;
this.color = color;
}
}
private boolean isRed(Node node) {
if (node == null) return BLACK;
return node.color;
}
旋轉和顏色變換:
// 傳入指向父結點連線
// 返回父節點,並在呼叫函式重置指向父結點的連線
// 左旋轉:將紅色右連線變成紅色左連線
private Node rotateLeft(Node h) {
Node x = h.right;
h.right = x.left;
x.left = h;
x.color = h.color;
x.left.color = RED;
x.N = h.N;
h.N = size(h);
return x;
}
// 右旋轉
private Node rotateRight(Node h) {
Node x = h.left;
h.left = x.right;
x.right = h;
x.color = h.color;
x.right.color = RED;
x.N = h.N;
h.N = size(h);
return x;
}
// 顏色變換
// 左右連結串列由紅變黑,父連線變紅
private void flipColors(Node h) {
h.left.color = BLACK;
h.right.color = BLACK;
h.color = RED;
}
private int size(Node node) {
if (node == null) {
return 0;
}
return size(node.left) + size(node.right) + 1;
}
插入演算法:
// 插入演算法
public void put(K key, V value) {
root = put(root, key, value);
root.color = BLACK;
}
private Node put(Node node, K key, V value) {
if (node == null) {
return new Node(key, value, 1, RED);
}
int cmp = node.key.compareTo(key);
if (cmp < 0) {
node.left = put(node.left, key, value);
} else if (cmp > 0){
node.right = put(node.right, key, value);
} else {
node.value = value;
}
if (!isRed(node.left) && isRed(node.right)) node = rotateLeft(node);
if (isRed(node.left) && isRed(node.left.left)) node = rotateRight(node);
if (isRed(node.left) && isRed(node.right)) flipColors(node);
node.N = size(node);
return node;
}