1.红黑树基本概念
不同于链表,二叉搜索树能极大简化元素搜索成本。但是在一些极端情况下,构建的二乘搜索树会退化成链表,时间复杂度为O(n)。因此需要对二叉搜索树进行优化,保证元素搜索时间复杂度为 O ( l o g 2 n ) O(log_2n) O(log2n),典型的包括红黑树和平衡二叉树。
- 平衡二叉树(AVL)树是严格的平衡二叉树,平衡条件定义平衡因子不大于1(所有节点的左右子树高度差不超过1),不管我们是执行插入还是删除操作,只要不满足平衡因子要求,就要通过旋转来保持平衡,而旋转是非常耗时的。所以平衡二叉树(AVL)适合用于插入与删除次数比较少,但查找多的情况。
- 红黑树是对平衡二叉树的优化,有5个性质要求。
- 每个结点有一个颜色标识,要么是红色要么是黑色。
- 根节点必须是黑色。
- 每个叶节点必须是NIL是黑色。
- 一对父亲和孩子节点不能都是红色。即如果一个节点是红色,那它的两个子节点必须是黑色。
- 对于每个结点从该节点出发到叶子节点的路径上包含相同数目的黑色节点。俗称黑高。
红黑树不同于完美的平衡二叉树。从图中可以看出,根节点p的左子树显然比右子树高2,但左子树和右子树的黑色节点层数是相等的。
构建红黑树主要包括旋转和颜色变化操作。
2.数据节点定义
每个结点包含指向左右孩子和父亲的指针。红黑树定义的颜色值。节点储存的值。
public class Node {
int key; // 存储的结点值
Node left, right; // 左右孩子结点
Node parent; // 父节点便于回溯
boolean color; // 红黑树颜色值
public Node(boolean color) {
super();
this.color = color;
}
public Node(int key) {
super();
this.key = key;
}
@Override
public String toString() {
return "Node [key=" + key + ", color=" + color + " p=" + parent.key + "]";
}
}
3. 旋转操作
- 左旋-代码中注意父亲节点的指针变化
public void rotateLeft(Node x) {
Node y = x.right; // 定义y节点为x的右孩子
x.right = y.left;// y的左子树作为x的右子树
if (y.left != nil) {// y存在左子树
y.left.parent = x;// 该左子树的父节点变为x
}
y.parent = x.parent;// x之前的父节点现在成为y的父节点
if (x.parent == nil) {// x是根节点
root = y;// y替代x成为根节点
} else if (x == x.parent.left) {// x是左子节点
x.parent.left = y;// y替代x作为左子节点
} else {// x是右子节点
x.parent.right = y;// y替代x作为右子节点
}
y.left = x;// x作为y的左子节点
x.parent = y;// y作为x的父节点
}
- 右旋
public void rotateRight(Node y) {
Node x = y.left;
y.left = x.right;
if (x.right != null) {// x存在右子树
x.right.parent = y;// x的右子树的父节点变为y
}
x.parent = y.parent;// y之前的父节点现在变为x的父节点
if (y == nil) {// y是根节点
root = x;// x替代y成为根节点
} else if (y == y.parent.left) {// y是左子节点
y.parent.left = x;// x代替y作为其父节点的左子节点
} else {// y是右子节点
y.parent.right = x;// x代替y作为其父节点的右子节点
}
x.right = y;// y变成x的右子节点
y.parent = x;// x变成y的父节点
}
下图所示对18节点进行右旋。
4. 插入和调整
4.1 寻找插入位置
第一步找到插入节点的位置并添加z节点。
public void insert(Node z) { // z代表插入节点。
Node y = nil;// y初始化指向哨兵节点-y代表x的父亲节点。
Node x = root;// x指向根节点
while (x != nil) {
y = x;// y保存更新前的x
if (z.key < x.key) {// z的key较小 然后去x左子树中查找
x = x.left;
} else {// z的key较大 然后去x右子树中查找
x = x.right;
}
}
z.parent = y; // y作为z的父节点
if (y == nil) { // 说明目前树为空 插入的是第一个节点
root = z; // z成为根节点
} else if (z.key < y.key) {
y.left = z; // z的值比父节点值小 作为左子节点
} else {
y.right = z;// z的值比父节点值大 作为右子节点
}
// z是叶子节点
z.left = nil;
z.right = nil;
z.color = RED;// 叶子节点红色
insetFix(z); // 插入之后需要调整树的结构
}
4.2 调整树结构
第二步,插入后,调整树的结构,最终满足红黑树要求。
设z.p代表z的parent。即父亲节点,z.p.p代表爷爷节点。
- 当插入节点Z的父亲节点为黑色或者红黑树为空时,不需要调整树结构,第一步插入操作既可满足红黑树要求。
- 2.插入节点的父亲节点为红色。
- 2.1插入节点的父节点为插入节点的爷爷节点的左孩子。
- 2.1.1 插入节点的叔叔节点为红色。-情况1
将新插入节点的父节点和其叔叔节点设置为黑色,爷爷节点设置为红色。并将爷爷节点设置为当前节点。然后循环去调整当前节点。
- 2.1.2 叔叔节点不存在,分为两种情况。
z节点是左孩子(情况三)还是右孩子(情况二)。
- 2.1.1 插入节点的叔叔节点为红色。-情况1
- 2.1插入节点的父节点为插入节点的爷爷节点的左孩子。
-
3.插入节点的父亲节点为红色。
- 3.1 插入节点的父节点为插入节点的爷爷节点的右孩子。
- 3.1.1 插入节点的叔叔节点存在,且为红色。----对应情况1
- 3.1.2 叔叔节点不存在。----对应情况2和3
和左节点操作对称。
- 3.1 插入节点的父节点为插入节点的爷爷节点的右孩子。
-
代码
public void insetFix(Node z) {
while (z.parent.color == RED) {
if (z.parent == z.parent.parent.left) {// z的父节点是左子节点
Node y = z.parent.parent.right;
if (y != nil && y.color == RED) {// 情况1 父亲和叔父都是红色
z.parent.color = BLACK;
y.color = BLACK; // y的颜色是黑色
z.parent.parent.color = RED;
z = z.parent.parent;
continue; // 递归而上
} else if (z == z.parent.right) {// 情况2
z = z.parent; // 父亲是左节点自己为右节点 先左旋
rotateLeft(z);
}
//情况2经过调整之后可以变成情况3
// 情况3
z.parent.color = BLACK;
z.parent.parent.color = RED;
rotateRight(z.parent.parent);
} else if (z.parent == z.parent.parent.right) {// z的父节点是右子节点
Node y = z.parent.parent.left;
if (y != nil && y.color == RED) {
z.parent.color = BLACK;
y.color = BLACK;
z.parent.parent.color = RED;
z = z.parent.parent;
continue;
} else if (z == z.parent.left) {
z = z.parent;
rotateRight(z);
}
z.parent.color = BLACK;
z.parent.parent.color = RED;
rotateLeft(z.parent.parent);
}
root.color = BLACK;
}
}
4.3删除操作
如果待删除节点x没有左子节点,就用右子节点替代x
如果待删除节点x没有右子节点,就用左子节点替代x
如果x既有左子节点也有右子节点,可以使用x右子树中的最小节点y来替代x, y之前的右子树来替换y, x的右子树作为y新的右子树,最后根据节点的颜色进行树结构的调整。
- 1.结点x的兄弟节点w为红色。
删除x后会造成不平衡需要将B节点左旋,将D设置为根节,删除x。 - 2.x的兄弟节点w是黑色的,并且w的两个子节点也是黑色的。
将兄弟节点变成红色即可。然后循环B节点。 - 3.x的兄弟节点w是黑色的,w的左子节点为红色,右子节点为黑色。
颜色变换后进行右旋。变成下面4这种情况。
- 4.x的兄弟节点w是黑色,w的右子节点为红色。左子节点不考虑。
颜色变换后进行左旋
public void delete(Node z) {
Node y = z;
Node x = null;
boolean originColor = y.color;
if (z.left == nil) {// z没有左子节点
x = z.right;
transplant(z, z.right);// z的右子节点替代z
} else if (z.right == nil) {// z没有右子节点
x = z.left;
transplant(z, z.left);// z的左子节点替代z
} else {// z的左右子节点都存在
y = minimum(z.right);// y保存z右子树中的最小节点 用y替换z
originColor = y.color;
x = y.right;
if (y.parent == z) {// y是z的直接右子节点 不需要else语句中的y的右子树替换y
x.parent = y;
} else {
transplant(y, y.right);// y的右子树替换y
y.right = z.right;// 待删除节点z的右子树成为y的右子树
y.right.parent = y;// 更新y的右子树的的父节点
}
transplant(z, y);// 用y替换z
y.left = z.left;
y.left.parent = y;
y.color = z.color;
}
if (originColor == BLACK) {// 节点y的颜色是黑色 需要调整树的结构
deleteFix(x);
}
}
参考博客
https://www.jianshu.com/p/7dd06862637b
https://blog.csdn.net/maqian5/article/details/99447138
https://blog.csdn.net/qq_43478694/article/details/124918054#t0