什么是红黑树?
红黑树是一种特殊的二叉查找树,它的每个节点都有一个颜色属性(红色或黑色)。这种数据结构由两位计算机科学家Leland和Tarjan于1978年发明,主要用于提高二叉查找树的操作效率。
红黑树的性质
- 节点颜色: 每个节点要么是红色,要么是黑色。
- 根节点是黑的: 根节点总是涂成黑色。
- 叶节点是黑的:所有的叶子节点(NIL节点,空节点)都是黑色的。
- 红色节点的子节点必须是黑色的: 如果一个节点是红色的,那么它的两个子节点都是黑色的。
- 从任一节点到其后代的所有路径包含相同数目的黑节点: 这保证了树的左右两边保持平衡。
红黑树的操作
插入
插入操作遵循以下步骤:
按照二叉查找树的方式插入新节点,并将新节点着色为红色。
如果新插入的节点破坏了红黑树的性质,则通过旋转和重新着色将其修复。
package org.zyf.javabasic.rbtest;
/**
* @program: zyfboot-javabasic
* @description:
* @author: zhangyanfeng
* @create: 2023-08-19 13:10
**/
public class RedBlackTreeDelete {
private TreeNode root;
enum Color {
RED, BLACK
}
class TreeNode {
int key;
Color color;
TreeNode left;
TreeNode right;
TreeNode parent;
public TreeNode(int key, Color color) {
this.key = key;
this.color = color;
this.left = null;
this.right = null;
this.parent = null;
}
}
// 省略其他方法...
public void delete(int key) {
TreeNode nodeToDelete = findNodeToDelete(root, key);
if (nodeToDelete != null) {
deleteNode(nodeToDelete);
}
}
private void deleteNode(TreeNode nodeToDelete) {
TreeNode replacementNode = (nodeToDelete.left != null && nodeToDelete.right != null)
? findSuccessor(nodeToDelete.right)
: nodeToDelete;
TreeNode childNode = (replacementNode.left != null) ? replacementNode.left : replacementNode.right;
if (childNode != null) {
childNode.parent = replacementNode.parent;
}
if (replacementNode.parent == null) {
root = childNode;
} else if (replacementNode == replacementNode.parent.left) {
replacementNode.parent.left = childNode;
} else {
replacementNode.parent.right = childNode;
}
if (replacementNode != nodeToDelete) {
nodeToDelete.key = replacementNode.key; // 将替代节点的值赋给被删除节点
}
if (replacementNode.color == Color.BLACK) {
deleteFixup(childNode, replacementNode.parent); // 进行颜色调整和可能的旋转操作
}
}
// 寻找要删除的节点
private TreeNode findNodeToDelete(TreeNode current, int key) {
if (current == null || current.key == key) {
return current;
}
if (key < current.key) {
return findNodeToDelete(current.left, key);
}
return findNodeToDelete(current.right, key);
}
// 寻找后继节点
private TreeNode findSuccessor(TreeNode node) {
TreeNode current = node;
while (current.left != null) {
current = current.left;
}
return current;
}
private void deleteFixup(TreeNode x, TreeNode xParent) {
while (x != root && (x == null || x.color == Color.BLACK)) {
if (x == xParent.left) {
TreeNode sibling = xParent.right;
if (sibling.color == Color.RED) {
// 情况 1:兄弟节点为红色
sibling.color = Color.BLACK;
xParent.color = Color.RED;
leftRotate(xParent);
sibling = xParent.right;
}
if ((sibling.left == null || sibling.left.color == Color.BLACK) &&
(sibling.right == null || sibling.right.color == Color.BLACK)) {
// 情况 2:兄弟节点和其子节点都为黑色
sibling.color = Color.RED;
x = xParent;
xParent = xParent.parent;
} else {
if (sibling.right == null || sibling.right.color == Color.BLACK) {
// 情况 3:兄弟节点是黑色,且兄弟节点的左子节点是红色
if (sibling.left != null) {
sibling.left.color = Color.BLACK;
}
sibling.color = Color.RED;
rightRotate(sibling);
sibling = xParent.right;
}
// 情况 4:兄弟节点是黑色,且兄弟节点的右子节点是红色
sibling.color = xParent.color;
xParent.color = Color.BLACK;
if (sibling.right != null) {
sibling.right.color = Color.BLACK;
}
leftRotate(xParent);
x = root; // 完成调整,退出循环
}
} else {
// 同上,但是左右对调
}
}
if (x != null) {
x.color = Color.BLACK; // 将 x 染黑
}
}
private void leftRotate(TreeNode x) {
TreeNode y = x.right;
x.right = y.left;
if (y.left != null) {
y.left.parent = x;
}
y.parent = x.parent;
if (x.parent == null) {
root = y;
} else if (x == x.parent.left) {
x.parent.left = y;
} else {
x.parent.right = y;
}
y.left = x;
x.parent = y;
}
private void rightRotate(TreeNode y) {
TreeNode x = y.left;
y.left = x.right;
if (x.right != null) {
x.right.parent = y;
}
x.parent = y.parent;
if (y.parent == null) {
root = x;
} else if (y == y.parent.left) {
y.parent.left = x;
} else {
y.parent.right = x;
}
x.right = y;
y.parent = x;
}
}
删除
删除操作稍微复杂一些,因为需要处理被删除节点的三种情况:
被删除的节点是叶子节点(没有子节点)。
被删除的节点有一个子节点。
被删除的节点有两个子节点。
删除后,可能需要进行一系列的旋转和重新着色来维持红黑树的性质。
package org.zyf.javabasic.rbtest;
/**
* @program: zyfboot-javabasic
* @description:
* @author: zhangyanfeng
* @create: 2023-08-19 13:10
**/
public class RedBlackTreeDelete {
private TreeNode root;
enum Color {
RED, BLACK
}
class TreeNode {
int key;
Color color;
TreeNode left;
TreeNode right;
TreeNode parent;
public TreeNode(int key, Color color) {
this.key = key;
this.color = color;
this.left = null;
this.right = null;
this.parent = null;
}
}
// 省略其他方法...
public void delete(int key) {
TreeNode nodeToDelete = findNodeToDelete(root, key);
if (nodeToDelete != null) {
deleteNode(nodeToDelete);
}
}
private void deleteNode(TreeNode nodeToDelete) {
TreeNode replacementNode = (nodeToDelete.left != null && nodeToDelete.right != null)
? findSuccessor(nodeToDelete.right)
: nodeToDelete;
TreeNode childNode = (replacementNode.left != null) ? replacementNode.left : replacementNode.right;
if (childNode != null) {
childNode.parent = replacementNode.parent;
}
if (replacementNode.parent == null) {
root = childNode;
} else if (replacementNode == replacementNode.parent.left) {
replacementNode.parent.left = childNode;
} else {
replacementNode.parent.right = childNode;
}
if (replacementNode != nodeToDelete) {
nodeToDelete.key = replacementNode.key; // 将替代节点的值赋给被删除节点
}
if (replacementNode.color == Color.BLACK) {
deleteFixup(childNode, replacementNode.parent); // 进行颜色调整和可能的旋转操作
}
}
// 寻找要删除的节点
private TreeNode findNodeToDelete(TreeNode current, int key) {
if (current == null || current.key == key) {
return current;
}
if (key < current.key) {
return findNodeToDelete(current.left, key);
}
return findNodeToDelete(current.right, key);
}
// 寻找后继节点
private TreeNode findSuccessor(TreeNode node) {
TreeNode current = node;
while (current.left != null) {
current = current.left;
}
return current;
}
private void deleteFixup(TreeNode x, TreeNode xParent) {
while (x != root && (x == null || x.color == Color.BLACK)) {
if (x == xParent.left) {
TreeNode sibling = xParent.right;
if (sibling.color == Color.RED) {
// 情况 1:兄弟节点为红色
sibling.color = Color.BLACK;
xParent.color = Color.RED;
leftRotate(xParent);
sibling = xParent.right;
}
if ((sibling.left == null || sibling.left.color == Color.BLACK) &&
(sibling.right == null || sibling.right.color == Color.BLACK)) {
// 情况 2:兄弟节点和其子节点都为黑色
sibling.color = Color.RED;
x = xParent;
xParent = xParent.parent;
} else {
if (sibling.right == null || sibling.right.color == Color.BLACK) {
// 情况 3:兄弟节点是黑色,且兄弟节点的左子节点是红色
if (sibling.left != null) {
sibling.left.color = Color.BLACK;
}
sibling.color = Color.RED;
rightRotate(sibling);
sibling = xParent.right;
}
// 情况 4:兄弟节点是黑色,且兄弟节点的右子节点是红色
sibling.color = xParent.color;
xParent.color = Color.BLACK;
if (sibling.right != null) {
sibling.right.color = Color.BLACK;
}
leftRotate(xParent);
x = root; // 完成调整,退出循环
}
} else {
// 同上,但是左右对调
}
}
if (x != null) {
x.color = Color.BLACK; // 将 x 染黑
}
}
private void leftRotate(TreeNode x) {
TreeNode y = x.right;
x.right = y.left;
if (y.left != null) {
y.left.parent = x;
}
y.parent = x.parent;
if (x.parent == null) {
root = y;
} else if (x == x.parent.left) {
x.parent.left = y;
} else {
x.parent.right = y;
}
y.left = x;
x.parent = y;
}
private void rightRotate(TreeNode y) {
TreeNode x = y.left;
y.left = x.right;
if (x.right != null) {
x.right.parent = y;
}
x.parent = y.parent;
if (y.parent == null) {
root = x;
} else if (y == y.parent.left) {
y.parent.left = x;
} else {
y.parent.right = x;
}
x.right = y;
y.parent = x;
}
}
旋转
红黑树中的左旋和右旋是用于维护树的平衡性的旋转操作。
左旋
当需要解决一个结点因为其子节点过大而违反红黑树的性质时,可以通过左旋来调整。左旋通常发生在当前节点是红色,它的叔叔节点(即父节点的兄弟节点)是黑色,并且当前节点位于其父节点的右侧时。在左旋操作中,会将当前节点的父节点作为旋转的中心,使得当前节点上升为其祖父节点的左孩子,同时原父节点变为当前节点的右孩子。
右旋
与左旋相对,右旋是在当前节点位于其父节点的左侧时进行的旋转操作。右旋的目的是让当前节点上升为其祖父节点的右孩子,同时原父节点变为当前节点的左孩子。
package org.zyf.javabasic.rbtest;
/**
* @program: zyfboot-javabasic
* @description:
* @author: zhangyanfeng
* @create: 2023-08-19 12:38
**/
public class RedBlackTree {
private TreeNode root;
public RedBlackTree() {
root = null;
}
// 左旋操作
private void leftRotate(TreeNode x) {
TreeNode y = x.right; // y 为 x 的右子节点
x.right = y.left; // 将 y 的左子节点设置为 x 的右子节点
if (y.left != null) {
y.left.parent = x;
}
y.parent = x.parent; // 更新 y 的父节点
if (x.parent == null) {
root = y;
} else if (x == x.parent.left) {
x.parent.left = y;
} else {
x.parent.right = y;
}
y.left = x; // 将 x 设为 y 的左子节点
x.parent = y;
}
// 右旋操作
private void rightRotate(TreeNode y) {
TreeNode x = y.left; // x 为 y 的左子节点
y.left = x.right; // 将 x 的右子节点设置为 y 的左子节点
if (x.right != null) {
x.right.parent = y;
}
x.parent = y.parent; // 更新 x 的父节点
if (y.parent == null) {
root = x;
} else if (y == y.parent.left) {
y.parent.left = x;
} else {
y.parent.right = x;
}
x.right = y; // 将 y 设为 x 的右子节点
y.parent = x;
}
}
应用场景
红黑树广泛应用于各种需要快速插入、删除和查找操作的场景,例如:
- 数据库索引
- 关联数组
- 动态集合
- 有序映射。