本文主要解析java8中HashMap的紅黑樹源碼部分,如有不懂可以先看我寫的紅黑樹基礎-第一篇和紅黑樹基礎-第二篇
1.左旋
注意:圖中節點標號跟下面代碼中變量名一致,便於理解。
/**
* 紅黑樹節點左旋操作
* @param root 根節點
* @param p 旋轉節點
* @return 新root節點
*/
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) {
//1.把r的左子樹變成p的右子樹,並標記爲rl,即p->right=rl
if ((rl = p.right = r.left) != null)
//2.如果rl不爲空,鏈起來,rl->parent=p
rl.parent = p;
//3.把p的父節點變成r的父節點,並標記爲pp,即r->parent=pp
if ((pp = r.parent = p.parent) == null)
//4.如果pp爲空,那麼之前p是根節點,現在左旋後r變成根節點,root=r,並且根節點爲黑色,r.red=false
(root = r).red = false;
//5.否則pp不爲空,就判斷p是否是pp的左孩子,如果是,旋轉後r就是左孩子
else if (pp.left == p)
pp.left = r;
else
//6.否則就是右孩子
pp.right = r;
//7.最後完善樹的父子關係,鏈起來
//r->left=p
r.left = p;
//p->parent=r
p.parent = r;
}
//最後返回新的root節點
return root;
}
2.右旋
/**
* 紅黑樹節點右旋操作
* @param root 根節點
* @param p 旋轉節點
* @return 新root節點
*/
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) {
//1.把l的右子樹變成p的左子樹,並標記爲lr,即p->left=lr
if ((lr = p.left = l.right) != null)
//2.如果lr不爲空,鏈起來,lr->parent=p
lr.parent = p;
//3.把p的父節點變成l的父節點,並標記爲pp,即l->parent=pp
if ((pp = l.parent = p.parent) == null)
//4.如果pp爲空,那麼之前p是根節點,現在右旋後l變成根節點,root=l,並且根節點爲黑色,l.red=false
(root = l).red = false;
//5.否則pp不爲空,就判斷p是否是pp的右孩子,如果是,旋轉後l就是右孩子
else if (pp.right == p)
pp.right = l;
else
//6.否則就是左孩子
pp.left = l;
//7.最後完善樹的父子關係,鏈起來
//l->right=p
l.right = p;
//p->parent=l
p.parent = l;
}
//最後返回新的root節點
return root;
}
3.平衡插入
講解平衡插入之前,我們直接從源碼入手,如果感覺理解起來有困難,請先看懂我寫的紅黑樹基礎-第一篇
下面約定下面代碼變量的命名規則,x爲邏輯上待插入的節點(有可能是第一次插入,也有可能是自底向上調整時的插入),xp爲x節點的父節點,xpp爲x節點的祖父節點,也是xp的父節點。xppl是xpp的左孩子,xppr是xpp的右孩子。xp是xppl和xppr中的其中之一。
/**
* 紅黑樹節點平衡插入操作
* @param root 根節點
* @param x 插入節點
* @return 新root節點
*/
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
TreeNode<K,V> x) {
//首先插入節點x的顏色初始化爲紅色
x.red = true;
//for循環沒有結束條件,自底向上插入循環處理,直至到root或者紅黑自平衡退出循環
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
//如果xp爲空,說明x已經爲root了
if ((xp = x.parent) == null) {
x.red = false;
return x;
}
//否則判斷父節點xp是否是黑色,或者xp是否爲root
else if (!xp.red || (xpp = xp.parent) == null)
return root;
//判斷父節點xp是否是祖父節點的左孩子
if (xp == (xppl = xpp.left)) {
//叔叔節點存在,並且爲紅節點
if ((xppr = xpp.right) != null && xppr.red) {
//紅黑樹基礎-第一篇,根據情景4.1進行變色
xppr.red = false;
xp.red = false;
xpp.red = true;
//以xpp爲插入節點,進行下一次循環
x = xpp;
}
//否則叔叔節點要麼爲空,要麼爲黑色
else {
//如果插入節點x是父節點xp的右孩子,那麼滿足紅黑樹基礎-第一篇,情景4.2.2
if (x == xp.right) {
//把xp進行左旋,同時把插入節點x賦值爲xp
root = rotateLeft(root, x = xp);
//重新設置xp和xpp
xpp = (xp = x.parent) == null ? null : xp.parent;
}
//進行紅黑樹基礎-第一篇,情景4.2.1的處理
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
//xpp右旋
root = rotateRight(root, xpp);
}
}
}
}
//否則父節點xp是祖父節點的右孩子
else {
//叔叔節點存在,並且爲紅節點
if (xppl != null && xppl.red) {
//紅黑樹基礎-第一篇,根據情景4.1進行變色
xppl.red = false;
xp.red = false;
xpp.red = true;
//以xpp爲插入節點,進行下一次循環
x = xpp;
}
//否則叔叔節點要麼爲空,要麼爲黑色
else {
//如果插入節點x是父節點xp的左孩子,那麼滿足紅黑樹基礎-第一篇,情景4.3.2
if (x == xp.left) {
//把xp進行右旋,同時把插入節點x賦值爲xp
root = rotateRight(root, x = xp);
//重新設置xp和xpp
xpp = (xp = x.parent) == null ? null : xp.parent;
}
//進行紅黑樹基礎-第一篇,情景4.3.1的處理
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
//xpp左旋
root = rotateLeft(root, xpp);
}
}
}
}
}
}
4.平衡刪除
4.1. 先找到替代節點
先看圖解,如果不懂原理請關注我的紅黑樹基礎-第二篇-刪除,或者HashMap源碼解析
這個函數主要是this是要刪除的節點,找到節點s與之互換,然後用balanceDeletion調整,看下圖中樹結構的變化再看代碼註釋
/**
* Removes the given node, that must be present before this call.
* This is messier than typical red-black deletion code because we
* cannot swap the contents of an interior node with a leaf
* successor that is pinned by "next" pointers that are accessible
* independently during traversal. So instead we swap the tree
* linkages. If the current tree appears to have too few nodes,
* the bin is converted back to a plain bin. (The test triggers
* somewhere between 2 and 6 nodes, depending on tree structure).
* @param map 該hashmap
* @param tab hashmap內部Node數組
* @param movable 是否需要移動root到鏈表頭
* @param this 待刪除節點p
*/
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
boolean movable) {
int n;
if (tab == null || (n = tab.length) == 0)
return;
int index = (n - 1) & hash;
//root是根節點
TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
//分別是this=p的鏈表指針的前驅和後繼
TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;
if (pred == null)
//如果要刪除的p是鏈表的頭,那麼first = succ;並且tab[index] = succ;
tab[index] = first = succ;
else
//否則鏈表斷開this的鏈接
pred.next = succ;
if (succ != null)
//鏈表斷開this的鏈接,把鏈表關係完善
succ.prev = pred;
if (first == null)
//空樹
return;
if (root.parent != null)
//獲取到真正的根root
root = root.root();
if (root == null || root.right == null ||
(rl = root.left) == null || rl.left == null) {
//根的左孩子的左孩子爲空,基本上可以判斷只剩2-6個node了,紅黑樹可以變成鏈表了
tab[index] = first.untreeify(map); // too small
return;
}
TreeNode<K,V> p = this, pl = left, pr = right, replacement;
//主要關注這種左右孩子都非空的場景,爲什麼這種這麼複雜,請看我寫的關於紅黑樹基礎的其他博客
if (pl != null && pr != null) {
//請看圖解,我隨便畫了個圖,節點名和變量名一致
TreeNode<K,V> s = pr, sl;
//先找到大於刪除節點p的最小節點,爲什麼這麼做,請看紅黑樹基礎-第3篇
while ((sl = s.left) != null) // find successor
s = sl;
//首先互換p節點和s節點的顏色,因爲最終s要被換到p的位置,p要被換到s的位置
//互換後,在p位置的s因爲是p的顏色,所以不影響紅黑樹的性質
//而換到s位置的p,顏色是原s節點的顏色
boolean c = s.red; s.red = p.red; p.red = c; // swap colors
TreeNode<K,V> sr = s.right;
TreeNode<K,V> pp = p.parent;
if (s == pr) { // p was s's direct parent
//此時s==sp==pr,其實是建立p和s的關係,將else的內容簡化了
p.parent = s;
s.right = p;
}
else {
TreeNode<K,V> sp = s.parent;
//建立p和sp的新父子關係,大家可以畫圖分析
if ((p.parent = sp) != null) {
if (s == sp.left)
sp.left = p;
else
sp.right = p;
}
//建立s和pr的新父子關係
if ((s.right = pr) != null)
pr.parent = s;
}
p.left = null;
//建立p和sr的新父子關係
if ((p.right = sr) != null)
sr.parent = p;
//建立s和pl的新父子關係
if ((s.left = pl) != null)
pl.parent = s;
//建立s和pp的新父子關係
//如果pp爲空,之前p就是根節點,那麼現在s就是根節點了
if ((s.parent = pp) == null)
root = s;
else if (p == pp.left)
//如果原來p是pp的左孩子,互換後s就還是pp左孩子
pp.left = s;
else
pp.right = s;
//如果sr不爲空,則互換後p有右孩子,沒有左孩子,
if (sr != null)
//單鏈接情況直接用孩子替換
replacement = sr;
else
//此時p沒有孩子
replacement = p;
}
//單鏈接情況,單單隻有左孩子
else if (pl != null)
replacement = pl;
//單鏈接情況,單單隻有右孩子
else if (pr != null)
replacement = pr;
else
//p是葉子結點
replacement = p;
//互換後,如果p不是葉子結點,在樹結構中直接把p節點幹掉
if (replacement != p) {
TreeNode<K,V> pp = replacement.parent = p.parent;
if (pp == null)
root = replacement;
else if (p == pp.left)
pp.left = replacement;
else
pp.right = replacement;
p.left = p.right = p.parent = null;
}
//回到紅黑樹的平衡刪除了,如果要刪除的節點是紅色,那麼直接刪除即可
//如果p是黑色的,那麼此時就不滿足紅黑樹的性質了,因爲少了一個黑色節點,那麼要進行balanceDeletion調整
//注意:p的顏色是原來s的顏色
TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);
//那麼如果p是葉子結點,在樹結構中直接把p節點幹掉,detach斷開連接
if (replacement == p) { // detach
TreeNode<K,V> pp = p.parent;
p.parent = null;
if (pp != null) {
if (p == pp.left)
pp.left = null;
else if (p == pp.right)
pp.right = null;
}
}
//是否需要把root放到鏈表head
if (movable)
moveRootToFront(tab, r);
}
4.2 balanceDeletion方法進行平衡調整,原理不懂得請一定先看紅黑樹基礎第二篇,對照圖解
/**
* 平衡刪除調整部分代碼
* 情景?.?請見紅黑樹基礎-第二篇-刪除 這篇文章對應的解析
* @param x 替代節點
* @param root 樹的根節點
* @return 調整後,新的root節點
* 變量規則,x是替代節點,xp是x的父節點,xpl是xp的左孩子,xpr是xp的右孩子
*/
static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,
TreeNode<K,V> x) {
for (TreeNode<K,V> xp, xpl, xpr;;) {
//替代節點已經到達根節點,直接退出函數返回
if (x == null || x == root)
return root;
//如果x的父節點是空,說明x==root了
else if ((xp = x.parent) == null) {
x.red = false;
return x;
}
//如果替代節點是紅色,直接變爲黑色,返回
else if (x.red) {
x.red = false;
return root;
}
//情景2.1
else if ((xpl = xp.left) == x) {
//情景2.1.1
if ((xpr = xp.right) != null && xpr.red) {
xpr.red = false;
xp.red = true;
root = rotateLeft(root, xp);
//此時,x不變,xp和xpr更新
xpr = (xp = x.parent) == null ? null : xp.right;
}
//如果xpr爲空,那麼右子樹沒有紅節點借,找父母的兄弟去借吧,自底向上處理
if (xpr == null)
x = xp;
//否則,情景2.1.2,xpr是黑色
else {
TreeNode<K,V> sl = xpr.left, sr = xpr.right;
//如果xpr的左右孩子都是黑色,情景2.1.2.3
if ((sr == null || !sr.red) &&
(sl == null || !sl.red)) {
//找父母的兄弟去處理,自底向上處理
xpr.red = true;
x = xp;
}
else {
//否則,左子樹紅色,右子樹黑色
if (sr == null || !sr.red) {
//情景2.1.2.2
if (sl != null)
sl.red = false;
xpr.red = true;
root = rotateRight(root, xpr);
//更新xpr和xp
xpr = (xp = x.parent) == null ?
null : xp.right;
}
//情景2.1.2.1
if (xpr != null) {
xpr.red = (xp == null) ? false : xp.red;
if ((sr = xpr.right) != null)
sr.red = false;
}
//情景2.1.2.1
if (xp != null) {
xp.red = false;
root = rotateLeft(root, xp);
}
x = root;
}
}
}
//情景2.2
else { // symmetric
//情景2.2.1
if (xpl != null && xpl.red) {
xpl.red = false;
xp.red = true;
root = rotateRight(root, xp);
//此時,x不變,xp和xpl更新
xpl = (xp = x.parent) == null ? null : xp.left;
}
//如果xpl爲空,那麼左子樹沒有紅節點借,找父母的兄弟去借吧,自底向上處理
if (xpl == null)
x = xp;
//否則,情景2.2.2,xpl是黑色
else {
//如果xpl的左右孩子都是黑色,情景2.2.2.3
TreeNode<K,V> sl = xpl.left, sr = xpl.right;
if ((sl == null || !sl.red) &&
(sr == null || !sr.red)) {
//找父母的兄弟去處理,自底向上處理
xpl.red = true;
x = xp;
}
else {
//否則,左子樹黑色,右子樹紅色
if (sl == null || !sl.red) {
//情景2.2.2.2
if (sr != null)
sr.red = false;
xpl.red = true;
root = rotateLeft(root, xpl);
//更新xpr和xp
xpl = (xp = x.parent) == null ?
null : xp.left;
}
//情景2.2.2.1
if (xpl != null) {
xpl.red = (xp == null) ? false : xp.red;
if ((sl = xpl.left) != null)
sl.red = false;
}
//情景2.2.2.1
if (xp != null) {
xp.red = false;
root = rotateRight(root, xp);
}
x = root;
}
}
}
}
}
紅黑樹的源碼比較複雜,如有不懂請通過紅黑樹基礎第1篇,第2篇深入理解後再看。