红黑树定义
相比二叉查找树,红黑树中的节点多个颜色属性。通过颜色属性,确保了从根节点到每个叶子节点的简单路径,没有一条路径超过其他路径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查看源码