Java實現資料結構——紅黑樹
紅黑樹定義
相比二叉查詢樹,紅黑樹中的節點多個顏色屬性。通過顏色屬性,確保了從根節點到每個葉子節點的簡單路徑,沒有一條路徑超過其他路徑2倍,近似於平衡。
性質:
- 每個節點或是紅色,或是黑色
- 根節點是黑色
- 每個葉節點是黑色
- 如果一個節點是紅色,那麼它的兩個子節點都是黑色
- 對於每個節點,從該節點到其所有後代葉節點的簡單路徑上,包含相同數目的黑色節點
Java程式碼實現中,性質3:每個葉節點為黑色,預設無值葉節點指向Null
旋轉
通過旋轉操作,改變樹中節點的指標結構,並且保持二叉查詢樹性質(當前節點大於等於左子樹所有節點,小於右子樹所有節點)。
左旋
將當前節點移動到其左孩子節點的位置,右孩子移動到當前節點的位置
步驟:
- 關聯當前節點c和其右孩子的左孩子
- 關聯當前節點的雙親和右孩子
- 關聯當前節點和右孩子
/**
* 左旋
*
* @param root 根結點
* @param c 當前結點
* @return 根結點
*/
public <E> RBTreeNode<E> rotateLeft(RBTreeNode<E> root, RBTreeNode<E> c) {
RBTreeNode<E> r, cp, rl;
if (c != null && (r = c.right) != null) {
// 1.connect c and rl
if ((rl = c.right = r.left) != null) {
rl.parent = c;
}
// 2.connect r and cp
if ((cp = r.parent = c.parent) == null) {
(root = r).red = false; // done if c is root
} else if (cp.left == c) {
cp.left = r;
} else {
cp.right = r;
}
// 3.connect c and r
r.left = c;
c.parent = r;
}
return root;
}
右旋
將當前節點移動到其右孩子節點的位置,左孩子移動到當前節點的位置
步驟:
- 關聯當前節點和其左孩子的右孩子
- 關聯當前節點的雙親和其左孩子
- 關聯當前節點和其左孩子
/**
* 右旋
*
* @param root 根結點
* @param c 當前結點
* @return root 根結點
*/
public <E> RBTreeNode<E> rotateRight(RBTreeNode<E> root, RBTreeNode<E> c) {
RBTreeNode l, cp, lr;
if (c != null && (l = c.left) != null) {
// 1.connect c and lr
if ((lr = c.left = l.right) != null) {
lr.parent = c;
}
// 2.connect l and cp
if ((cp = l.parent = c.parent) == null) {
(root = l).red = false;
} else if (cp.left == c) {
cp.left = l;
} else {
cp.right = l;
}
// 3.connect c and l
l.right = c;
c.parent = l;
}
return root;
}
插入
查詢樹的插入位置,可參考二叉查詢樹-新增元素
根據紅黑樹的基本性質,新增節點的顏色為紅色更為方便進行操作(黑色的話會破壞性質5)
在插入節點為紅色的前提下,破壞紅黑樹的性質有且僅有下面兩種情況:
- 插入節點為根節點(空樹新增節點)
- 插入節點的父節點為紅色
插入節點x的父節點xp是左孩子
迭代下面操作,直到x或xp為根節點:
1. 如果x的叔父節點u為紅色:將x的祖父節點xpp的黑色屬性賦予給它的兩個孩子,xpp設定為x節點。
2. 如果x的叔父節點u為黑色,且x為右孩子:以xp左旋(由於x和xp都是紅色,不影響黑高),將x設定為xp。此時,x為左孩子。
3. 如果x的叔父節點u為黑色,且x為左孩子:xp和xpp的顏色互換,並且,以xpp做右旋,平衡黑高
演算法導論截圖:
步驟2和步驟3解決的問題:
插入節點x的父節點xp是右孩子
迭代下面操作,直到x或xp為根節點:
1. 如果x的叔父節點u為紅色:將x的祖父節點xpp的黑色屬性賦予給它的兩個孩子,xpp設定為x節點。
2. 如果x的叔父節點u為黑色,且x為左孩子:以xp右旋(由於x和xp都是紅色,不影響黑高),將x設定為xp。此時,x為右孩子。
3. 如果x的叔父節點u為黑色,且x為右孩子:xp和xpp的顏色互換,並且,以xpp做左旋,平衡黑高
Java程式碼實現
@Override
public boolean insert(E e) {
// 1、關聯插入位置
RBTreeNode<E> newNode = createRBTreeNode(e);
RBTreeNode<E> parent = null; // 插入元素的父結點
if (root == null) {
root = newNode;
root.red = false;
} else {
RBTreeNode<E> current = root;
while (current != null) {
if (e.compareTo(current.e) < 0) {
parent = current;
current = current.left;
} else if (e.compareTo(current.e) > 0) {
parent = current;
current = current.right;
} else {
return false;
}
}
if (e.compareTo(parent.e) < 0) {
parent.left = newNode;
} else {
parent.right = newNode;
}
}
newNode.parent = parent;
// 2、保持紅黑樹性質
root = this.balanceInsertion(root, newNode);
size++;
return true;
}
/**
* 平衡插入後的樹
*
* @param root 根結點
* @param x 插入結點
*/
public <E> RBTreeNode<E> balanceInsertion(RBTreeNode<E> root, RBTreeNode<E> x) {
// 1.遍歷結點必為紅結點
x.red = true;
for (RBTreeNode<E> xp, xpp, xppl, xppr; ; ) {
// 2-1.空樹
if ((xp = x.parent) == null) {
x.red = false;
return x;
}
// 2-2.xp為黑結點 || xp為根結點
else if (!xp.red || (xpp = xp.parent) == null) {
return root;
}
// 2-3-1.xp is left-child
// case1: a -> b
if (xp == (xppl = xpp.left)) {
// 2-3-1-1.x uncle is red
if ((xppr = xpp.right) != null && xppr.red) {
xppr.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
// 2-3-1-2.x uncle is black
else {
// x is right-child
// case2: b -> c
if (x == xp.right) {
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
// x is left-child
// case3: c -> d
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateRight(root, xpp);
}
}
}
}
// 2-3-2.xp is right-child
else {
// 2-3-2-1.x uncle is red
if ((xppl = xpp.left) != null && xppl.red) {
xppl.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
// 2-3-2-2.x uncle is black
else {
// x is left-child
if (x == xp.left) {
root = rotateRight(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
// x is right-child
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateLeft(root, xpp);
}
}
}
}
}
}
總結
當插入節點的叔父節點為黑色的時候,x和xp轉換為同側,即:(xpp.left=xp & xp.left=x)或(xpp.right=xp & xp.right = x)
GitHub檢視原始碼