一、概述
balanceInsertion指的是紅黑樹的插入平衡算法,當樹結構中新插入了一個節點後,要對樹進行重新的結構化,以保證該樹始終維持紅黑樹的特性。
關於紅黑樹的特性:
性質1. 節點是紅色或黑色。
性質2. 根節點是黑色。
性質3 每個葉節點(NIL節點,空節點)是黑色的。
性質4 每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)
性質5. 從任一節點到其每個葉子的路徑上包含的黑色節點數量都相同。
二、方法解析
/**
* 紅黑樹插入節點後,需要重新平衡
* root 當前根節點
* x 新插入的節點
* 返回重新平衡後的根節點
*/
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
TreeNode<K,V> x) {
x.red = true; // 新插入的節點標爲紅色
/*
* 這一步即定義了變量,又開起了循環,循環沒有控制條件,只能從內部跳出
* xp:當前節點的父節點、xpp:爺爺節點、xppl:左叔叔節點、xppr:右叔叔節點
*/
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
// 如果父節點爲空、說明當前節點就是根節點,那麼把當前節點標爲黑色,返回當前節點
if ((xp = x.parent) == null) { // L1
x.red = false;
return x;
}
// 父節點不爲空
// 如果父節點爲黑色 或者 【(父節點爲紅色 但是 爺爺節點爲空) -> 這種情況何時出現?】
else if (!xp.red || (xpp = xp.parent) == null) // L2
return root;
if (xp == (xppl = xpp.left)) { // 如果父節點是爺爺節點的左孩子 // L3
if ((xppr = xpp.right) != null && xppr.red) { // 如果右叔叔不爲空 並且 爲紅色 // L3_1
xppr.red = false; // 右叔叔置爲黑色
xp.red = false; // 父節點置爲黑色
xpp.red = true; // 爺爺節點置爲紅色
x = xpp; // 運行到這裏之後,就又會進行下一輪的循環了,將爺爺節點當做處理的起始節點
}
else { // 如果右叔叔爲空 或者 爲黑色 // L3_2
if (x == xp.right) { // 如果當前節點是父節點的右孩子 // L3_2_1
root = rotateLeft(root, x = xp); // 父節點左旋,見下文左旋方法解析
xpp = (xp = x.parent) == null ? null : xp.parent; // 獲取爺爺節點
}
if (xp != null) { // 如果父節點不爲空 // L3_2_2
xp.red = false; // 父節點 置爲黑色
if (xpp != null) { // 爺爺節點不爲空
xpp.red = true; // 爺爺節點置爲 紅色
root = rotateRight(root, xpp); //爺爺節點右旋,見下文右旋方法解析
}
}
}
}
else { // 如果父節點是爺爺節點的右孩子 // L4
if (xppl != null && xppl.red) { // 如果左叔叔是紅色 // L4_1
xppl.red = false; // 左叔叔置爲 黑色
xp.red = false; // 父節點置爲黑色
xpp.red = true; // 爺爺置爲紅色
x = xpp; // 運行到這裏之後,就又會進行下一輪的循環了,將爺爺節點當做處理的起始節點
}
else { // 如果左叔叔爲空或者是黑色 // L4_2
if (x == xp.left) { // 如果當前節點是個左孩子 // L4_2_1
root = rotateRight(root, x = xp); // 針對父節點做右旋,見下文右旋方法解析
xpp = (xp = x.parent) == null ? null : xp.parent; // 獲取爺爺節點
}
if (xp != null) { // 如果父節點不爲空 // L4_2_4
xp.red = false; // 父節點置爲黑色
if (xpp != null) { //如果爺爺節點不爲空
xpp.red = true; // 爺爺節點置爲紅色
root = rotateLeft(root, xpp); // 針對爺爺節點做左旋
}
}
}
}
}
}
/**
* 節點左旋
* root 根節點
* p 要左旋的節點
*/
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
TreeNode<K,V> p) {
TreeNode<K,V> r, pp, rl;
if (p != null && (r = p.right) != null) { // 要左旋的節點以及要左旋的節點的右孩子不爲空
if ((rl = p.right = r.left) != null) // 要左旋的節點的右孩子的左節點 賦給 要左旋的節點的右孩子 節點爲:rl
rl.parent = p; // 設置rl和要左旋的節點的父子關係【之前只是爹認了孩子,孩子還沒有答應,這一步孩子也認了爹】
// 將要左旋的節點的右孩子的父節點 指向 要左旋的節點的父節點,相當於右孩子提升了一層,
// 此時如果父節點爲空, 說明r 已經是頂層節點了,應該作爲root 並且標爲黑色
if ((pp = r.parent = p.parent) == null)
(root = r).red = false;
else if (pp.left == p) // 如果父節點不爲空 並且 要左旋的節點是個左孩子
pp.left = r; // 設置r和父節點的父子關係【之前只是孩子認了爹,爹還沒有答應,這一步爹也認了孩子】
else // 要左旋的節點是個右孩子
pp.right = r;
r.left = p; // 要左旋的節點 作爲 他的右孩子的左節點
p.parent = r; // 要左旋的節點的右孩子 作爲 他的父節點
}
return root; // 返回根節點
}
/**
* 節點右旋
* root 根節點
* p 要右旋的節點
*/
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
TreeNode<K,V> p) {
TreeNode<K,V> l, pp, lr;
if (p != null && (l = p.left) != null) { // 要右旋的節點不爲空以及要右旋的節點的左孩子不爲空
if ((lr = p.left = l.right) != null) // 要右旋的節點的左孩子的右節點 賦給 要右旋節點的左孩子 節點爲:lr
lr.parent = p; // 設置lr和要右旋的節點的父子關係【之前只是爹認了孩子,孩子還沒有答應,這一步孩子也認了爹】
// 將要右旋的節點的左孩子的父節點 指向 要右旋的節點的父節點,相當於左孩子提升了一層,
// 此時如果父節點爲空, 說明l 已經是頂層節點了,應該作爲root 並且標爲黑色
if ((pp = l.parent = p.parent) == null)
(root = l).red = false;
else if (pp.right == p) // 如果父節點不爲空 並且 要右旋的節點是個右孩子
pp.right = l; // 設置l和父節點的父子關係【之前只是孩子認了爹,爹還沒有答應,這一步爹也認了孩子】
else // 要右旋的節點是個左孩子
pp.left = l; // 同上
l.right = p; // 要右旋的節點 作爲 他左孩子的右節點
p.parent = l; // 要右旋的節點的父節點 指向 他的左孩子
}
return root;
}
三、圖例
1、無旋轉
2、有旋轉