概要
红黑树是一颗二叉搜索树,它在每个节点上增加一个存储位来表示节点的颜色,可以是RED或BLACK。
通过对任何一条从根到叶子的简单路径上各个节点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而近视于平衡的。
- 每个节点的颜色是红色或是黑色。
- 根节点的颜色是黑色
- 每个叶子节点(NIL)是黑色
- 如果一个节点是红色的,则它的两个子节点都是黑色的(红色不连续)。
- 对每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
整体架构流程
新增
新创建的节点默认为红色。
x:操作节点,xp:x的父节点 xb:x的兄弟节点 xu:xp的子节点 xg:x的祖父节点
一:x节点的父节点为空
- 则说明x节点已经是根节点。
- 将x节点设置为黑色(因根节点必须为黑色),并返回。
二:xp节点不为空,且xp节点是黑色或是xg为空
- xp节点是黑色,x节点是红色。满足红黑树特性,直接返回即可。
- xg为空,则说明xp是根节点,xp一定为黑色。所以x节点为红色。满足红黑树特性。返回根节点。
xp节点不为空,且xg为空
xp节点不为空,且xp节点是黑色
三:xp是xg的左子节点,x是xp的左子节点,xb不为空,且xb为红色
- 因xp为红且xb为红,且x是红色。不满足红色不联系。
- 解决版本是将xp和xb设置为黑,xg设置为红。因xg设置为红色,可以导致xg节点红色连续。
- 所以需要将x上移到xg。x上移两个层级,x = xg。继续循环
四:xp是xg的左子节点,x是xp的左子节点,xp是红色。
- x节点和xp节点都是红色,导致不连续。所以需要完成对xg的右旋。
- 当x是xp的右子节点,则需要对xp进行左旋,x = xp,已方便xg的右旋。重新获取xg和xp。
- 需要将xp设置为黑色,xg设置为红色,在对xg进行右旋。继续循环。
xp是xg的左子节点,x是xp的左子节点,xp是红色,xu为空。
xp是xg的左子节点,x是xp的左子节点,xp是红色,xu为黑色。从第二个图开始。
五:xp是xg的右子节点,x是xp的右子节点,xb不为空,且xb为红色
- 因xp为红且xb为红,且x是红色。不满足红色不联系。
- 解决版本是将xp和xb设置为黑,xg设置为红。因xg设置为红色,可以导致xg节点红色连续。
- 所以需要将x上移到xg。x上移两个层级,x = xg。继续循环
六:xp是xg的右子节点,xb节点为空或是节点颜色为黑色,且xp是红色
- x节点和xp节点都是红色,导致不连续。所以需要完成对xg的左旋。
- 当x是xp的左子节点,则需要对xp进行右旋,x = xp,已方便xg的左旋。重新获取xg和xp。
- 需要将xp设置为黑色,xg设置为红色,在对xg进行左旋。继续循环。
xp是xg的右子节点,xb节点为空,且xp是红色
xp是xg的右子节点,xb节点为黑色,且xp是红色。从第二个图开始。
删除
- 根据Key查找到删除的节点(remove)。如果为空则直接返回。
- 寻找(remove)节点的替换(replace)节点。
2.1 当remove左右子节点都存在,则(remove)的右子树(removeRight)的最小值(min)。
2.2 将remove节点和min节点互换。
2.3 如果min节点存在右子节点(minRight),则(replace)替换节点设置为minRight。否则replace设置为remove。
2.3 当remove存在右子节点(removeRight)。则(replace)替换节点设置为removeRight。
2.4 当remove存在左子节点(removeLeft)。则(replace)替换节点设置为removeLeft。
2.5 当remove左右节点都不存在。则(replace)替换节点设置为remove。 - 当remove != replace 则,删除remove节点。
- 根据replace调整位置。使其满足红黑树特性。
- 当remove == replace 则,删除remove节点。
其中红色箭头线:表示删除节点。蓝色箭头线:表示替换节点。黄色箭头:表示平衡节点。 紫色箭头:表示选择节点。
x:操作节点,xp:x的父节点 xb:x的兄弟节点 xbs:xb的子节点
一:x节点是根据或是x的父节点为空
1.x节点是根节点则直接返回。
2.x的父节点为空,则x设置为黑色,并返回。
节点为空或是根节点
删除根节点,父节点为空
二:x节点是红色。
1.如果x是删除节点:只需要返回,在删除该节点即可。因是删除节点所以颜色随意。配合步骤2所以设置为黑色。
2.如果x不是删除节点:则xp向x方向的黑色节点比xp向xb方向的黑色节点数量少1。
3.所以需要把x设置为黑色,返回即可。
三:x是其父节点的左子节点,且x是黑色节点,且xb不为空,xb为红色。
- 因b是红色,则说明xb存在两个子节点。且两个子节点的颜色为黑色。xp为黑色。
- 因需要xp填补x,即需要xp进行左旋。重新获取xp和xb等节点。
- 因b有两个子节点,在左旋之后x还有兄弟节点。所以需要把兄弟节点设置为红色,父节点设置为黑色。并返回。
四:x是其父节点的左子节点,且x是黑色节点,且xb不为空,xbs也不为空。
- 因x的兄弟节点,存在一个子节点,且为红色。
- 只需xp填补x即满足红黑树特性,则需要对xp进行右旋。
- 如果xbs的节点是xb的左子节点,则需要先对xb进行右旋,重新获取xb和xbs。已方便xp进行左旋。
- 通过上一步操作,xbs已经是xb的右子节点。需要xb继承xp的颜色(左旋之后xb替代xp的位置),xbs设置为黑色。
- 设置xp为黑色,对xp进行左旋。即可以直接返回。
x是其父节点的左子节点,且x是黑色节点,且xb不为空,xbs也不为空,xbs是xb的右子节点,xbs为红色。
x是其父节点的左子节点,且x是黑色节点,且xb不为空,xbs也不为空,xbs是xb的左子节点,xbs为红色。
五:x是其父节点的左子节点,且x是黑色节点,且xb不为空,xb是黑色节点,xb的两个子节点都为空或是黑。
1.当x是删除节点,则根节点到所有子节点的黑色数量将不一致。
2.当x不是删除节点(即x是上移之后的值),则xp到各个子节点的黑色数量不一致。
3.所以b设置为红色以满足红黑树特性,x上移(直至到根节点)父节点。x = xp。重新循环
4.x上移完成之后, x是红色则执行步骤一。
5.x上移完成之后, 重新获取xb可能 xb为空。则x继续向上借数,x = xp。重新循环
6.x上移完成之后, x是根节点,则执行第二步。
7.x上移完成之后,x是其父节点的左子节点,且x是黑色节点,且xb不为空,xb是黑色节点,xb的两个子节点都为空或是黑。则执行步骤五。
8.x上移完成之后,x是其父节点的右子节点,且x是黑色节点,且xb不为空,xb是黑色节点,xb的两个子节点都为空或是黑。则执行步骤八。
六:x是其父节点的右子节点,且x是黑色节点,且xb不为空,xb为红色。
- 因b是红色,则说明xb存在两个子节点。且两个子节点的颜色为黑色。xp为黑色。
- 因需要xp填补x,即需要xp进行右旋。重新获取xp和b等节点。
- 因b有两个子节点,在右旋之后x还有兄弟节点。所以需要把兄弟节点设置为红色,父节点设置为黑色。并返回。
七:x是其父节点的右子节点,且x是黑色节点,且xb不为空,xbs也不为空。
- 因x的兄弟节点,存在一个子节点,且为红色。
- 只需xp填补x即满足红黑树特性,则需要对xp进行右旋。
- 如果xbs的节点是xb的右子节点,则需要先对xb进行左旋,重新获取xb和xbs。已方便xp进行右旋。
- 通过上一步操作,xbs已经是xb的左子节点。需要xb继承xp的颜色(右旋之后xb替代xp的位置),xbs设置为黑色。
- 设置xp为黑色,对xp进行右旋。即可以直接返回。
x是其父节点的右子节点,且x是黑色节点,且xb不为空,xbs也不为空。xbs是xb的右子节点,xbs是红色:示意图
x是其父节点的右子节点,且x是黑色节点,且xb不为空,xbs也不为空。xbs是xb的左子节点,xbs是红色:示意图
八:x是其父节点的右子节点,且x是黑色节点,且xb不为空,xb是黑色节点,xb的两个子节点都为空或是黑。
- 当x是删除节点,则根节点到所有子节点的黑色数量将不一致。
- 当x不是删除节点(即x是上移之后的值),则xp到各个子节点的黑色数量不一致。
- 所以b设置为红色以满足红黑树特性,x上移(直至到根节点)父节点。x = xp。
- x上移完成之后, x是红色则执行步骤一。
- x上移完成之后, 重新获取xb可能 xb为空。则x继续向上借数,x = xp。
- x上移完成之后, x是根节点,则执行第二步。
- x上移完成之后,x是其父节点的左子节点,且x是黑色节点,且xb不为空,xb是黑色节点,xb的两个子节点都为空或是黑。则执行步骤五。
- x上移完成之后,x是其父节点的右子节点,且x是黑色节点,且xb不为空,xb是黑色节点,xb的两个子节点都为空或是黑。则执行步骤八。
左旋
将需要旋转的节点(C),变成C的右子节点(CR)的左子节点。CR上移一个层级,C下移一个层级。完成C和CR的替换。
右旋
将需要旋转的节点(C),变成C的左子节点(CL)的右子节点。CL上移一个层级,C下移一个层级。
代码实现
package study.algorithm.collection;
public class RedBlackTree<K extends Comparable<K>, V> {
private class Node<K, V> {
public K key;
public V val;
public Node<K, V> left, right, parent;
public boolean red;
public Node(K key, V val, Node<K, V> parent) {
this.key = key;
this.val = val;
this.red = true;
this.parent = parent;
}
}
/**
* 根节点
*/
private Node<K, V> root;
public V get(K key) {
Node<K, V> node = find(root, key);
if (node == null) {
return null;
}
return node.val;
}
private Node<K, V> find(Node<K, V> node, K key) {
if (node == null) {
return null;
}
//判断key和当前节点的key。如果小于则从left找,大于从right找。等于则返回。
int cmp = key.compareTo(node.key);
if (cmp < 0) {
return find(node.left, key);
} else if (cmp > 0) {
return find(node.right, key);
} else {
return node;
}
}
public void put(K key, V val) {
Node<K, V> current = root;
Node<K, V> parent = root;
boolean isRight = true;
while (current != null) {
parent = current;
int cmp = key.compareTo(current.key);
if (cmp < 0) {
current = current.left;
isRight = false;
} else if (cmp > 0) {
current = current.right;
isRight = true;
} else {
current.val = val;
break;
}
}
Node<K, V> node = new Node<>(key, val, parent);
;
if (parent == null) {
root = node;
} else if (current != parent) {
if (isRight) {
parent.right = node;
} else {
parent.left = node;
}
}
root = balanceInsertion(node);
print();
}
public void print() {
if (root != null) {
System.out.println("跟节点的值:" + root.val + " 节点颜色:" + root.red);
}
print(root);
printPre(root);
}
private void print(Node<K, V> node) {
if (node != null) {
print(node.left);
System.out.println("中序:节点值:" + node.val + " 节点颜色:" + node.red);
print(node.right);
}
}
private void printPre(Node<K, V> node) {
if (node != null) {
System.out.println("先序:节点值:" + node.val + " 节点颜色:" + node.red);
printPre(node.left);
printPre(node.right);
}
}
private Node<K, V> balanceInsertion(Node<K, V> x) {
x.red = true;
for (Node<K, V> xp, xpp, xppl, xppr; ; ) {
//父节点为空,则设置x为红色,并返回。
if ((xp = x.parent) == null) {
x.red = false;
return x;
}
//父节点为黑色或祖父节点为空,则返回根节点。
if (!xp.red || (xpp = xp.parent) == null) {
return root;
}
if (xp == (xppl = xpp.left)) {
//三:xp是xg的左子节点,x是xp的左子节点,xb不为空,且xb为红色
//1. 因xp为红且xb为红,且x是红色。不满足红色不联系。
//2. 解决版本是将xp和xb设置为黑,xg设置为红。因xg设置为红色,可以导致xg节点红色连续。
//2. 所以需要将x上移到xg。x上移两个层级,x = xg。继续循环
if ((xppr = xpp.right) != null && xppr.red) {
xppr.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
} else {
//四:xp是xg的左子节点,xb节点为空或是节点颜色是黑色,且xp是红色。
//1. x节点和xp节点都是红色,导致不连续。所以需要完成对xg的右旋。
//2. 当x是xp的右子节点,则需要对xp进行左旋,x = xp,已方便xg的右旋。重新获取xg和xp。
//3. 需要将xp设置为黑色,xg设置为红色,在对xg进行右旋。继续循环。
if (x == xp.right) {
rotateLeft(x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
rotateRight(xpp);
}
}
}
} else {
//五:xp是xg的右子节点,x是xp的右子节点,xb不为空,且xb为红色
//1. 因xp为红且xb为红,且x是红色。不满足红色不联系。
//2. 解决版本是将xp和xb设置为黑,xg设置为红。因xg设置为红色,可以导致xg节点红色连续。
//2. 所以需要将x上移到xg。x上移两个层级,x = xg。继续循环
if (xppl != null && xppl.red) {
xppl.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
} else {
//六:xp是xg的右子节点,xb节点为空或是节点颜色为黑色,且xp是红色
//1. x节点和xp节点都是红色,导致不连续。所以需要完成对xg的左旋。
//2. 当x是xp的左子节点,则需要对xp进行右旋,x = xp,已方便xg的左旋。重新获取xg和xp。
//3. 需要将xp设置为黑色,xg设置为红色,在对xg进行左旋。继续循环。
if (x == xp.left) {
rotateRight(x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
rotateLeft(xpp);
}
}
}
}
}
}
public Node<K, V> remove(K key) {
//1. 查找到需要移除的节点。
Node<K, V> remove = find(root, key);
if (remove == null) {
return null;
}
//2. 删除,查找右子树的最小值。进行值替换。
Node<K, V> removeLeft = remove.left, removeRight = remove.right, replacement;
//2.1 左右子节点都不为空
if (removeLeft != null && removeRight != null) {
Node<K, V> min = removeRight, minLeft;
while ((minLeft = min.left) != null) {
min = minLeft;
}
//交互remove和min的颜色
boolean c = min.red;
min.red = remove.red;
remove.red = c;
//最小值的右子节点。如果最小值存在左子节点说明min不是最小值。
Node<K, V> minRight = min.right;
//删除节点的父节点
Node<K, V> removeParent = remove.parent;
if (min == removeRight) {
remove.parent = min;
min.right = remove;
} else {
Node<K, V> minParent = min.parent;
//设置最小值的父节点。以及最小值是父节点的左子节点还是右子节点。
if ((remove.parent = minParent) != null) {
if (min == minParent.left) {
minParent.left = remove;
} else {
minParent.right = remove;
}
}
//设置最小值的右子节点。
if ((min.right = removeRight) != null) {
removeRight.parent = min;
}
}
remove.left = null;
//设置最小值右子节点的父节点
if ((remove.right = minRight) != null) {
minRight.parent = remove;
}
//替换后最小值的左子节
if ((min.left = removeLeft) != null) {
removeLeft.parent = min;
}
if ((min.parent = removeParent) == null) {
root = min;
} else if (remove == removeParent.left) {
removeParent.left = remove;
} else {
removeParent.right = remove;
}
//如果有右子节点,则替换为设置为minRight.
if (minRight != null) {
replacement = minRight;
} else {
replacement = remove;
}
} else if (removeLeft != null) {
replacement = removeLeft;
} else if (removeRight != null) {
replacement = removeRight;
} else {
replacement = remove;
}
//删除节点
if (replacement != remove) {
Node<K, V> pp = replacement.parent = remove.parent;
if (pp == null) {
root = replacement;
} else if (remove == pp.left) {
pp.left = replacement;
} else {
pp.right = replacement;
}
remove.left = remove.right = remove.parent = null;
}
//3. 调整位置
balanceDeletion(replacement);
//替换节点并删除节点
if (replacement == remove) {
Node<K, V> pp = remove.parent;
remove.parent = null;
if (pp != null) {
if (remove == pp.left) {
pp.left = null;
} else {
pp.right = null;
}
}
}
//替换和删除节点等于根节点,说明删除的只剩跟节点了
if(replacement == root && remove == root){
root = null;
}
print();
return remove;
}
private Node<K, V> balanceDeletion(Node<K, V> x) {
for (Node<K, V> xp, xpl, xpr; ; ) {
//1. 替换节点是空,或是替换节点是跟节点,则直接返回。
if (x == null || x == root)
return root;
//2. x的父节点为空,将当前节点染黑,并返回。
if ((xp = x.parent) == null) {
x.red = false;
return x;
}
//3. x是红色,则说明父满足红黑树特性。将x染黑。
if (x.red) {
x.red = false;
return root;
}
//如果x是父节点的左子节点
if ((xpl = xp.left) == x) {
//因x是黑色,如果xpr不为空,且是红色,则说明xpr的左右子节点都存在且为黑。
if ((xpr = xp.right) != null && xpr.red) {
//将xpr染黑、xp染红,在对xp进行左旋。重新获取xp和xpr。
xpr.red = false;
xp.red = true;
rotateLeft(xp);
xpr = (xp = x.parent) == null ? null : xp.right;
}
//xpr为空,则需要将x上移。
if (xpr == null)
x = xp;
else {
//xpr是黑色,xpr的左右子节点最多子存在一个,且颜色为红。
Node<K, V> sl = xpr.left, sr = xpr.right;
if ((sr == null || !sr.red) && (sl == null || !sl.red)) {
//xpr左右子节点都为空,则需要将xpr染红,x上移到xp。
xpr.red = true;
x = xp;
} else {
//因x的兄弟节点,存在一个子节点,且为红色。
//只需xp填补x即满足红黑树特性,则需要对xp进行左旋。
//如果xus的节点是xu的左子节点,则需要先对xu进行右旋,重新获取xu和xus。已方便xp进行左旋。
//通过上一步操作,xus已经是xu的右子节点。需要xu继承xp的颜色(左旋之后xu替代xp的位置),xus设置为黑色。
//设置xp为黑色,对xp进行左旋。即可以直接返回。
if (sr == null || !sr.red) {
//将xpr进行右旋
rotateRight(xpr);
xpr = xp.right;
}
if (xpr != null) {
//因xpr不为空,则xp不为空。
//设置xpr的颜色为xp的颜色
//如果xpr存在右子节点。则将该节点设置为黑色。
xpr.red = xp.red;
if ((sr = xpr.right) != null)
sr.red = false;
}
//因xp不为空,将xp设置为黑色,并对xp进行左旋。
xp.red = false;
rotateLeft(xp);
return root;
}
}
} else {
//x在xp的右子节点。
if (xpl != null && xpl.red) {
//xpl不为空,且xpl为红色。说明xpl存在左右两个子节点,且为黑色。
//设置xpl为黑色,xp为红色。
//对xp进行右旋
//重新获取xp和xpl
xpl.red = false;
xp.red = true;
rotateRight(xp);
xpl = (xp = x.parent) == null ? null : xp.left;
}
//当xpl为空,则说明需要向上借数
if (xpl == null)
x = xp;
else {
//xpl是黑色,xpl的左右子节点最多子存在一个,且颜色为红。
Node<K, V> sl = xpl.left, sr = xpl.right;
if ((sl == null || !sl.red) && (sr == null || !sr.red)) {
//xpl左右节点都为空,则xpl设置为红色,x上移
xpl.red = true;
x = xp;
} else {
if (sl == null || !sl.red) {
//当sl为空,则sr不为空,且sr为红色
//sr设置为黑色,xpl设置为红色
//对xpl进行左旋
//重新获取xp和xpl
sr.red = false;
xpl.red = true;
rotateLeft(xpl);
xpl = (xp = x.parent) == null ?
null : xp.left;
}
if (xpl != null) {
//当xpl为空,则说明xp不为空
//xpl继承xp的颜色
//xpl的左子节点设置为黑色
xpl.red = xp.red;
if ((sl = xpl.left) != null)
sl.red = false;
}
if (xp != null) {
//因xp不为空,将xp设置为黑色,并对xp进行左旋。
xp.red = false;
rotateRight(xp);
}
x = root;
}
}
}
}
}
private Node<K, V> rotateLeft(Node<K, V> p) {
Node<K, V> r, pp, rl;
if (p != null && (r = p.right) != null) {
if ((rl = p.right = r.left) != null)
rl.parent = p;
if ((pp = r.parent = p.parent) == null)
(root = r).red = false;
else if (pp.left == p)
pp.left = r;
else
pp.right = r;
r.left = p;
p.parent = r;
}
return root;
}
Node<K, V> rotateRight(Node<K, V> p) {
Node<K, V> l, pp, lr;
if (p != null && (l = p.left) != null) {
if ((lr = p.left = l.right) != null)
lr.parent = p;
if ((pp = l.parent = p.parent) == null)
(root = l).red = false;
else if (pp.right == p)
pp.right = l;
else
pp.left = l;
l.right = p;
p.parent = l;
}
return root;
}
}
总结
旋转: 是哪边少往哪边旋转。
新增节点是看:父、叔、祖父节点。
新增节点旋转原理:父红叔空或是父红叔黑。
删除节点是看:父、叔、兄、兄的子节点。
删除节点旋转原理:兄红或是兄不为空切兄的颜色黑色且子节点只有一个子树。