紅黑樹是一種自平衡二叉查找樹,常用于鍵值對存儲,例如Java的TreeMap中就采用紅黑樹實現。它可以在O(logN)時間內查找、插入和刪除
紅黑樹定義與性質
紅黑樹定義
點是紅色或者黑色
根節點是黑色
所有葉子節點是黑色(null節點)
每個紅色節點都必須要有兩個黑色子節點
從任一節點到葉子節點都包含同樣數目的黑色節點
紅黑樹性質
根據紅黑樹的定義,從根到葉子的最長路徑不多于最短的兩倍。由于性質4
每個紅色節點均有兩個黑色節點,性質5
限制了黑色節點的數目,這樣可以限制最短的路徑為全是黑色節點的,而最長的路徑為紅黑節點交替的路徑
TreeMap中紅黑樹
紅黑樹仍是一個二叉排序樹
,如果是二叉排序樹那么
若左子樹不空,則左子樹上的值均小于它的根節點
若右子樹不空,則右子樹上的值均大于它的根節點
它的左右字數也分別為排序二叉樹
那么,對一棵二叉排序樹進行中序遍歷就可以得到排序后的結果
中序排序后為 1,2,3,4,5,7
樹的旋轉
紅黑樹的旋轉可以保持節點符合排序的規則,但是不一定能使其滿足紅黑樹的紅黑顏色規則,需要對其進行修復。其操作分為左旋
,右旋
- 左旋
操作在旋轉節點的右子樹,將待旋轉節點的右節點上升到父節點的位置上
private void rotateLeft(RedBlackNode<T> node) {
if (node != null) {
RedBlackNode<T> rChild = node.right;
node.right = rChild.left;
if (rChild.left != null)
rChild.left.parent = node;
rChild.parent = node.parent;
if (node.parent == null)
root = rChild;
else if (node.parent.left == node)
node.parent.left = rChild;
else
node.parent.right = rChild;
rChild.left = node;
node.parent = rChild;
}
}
- 右旋
操作在旋轉節點的左子樹,將待旋轉節點的左節點上升到父節點的位置上
private void rotateRight(RedBlackNode<T> node) {
if (node != null) {
RedBlackNode<T> lChild = node.left;
node.left = lChild.right;
if (lChild.right != null)
lChild.parent = node;
lChild.parent = node.parent;
if (node.parent == null)
root = lChild;
else if (node.parent.left == node)
node.parent.left = lChild;
else
node.parent.right = lChild;
lChild.right = node;
node.parent = lChild;
}
}
紅黑樹的插入
紅黑樹的插入,首先確認插入的位置
private T insert(final RedBlackNode<T> newNode) {
RedBlackNode<T> t = root;
if (t == null) { // 如果為空樹
root = newNode;
size = 1;
return newNode.key;
}
RedBlackNode<T> parent;
int cmp;
do {
parent = t;
cmp = compare(newNode, t);
if (cmp < 0) {
t = t.left;
} else if (cmp > 0) {
t = t.right;
} else {
return t.key;
}
} while (t != null); // 查找到合適的位置
newNode.parent = parent;
if (cmp < 0) {
parent.left = newNode;
} else {
parent.right = newNode;
}
fixInsertion(newNode);
size++;
return null;
}
紅黑樹插入修復
插入之后需要對樹進行修復,使其滿足紅黑樹的性質
其中,
如果插入的是根節點,將新加入節點涂黑,對樹沒有影響,直接插入
如果插入的節點的父節點為黑色節點,對樹沒有影響
但是,當遇到
- 當前節點的父節點是紅色并且叔叔節點是紅色
插入新節點為顏色為紅色,不符合紅黑樹定義。需要進行調整
假如叔叔節點為祖父節點的右孩子。那么插入修復需要將當前節點的父親節點和叔叔節點的顏色改為黑色,并將祖父節點變更為紅色,當前節點指向祖父節點,繼續進行修復
- 當前節點的父節點為紅色,叔叔節點為黑色,當前節點為父節點的右節點
同樣不符合紅黑樹定義
如果當前節點的父節點為祖父節點的左孩子,則將當前節點指向當前節點的父節點,對新當前節點左旋
情況2修復完成之后,需要繼續進行情況3的修復
- 當前節點的父節點為紅色,叔叔節點為黑色,當前節點為父節點左節點
如果當前節點的父節點是祖父節點的左孩子,則將父節點變為黑色,祖父節點變為紅色,并且以祖父節點為支點右旋
private void fixInsertion(RedBlackNode<T> node) {
node.color = RED;
while (node != null && node != root && colorOf(parentOf(node)) == RED {
if (parentOf(node) == leftOf(ancestorOf(node))) {
// 當前的父節點是祖父節點的左孩子
RedBlackNode<T> uncle = rightOf(ancestorOf(node));
if (colorOf(uncle) == RED) {
setColor(parentOf(node), BLACK);
setColor(uncle, BLACK);
setColor(ancestorOf(node), RED);
node = ancestorOf(node);
} else {
if (node == rightOf(parentOf(node))) {
node = parentOf(node);
rotateLeft(node);
}
setColor(parentOf(node), BLACK);
setColor(ancestorOf(node), RED);
rotateRight(ancestorOf(node));
}
} else {
RedBlackNode<T> uncle = leftOf(ancestorOf(node));
if (colorOf(uncle) == RED) {
setColor(parentOf(node), BLACK);
setColor(uncle, BLACK);
setColor(ancestorOf(node), RED);
node = ancestorOf(node);
} else {
if (node == leftOf(parentOf(node))) {
node = parentOf(node);
rotateRight(node);
}
setColor(parentOf(node), BLACK);
setColor(ancestorOf(node), RED);
rotateLeft(ancestorOf(node));
}
}
}
root.color = BLACK;
}
插入的修復過程是不斷向走向根節點的,然后把整棵樹修復
紅黑樹的刪除
刪除紅黑樹中的節點之后,需要對樹進行維護使得紅黑樹仍符合紅黑樹的定義
如果被刪除的節點是葉結點,沒有孩子,直接從其父節點中刪除此節點即可
如果只有一個孩子,則直接將父節點的對應的孩子指向這個孩子即可
如果有兩個孩子,情況會復雜點。首先需要保證符合排序二叉樹的性質。刪除此結點之后,可以選擇其左子樹的最大結點或者右子樹的最小結點替換。TreeMap中是尋找了被刪除的結點的中序遍歷的后繼結點,也就是選擇了右子樹的最小結點來替換這個結點。
private void deleteNode(RedBlackNode<T> node) {
size--;
if (node.left != null && node.right != null) {
// 中序后繼節點替代
RedBlackNode<T> next = successor(node);
// 注意此時替換了key值
node.key = next.key;
// 僅僅替換了引用
node = next;
}
// 對替換的結點 與 被刪除的結點進行替換
RedBlackNode<T> replacement = (node.left != null ? node.left : node.right);
// 對替換的結點的孩子進行連接,并脫離被替換節點
if (replacement != null) {
replacement.parent = node.parent;
if (node.parent == null)
root = replacement;
else if (node == node.parent.left)
node.parent.left = replacement;
else node.parent.right = replacement;
node.left = node.right = node.parent = null;
if (node.color == BLACK)
fixDeletion(replacement);
} else if (node.parent == null) {
// 當前刪除的節點是根節點
root = null;
} else {
if (node.color == BLACK)
fixDeletion(node);
if (node.parent != null) {
if (node == node.parent.left)
node.parent.left = null;
else if (node == node.parent.right)
node.parent.right = null;
node.parent = null;
}
}
}
紅黑樹刪除修復
如果被刪除的結點是紅色,則不需要做任何修復即可
如果被是刪除結點是黑色,則有可能會造成紅黑樹性質被破壞
- 如果刪除的黑色不是紅黑樹的唯一結點,那么從被刪除結點的分支的黑色結點數必然會不正確,性質5被破壞
- 如果被刪除結點的唯一非空子結點是紅色,且父結點也是紅色,違反性質4
- 如果被刪除結點為根節點,且其替換結點為紅色,違反性質2
針對以上的情況,分以下解決(討論當前結點為父結點左孩子)
"下面我們用一個分析技巧:我們從被刪結點后來頂替它的那個結點開始調整,并認為它有額外的一重黑色。這里額外一重黑色是什么意思呢,我們不是把紅黑樹的結點加上除紅與黑的另一種顏色,這里只是一種假設,我們認為我們當前指向它,因此空有額外一種黑色,可以認為它的黑色是從它的父結點被刪除后繼承給它的,它現在可以容納兩種顏色,如果它原來是紅色,那么現在是紅+黑,如果原來是黑色,那么它現在的顏色是黑+黑。有了這重額外的黑色,原紅黑樹性質5就能保持不變?,F在只要恢復其它性質就可以了,做法還是盡量向根移動和窮舉所有可能性。"--saturnman
- 當前結點(被替換的結點)為黑色+黑色,且兄弟結點為紅色
此時父結點為黑色,并且兄弟的孩子結點也都為黑色,那么把父結點染紅,兄弟結點染黑,之后以父結點為點左旋,更新下兄弟節點。這樣轉化問題為兄弟節點為黑色的問題。這時替換結點上仍有一重黑色,繼續進入算法
if (colorOf(sibling) == RED) {
setColor(sibling, BLACK);
setColor(parentOf(node), RED);
rotateLeft(parentOf(node));
sibling = rightOf(parentOf(node));
}
- 當前為黑+黑,兄弟節點為黑色,且兄弟節點的孩子也為黑色
這時候需要將當前結點和兄弟節點中剝離出來一層黑色給他們的父結點。那么當前為黑+黑,因此還是黑色,而兄弟結點只有一層黑色,因此變為紅色,如果父結點為紅色,則算法結束,否則繼續以父結點為當前結點繼續進行算法
if (colorOf(leftOf(sibling)) == BLACK &&
colorOf(rightOf(sibling)) == BLACK) {
setColor(sibling, RED);
node = parentOf(node);
}
- 當前為黑+黑,兄弟結點為黑色,且兄弟結點的左孩子為紅色,右孩子為黑色
這種情況需要轉化為情況4,因此將兄弟結點染紅,兄弟的左子染黑,之后右旋,更新兄弟結點
if (colorOf(rightOf(sibling)) == BLACK) {
setColor(leftOf(sibling), RED);
setColor(sibling, RED);
rotateRight(sibling);
sibling = rightOf(parentOf(node));
}
- 當前結點為黑+黑,兄弟結點為黑色,且兄弟結點的右孩子為紅色,左孩子為任意顏色
將兄弟結點染成父結點顏色,父結點染成黑色,兄弟結點右孩子染黑,以父結點為支點左旋。當前結點設為根節點,調整結束
setColor(sibling, colorOf(parentOf(node)));
setColor(parentOf(node), BLACK);
setColor(rightOf(sibling), BLACK);
rotateLeft(parentOf(node));
node = root;
其實這個修復刪除的過程就是調整替代結點的多加的這層黑色,使其能夠補償到被刪除的黑色結點,這樣就可以在保持紅黑樹的性質。
參考
實現
package me.learn.datestruct;
import java.util.LinkedList;
import java.util.Queue;
public class RedBlackTree<T extends Comparable<T>> {
private transient RedBlackNode<T> root;
private transient int size = 0;
public void add(T key) {
RedBlackNode<T> node = new RedBlackNode<>(key);
insert(node);
}
public void remove(T key) {
RedBlackNode<T> node = getNode(key);
if (node == null)
return;
deleteNode(node);
}
private void deleteNode(RedBlackNode<T> node) {
size--;
if (node.left != null && node.right != null) {
// 中序后繼節點替代
RedBlackNode<T> next = successor(node);
node.key = next.key;
node = next;
}
RedBlackNode<T> replacement = (node.left != null ? node.left : node.right);
if (replacement != null) {
replacement.parent = node.parent;
if (node.parent == null)
root = replacement;
else if (node == node.parent.left)
node.parent.left = replacement;
else node.parent.right = replacement;
node.left = node.right = node.parent = null;
if (node.color == BLACK)
fixDeletion(replacement);
} else if (node.parent == null) {
// 當前刪除的節點是根節點
root = null;
} else {
if (node.color == BLACK)
fixDeletion(node);
if (node.parent != null) {
if (node == node.parent.left)
node.parent.left = null;
else if (node == node.parent.right)
node.parent.right = null;
node.parent = null;
}
}
}
private void fixDeletion(RedBlackNode<T> node) {
while (node != root && colorOf(node) == BLACK) {
if (node == leftOf(parentOf(node))) {
RedBlackNode<T> sibling = rightOf(parentOf(node));
if (colorOf(sibling) == RED) {
setColor(sibling, BLACK);
setColor(parentOf(node), RED);
rotateLeft(parentOf(node));
sibling = rightOf(parentOf(node));
}
if (colorOf(leftOf(sibling)) == BLACK &&
colorOf(rightOf(sibling)) == BLACK) {
setColor(sibling, RED);
node = parentOf(node);
} else {
if (colorOf(rightOf(sibling)) == BLACK) {
setColor(leftOf(sibling), RED);
setColor(sibling, RED);
rotateRight(sibling);
sibling = rightOf(parentOf(node));
}
setColor(sibling, colorOf(parentOf(node)));
setColor(parentOf(node), BLACK);
setColor(rightOf(sibling), BLACK);
rotateLeft(parentOf(node));
node = root;
}
} else {
RedBlackNode<T> sibling = leftOf(parentOf(node));
if (colorOf(sibling) == RED) {
setColor(sibling, BLACK);
setColor(parentOf(node), RED);
rotateRight(parentOf(node));
sibling = leftOf(parentOf(node));
}
if (colorOf(rightOf(sibling)) == BLACK &&
colorOf(leftOf(sibling)) == BLACK) {
setColor(sibling, RED);
node = parentOf(node);
} else {
if (colorOf(leftOf(sibling)) == BLACK) {
setColor(rightOf(sibling), RED);
setColor(sibling, RED);
rotateLeft(sibling);
sibling = leftOf(parentOf(node));
}
setColor(sibling, colorOf(parentOf(node)));
setColor(parentOf(node), BLACK);
setColor(leftOf(sibling), BLACK);
rotateRight(parentOf(node));
node = root;
}
}
}
setColor(node, BLACK);
}
/**
* node 中序的后繼節點
* @param node
* @return
*/
private RedBlackNode<T> successor(RedBlackNode<T> node) {
if (node == null)
return null;
if (node.right != null) {
RedBlackNode<T> t = node.right;
while (t.left != null)
t = t.left;
return t;
} else {
RedBlackNode<T> p = node.parent;
RedBlackNode<T> child = node;
while (p != null && child == p.right) {
child = p;
p = p.parent;
}
return p;
}
}
private RedBlackNode<T> getNode(T key) {
if (key == null)
throw new NullPointerException();
RedBlackNode<T> node = root;
while (node != null) {
int cmp = key.compareTo(node.key);
if (cmp < 0)
node = node.left;
else if (cmp > 0)
node = node.right;
else
return node;
}
return null;
}
private T insert(final RedBlackNode<T> newNode) {
RedBlackNode<T> t = root;
if (t == null) {
root = newNode;
size = 1;
return newNode.key;
}
RedBlackNode<T> parent;
int cmp;
do {
parent = t;
cmp = compare(newNode, t);
if (cmp < 0) {
t = t.left;
} else if (cmp > 0) {
t = t.right;
} else {
return t.key;
}
} while (t != null);
newNode.parent = parent;
if (cmp < 0) {
parent.left = newNode;
} else {
parent.right = newNode;
}
fixInsertion(newNode);
size++;
return null;
}
private void fixInsertion(RedBlackNode<T> node) {
node.color = RED;
while (node != null && node != root && colorOf(parentOf(node)) == RED) {
if (parentOf(node) == leftOf(ancestorOf(node))) {
// 當前的父節點是祖父節點的左孩子
RedBlackNode<T> uncle = rightOf(ancestorOf(node));
if (colorOf(uncle) == RED) {
setColor(parentOf(node), BLACK);
setColor(uncle, BLACK);
setColor(ancestorOf(node), RED);
node = ancestorOf(node);
} else {
if (node == rightOf(parentOf(node))) {
node = parentOf(node);
rotateLeft(node);
}
setColor(parentOf(node), BLACK);
setColor(ancestorOf(node), RED);
rotateRight(ancestorOf(node));
}
} else {
RedBlackNode<T> uncle = leftOf(ancestorOf(node));
if (colorOf(uncle) == RED) {
setColor(parentOf(node), BLACK);
setColor(uncle, BLACK);
setColor(ancestorOf(node), RED);
node = ancestorOf(node);
} else {
if (node == leftOf(parentOf(node))) {
node = parentOf(node);
rotateRight(node);
}
setColor(parentOf(node), BLACK);
setColor(ancestorOf(node), RED);
rotateLeft(ancestorOf(node));
}
}
}
root.color = BLACK;
}
private void rotateLeft(RedBlackNode<T> node) {
if (node != null) {
RedBlackNode<T> rChild = node.right;
node.right = rChild.left;
if (rChild.left != null)
rChild.left.parent = node;
rChild.parent = node.parent;
if (node.parent == null)
root = rChild;
else if (node.parent.left == node)
node.parent.left = rChild;
else
node.parent.right = rChild;
rChild.left = node;
node.parent = rChild;
}
}
private void rotateRight(RedBlackNode<T> node) {
if (node != null) {
RedBlackNode<T> lChild = node.left;
node.left = lChild.right;
if (lChild.right != null)
lChild.parent = node;
lChild.parent = node.parent;
if (node.parent == null)
root = lChild;
else if (node.parent.left == node)
node.parent.left = lChild;
else
node.parent.right = lChild;
lChild.right = node;
node.parent = lChild;
}
}
private void setColor(RedBlackNode<T> node, boolean color) {
if (node != null) node.color = color;
}
private boolean colorOf(RedBlackNode<T> node) {
return node == null ? BLACK : node.color;
}
private RedBlackNode<T> ancestorOf(RedBlackNode<T> node) {
return parentOf(parentOf(node));
}
private RedBlackNode<T> parentOf(RedBlackNode<T> node) {
return node.parent;
}
private RedBlackNode<T> leftOf(RedBlackNode<T> node) {
return node.left;
}
private RedBlackNode<T> rightOf(RedBlackNode<T> node) {
return node.right;
}
private int compare(RedBlackNode<T> t1, RedBlackNode<T> t2) {
return t1.key.compareTo(t2.key);
}
private static final boolean BLACK = true;
private static final boolean RED = false;
static final class RedBlackNode<T extends Comparable<T>> {
T key;
RedBlackNode<T> left;
RedBlackNode<T> right;
RedBlackNode<T> parent;
boolean color = BLACK;
public RedBlackNode() {
}
public RedBlackNode(T key) {
this.key = key;
}
public RedBlackNode(T key, RedBlackNode<T> parent) {
this.key = key;
this.parent = parent;
}
@Override
public String toString() {
String result = "";
return color == RED && parent != null ?
String.format("[Red][parent[%s]][key[%s]]", parent.key, key)
: color == BLACK && parent != null ?
String.format("[BLK][parent[%s]][key[%s]]", parent.key, key)
: String.format("ROOT[key[%s]]", key);
}
}
private void printTree() {
RedBlackNode<T> rootData = root;
Queue<RedBlackNode<T>> queue = new LinkedList<>();
Queue<RedBlackNode<T>> queue1 = new LinkedList<>();
queue.offer(rootData);
boolean isDefaultQueue = true;
while (!queue.isEmpty() || !queue1.isEmpty()) {
Queue<RedBlackNode<T>> mQueue = isDefaultQueue ? queue : queue1;
RedBlackNode<T> node = mQueue.poll();
if (node != null) {
String nodeInfo = "";
if (node.parent != null) {
if (node == node.parent.left)
nodeInfo = "[LE]";
else
nodeInfo = "[RH]";
}
if (node.left != null) {
(isDefaultQueue ? queue1 : queue).offer(node.left);
}
if (node.right != null) {
(isDefaultQueue ? queue1 : queue).offer(node.right);
}
System.out.print(nodeInfo + node + "\t");
} else {
System.out.println();
isDefaultQueue = !isDefaultQueue;
}
}
}
public static void main(String[] argv) {
RedBlackTree<Integer> tree = new RedBlackTree<>();
tree.add(1);
tree.add(2);
tree.add(3);
tree.add(4);
tree.add(5);
tree.add(10);
tree.printTree();
System.out.println("\n===============================");
tree.remove(4);
tree.printTree();
}
}