文章目录
在本教程中,您将了解什么是红黑树。此外,您还可以找到C语言的示例。
红黑树是一种自平衡的二叉搜索树,其中每个节点都包含一个额外的位来表示该节点的颜色,红色或黑色。
红黑树满足以下特性:
- 红/黑属性:每个节点都有颜色,红色或黑色。
- 根属性:根为黑色。
- 叶子特性:每片叶子(NIL)都是黑色的。
- 红色属性:如果红色节点有子节点,那么子节点总是黑色的。(不能有两个连续红色节点)
- 深度属性:对于每个节点,从该节点到其任何子叶的任意简单路径都具有相同的黑色深度(黑色节点数)。(从任一节结点其每个叶子的所有路径都包含相同数目的黑色结点)
红黑树的一个示例:
每个节点具有以下属性:
- 颜色
- 键
- 左孩子
- 右孩子
- 父级(根节点除外)
红黑树是如何保持自我平衡的?
红黑颜色用于平衡树。
节点颜色的限制确保从根到叶的任何简单路径的长度都不会超过其他路径的两倍(根据性质5,所有最长的路径都有相同数目的黑色结点,这就表明了没有路径能多于任何其他路径的两倍长)。它有助于维持红黑树的自平衡特性。
1. 红黑树上的操作
可以在红黑树上执行的各种操作包括:
1.1 旋转红黑树中的子树
在旋转操作中,子树节点的位置是互换的。
旋转操作的作用是在其他操作(例如插入和删除)破坏红黑树的属性时,维护红黑树属性。
旋转有两种类型:
1.1.1 左旋转
在左旋转中,右侧节点的排列转换为左侧节点的排列。
算法:
- 初始树为:
- 如果y有左子树,则指定x作为y的左子树的父级。
- 如果x的父级为空,则将y作为树的根。
- 否则,如果x是p的左子级,则将y作为p的左子级。
- 否则指定y作为p的右子级。
- 使y成为x的父级。
1.1.2 右旋转
在右旋转中,左侧节点的排列转换为右侧节点的排列。
- 初始树为:
- 如果x有一个右子树,则指定y作为x的右子树的父级。
- 如果y的父级为空,则将x作为树的根。
- 否则,如果y是其父p的右子级,则使x成为p的右子级。
- 否则指定x作为p的左子级。
- 使x成为y的父级。
1.1.3 左右和右左旋转
在左右旋转中,排列首先向左移动,然后向右移动。
- 在X-Y上做左旋转。
- 在y-z方向做右旋转。
在右左旋转中,排列首先向右移动,然后向左移动。
- 在x-y方向做右旋转。
- 在z-y上做左旋转。
1.2 在红黑树中插入元素
插入新节点时,新节点始终作为红色节点插入。在插入新节点之后,如果树违反了红黑树的属性,那么我们执行以下操作。
- 重新着色
- 旋转
1.2.1 插入节点的算法
按照以下步骤将新元素插入到红黑树中:
- 让y为树的叶子(即NIL),x为树的根。
- 检查树是否为空(即x是否为NIL)。如果是,则插入newNode作为根节点并将其着色为黑色。
- 否则,重复以下步骤,直到达到叶子(NIL)。
a. 比较newKey和rootKey。
b. 如果newKey大于rootKey,则遍历右子树。
c. 否则遍历左子树。 - 将叶的父节点指定为newNode的父节点。
- 如果leafKey大于newKey,则将newNode设为左孩子。
- 否则,将newNode设为右孩子。
- 将NULL赋给newNode的左子级和右子级。
- 指定newNode为红色。
- 调用InsertFix算法来维护红黑树的属性。
为什么在红黑树中新插入的节点总是红色的?
这是因为插入红色节点不会违反红黑树的深度属性。
如果将红色节点附加到红色节点,则会违反规则,但解决此问题比解决因违反深度属性而引入的问题更容易。
1.2.2 插入后保持红黑属性的算法
如果newNode的插入违反了红黑树的属性,则该算法用于维护红黑树的属性。
- 在newNode p的父节点为红色时执行以下操作。
- 如果p是z的祖父母gP的左孩子,请执行以下操作。
第一种情况:
a. 如果z的gP的右子级的颜色是红色,则将gP的两个子级的颜色都设置为黑色,并将gP的颜色设置为红色。
b. 将gP分配给newNode。
第二种情况:
c. 否则,如果newNode是p的右子级,则将p指定给newNode。
d. 坐旋转newNode。
第三种情况:
e. 将p的颜色设置为黑色,gP的颜色设置为红色。
f. 右旋转gP。 - 否则,请执行以下操作。
a. 如果z的gP的左子级的颜色是红色,则将gP的两个子级的颜色都设置为黑色,并将gP的颜色设置为红色。
b. 将gP分配给newNode。
c. 否则,如果newNode是p的左子级,则将p指定给newNode并右旋转newNode。
d. 将p的颜色设置为黑色,gP的颜色设置为红色。
e. 左旋转gP。 - 把树根设为黑色。
1.3 从红黑树中删除元素
此操作将从树中删除节点。删除节点后,将再次维护红黑属性。
1.3.1 删除节点的算法
- 将待删除节点的颜色保存为原始颜色。
- 如果待删除节点的左子级为NULL
a. 将待删除节点的右子级指定给x。
b. 移植带x的待删除节点。 - 否则,如果待删除节点的右孩子是NULL
a. 将待删除节点的左子级赋给x。
b. 移植带x的待删除节点。 - 否则
a. 将要删除的节点的右子树的最小值赋给y。
b. 将y的颜色保存为原始颜色。
c. 将y的右子级赋给x。
d. 如果y是待删除节点的子级,则将x的父级设置为y。
e. 否则,移植y和y的右孩子。
f. 移植带y的节点。
g. 用原色设置y的颜色。 - 如果原始颜色是黑色,则调用DeleteFix(x)。
1.3.2 删除后保持红黑属性的算法
该算法是在黑节点被删除时实现的,因为它违反了红黑树的黑深度特性。
通过假设节点x(占据y的原始位置)有一个额外的黑色来纠正这种冲突。这使得节点x既不是红色也不是黑色。它不是双黑就是黑加红。这违反了红黑属性。
但是,x的颜色属性没有改变,而额外的黑色表示为x指向节点。
额外的黑色可以删除,如果:
- 它到达根节点。
- 如果x指向一个红黑节点。在这个例子中,x是黑色的。
- 进行适当的旋转和重新着色。
以下算法保留红黑树的属性。
-
执行以下操作,直到x不是树的根并且x的颜色为黑色
-
如果x是其父级的左子级,那么
a. 将w赋给x的同级。
b. 如果x的父级的右孩子是红色,
第一种情况:
a. 将x的父级的右孩子的颜色设置为黑色。
b. 将x的父级的颜色设置为红色。
c. 左旋转x的父级。
d. 将x的父级的右孩子赋值给w。
c. 如果w的左、右孩子的颜色都是黑色,
第二种情况:
a. 将w的颜色设置为红色
b. 将x的父级指定给x。
d. 否则如果w的右孩子的颜色是黑色
第三种情况:
a. 将w的左孩子的颜色设置为黑色
b. 将w的颜色设置为红色
c. 右旋转w
d. 将x的父级的右孩子赋值给w。
e. 如果上述任何情况没有发生,则执行以下操作。
第四种情况:
a. 将w的颜色设置为x的父级的颜色。
b. 将x的父级的颜色设置为黑色。
c. 将w的右子级的颜色设置为黑色。
d. 左旋转x的父级。
e. 将x设置为树的根。 -
否则同上,从右改为左,反之亦然。
-
将x的颜色设置为黑色。
2. C示例
// Implementing Red-Black Tree in C
#include <stdio.h>
#include <stdlib.h>
enum nodeColor {
RED,
BLACK
};
struct rbNode {
int data, color;
struct rbNode *link[2];
};
struct rbNode *root = NULL;
// Create a red-black tree
struct rbNode *createNode(int data) {
struct rbNode *newnode;
newnode = (struct rbNode *)malloc(sizeof(struct rbNode));
newnode->data = data;
newnode->color = RED;
newnode->link[0] = newnode->link[1] = NULL;
return newnode;
}
// Insert an node
void insertion(int data) {
struct rbNode *stack[98], *ptr, *newnode, *xPtr, *yPtr;
int dir[98], ht = 0, index;
ptr = root;
if (!root) {
root = createNode(data);
return;
}
stack[ht] = root;
dir[ht++] = 0;
while (ptr != NULL) {
if (ptr->data == data) {
printf("Duplicates Not Allowed!!\n");
return;
}
index = (data - ptr->data) > 0 ? 1 : 0;
stack[ht] = ptr;
ptr = ptr->link[index];
dir[ht++] = index;
}
stack[ht - 1]->link[index] = newnode = createNode(data);
while ((ht >= 3) && (stack[ht - 1]->color == RED)) {
if (dir[ht - 2] == 0) {
yPtr = stack[ht - 2]->link[1];
if (yPtr != NULL && yPtr->color == RED) {
stack[ht - 2]->color = RED;
stack[ht - 1]->color = yPtr->color = BLACK;
ht = ht - 2;
} else {
if (dir[ht - 1] == 0) {
yPtr = stack[ht - 1];
} else {
xPtr = stack[ht - 1];
yPtr = xPtr->link[1];
xPtr->link[1] = yPtr->link[0];
yPtr->link[0] = xPtr;
stack[ht - 2]->link[0] = yPtr;
}
xPtr = stack[ht - 2];
xPtr->color = RED;
yPtr->color = BLACK;
xPtr->link[0] = yPtr->link[1];
yPtr->link[1] = xPtr;
if (xPtr == root) {
root = yPtr;
} else {
stack[ht - 3]->link[dir[ht - 3]] = yPtr;
}
break;
}
} else {
yPtr = stack[ht - 2]->link[0];
if ((yPtr != NULL) && (yPtr->color == RED)) {
stack[ht - 2]->color = RED;
stack[ht - 1]->color = yPtr->color = BLACK;
ht = ht - 2;
} else {
if (dir[ht - 1] == 1) {
yPtr = stack[ht - 1];
} else {
xPtr = stack[ht - 1];
yPtr = xPtr->link[0];
xPtr->link[0] = yPtr->link[1];
yPtr->link[1] = xPtr;
stack[ht - 2]->link[1] = yPtr;
}
xPtr = stack[ht - 2];
yPtr->color = BLACK;
xPtr->color = RED;
xPtr->link[1] = yPtr->link[0];
yPtr->link[0] = xPtr;
if (xPtr == root) {
root = yPtr;
} else {
stack[ht - 3]->link[dir[ht - 3]] = yPtr;
}
break;
}
}
}
root->color = BLACK;
}
// Delete a node
void deletion(int data) {
struct rbNode *stack[98], *ptr, *xPtr, *yPtr;
struct rbNode *pPtr, *qPtr, *rPtr;
int dir[98], ht = 0, diff, i;
enum nodeColor color;
if (!root) {
printf("Tree not available\n");
return;
}
ptr = root;
while (ptr != NULL) {
if ((data - ptr->data) == 0)
break;
diff = (data - ptr->data) > 0 ? 1 : 0;
stack[ht] = ptr;
dir[ht++] = diff;
ptr = ptr->link[diff];
}
if (ptr->link[1] == NULL) {
if ((ptr == root) && (ptr->link[0] == NULL)) {
free(ptr);
root = NULL;
} else if (ptr == root) {
root = ptr->link[0];
free(ptr);
} else {
stack[ht - 1]->link[dir[ht - 1]] = ptr->link[0];
}
} else {
xPtr = ptr->link[1];
if (xPtr->link[0] == NULL) {
xPtr->link[0] = ptr->link[0];
color = xPtr->color;
xPtr->color = ptr->color;
ptr->color = color;
if (ptr == root) {
root = xPtr;
} else {
stack[ht - 1]->link[dir[ht - 1]] = xPtr;
}
dir[ht] = 1;
stack[ht++] = xPtr;
} else {
i = ht++;
while (1) {
dir[ht] = 0;
stack[ht++] = xPtr;
yPtr = xPtr->link[0];
if (!yPtr->link[0])
break;
xPtr = yPtr;
}
dir[i] = 1;
stack[i] = yPtr;
if (i > 0)
stack[i - 1]->link[dir[i - 1]] = yPtr;
yPtr->link[0] = ptr->link[0];
xPtr->link[0] = yPtr->link[1];
yPtr->link[1] = ptr->link[1];
if (ptr == root) {
root = yPtr;
}
color = yPtr->color;
yPtr->color = ptr->color;
ptr->color = color;
}
}
if (ht < 1)
return;
if (ptr->color == BLACK) {
while (1) {
pPtr = stack[ht - 1]->link[dir[ht - 1]];
if (pPtr && pPtr->color == RED) {
pPtr->color = BLACK;
break;
}
if (ht < 2)
break;
if (dir[ht - 2] == 0) {
rPtr = stack[ht - 1]->link[1];
if (!rPtr)
break;
if (rPtr->color == RED) {
stack[ht - 1]->color = RED;
rPtr->color = BLACK;
stack[ht - 1]->link[1] = rPtr->link[0];
rPtr->link[0] = stack[ht - 1];
if (stack[ht - 1] == root) {
root = rPtr;
} else {
stack[ht - 2]->link[dir[ht - 2]] = rPtr;
}
dir[ht] = 0;
stack[ht] = stack[ht - 1];
stack[ht - 1] = rPtr;
ht++;
rPtr = stack[ht - 1]->link[1];
}
if ((!rPtr->link[0] || rPtr->link[0]->color == BLACK) &&
(!rPtr->link[1] || rPtr->link[1]->color == BLACK)) {
rPtr->color = RED;
} else {
if (!rPtr->link[1] || rPtr->link[1]->color == BLACK) {
qPtr = rPtr->link[0];
rPtr->color = RED;
qPtr->color = BLACK;
rPtr->link[0] = qPtr->link[1];
qPtr->link[1] = rPtr;
rPtr = stack[ht - 1]->link[1] = qPtr;
}
rPtr->color = stack[ht - 1]->color;
stack[ht - 1]->color = BLACK;
rPtr->link[1]->color = BLACK;
stack[ht - 1]->link[1] = rPtr->link[0];
rPtr->link[0] = stack[ht - 1];
if (stack[ht - 1] == root) {
root = rPtr;
} else {
stack[ht - 2]->link[dir[ht - 2]] = rPtr;
}
break;
}
} else {
rPtr = stack[ht - 1]->link[0];
if (!rPtr)
break;
if (rPtr->color == RED) {
stack[ht - 1]->color = RED;
rPtr->color = BLACK;
stack[ht - 1]->link[0] = rPtr->link[1];
rPtr->link[1] = stack[ht - 1];
if (stack[ht - 1] == root) {
root = rPtr;
} else {
stack[ht - 2]->link[dir[ht - 2]] = rPtr;
}
dir[ht] = 1;
stack[ht] = stack[ht - 1];
stack[ht - 1] = rPtr;
ht++;
rPtr = stack[ht - 1]->link[0];
}
if ((!rPtr->link[0] || rPtr->link[0]->color == BLACK) &&
(!rPtr->link[1] || rPtr->link[1]->color == BLACK)) {
rPtr->color = RED;
} else {
if (!rPtr->link[0] || rPtr->link[0]->color == BLACK) {
qPtr = rPtr->link[1];
rPtr->color = RED;
qPtr->color = BLACK;
rPtr->link[1] = qPtr->link[0];
qPtr->link[0] = rPtr;
rPtr = stack[ht - 1]->link[0] = qPtr;
}
rPtr->color = stack[ht - 1]->color;
stack[ht - 1]->color = BLACK;
rPtr->link[0]->color = BLACK;
stack[ht - 1]->link[0] = rPtr->link[1];
rPtr->link[1] = stack[ht - 1];
if (stack[ht - 1] == root) {
root = rPtr;
} else {
stack[ht - 2]->link[dir[ht - 2]] = rPtr;
}
break;
}
}
ht--;
}
}
}
// Print the inorder traversal of the tree
void inorderTraversal(struct rbNode *node) {
if (node) {
inorderTraversal(node->link[0]);
printf("%d ", node->data);
inorderTraversal(node->link[1]);
}
return;
}
// Driver code
int main() {
int ch, data;
while (1) {
printf("1. Insertion\t2. Deletion\n");
printf("3. Traverse\t4. Exit");
printf("\nEnter your choice:");
scanf("%d", &ch);
switch (ch) {
case 1:
printf("Enter the element to insert:");
scanf("%d", &data);
insertion(data);
break;
case 2:
printf("Enter the element to delete:");
scanf("%d", &data);
deletion(data);
break;
case 3:
inorderTraversal(root);
printf("\n");
break;
case 4:
exit(0);
default:
printf("Not available\n");
break;
}
printf("\n");
}
return 0;
}
3. 红黑树应用
- 实施有限图
- 实现Java包:java.util.TreeMap 和 java.util.TreeSet
- 在C++中实现标准模板库(STL):多集,映射,多图
- 用于Linux内核
参考文档
[1]Parewa Labs Pvt. Ltd.Red-Black Tree[EB/OL].https://www.programiz.com/dsa/red-black-tree,2020-01-01.
[2]David Galles.Red/Black Tree[EB/OL].https://www.cs.usfca.edu/~galles/visualization/RedBlack.html,2011-01-01.(一个很酷的红黑树在线生成器)