红黑树是一种自平衡二叉查找树,它能够保持树的平衡,从而确保查找、插入和删除的最坏情况时间复杂度为O( l o g n log_n logn)。在红黑树中,节点被涂成红色或黑色,并且通过旋转和重新着色等操作来保持树的平衡。本文将详细介绍红黑树的原理,并使用C语言进行模拟实现。
一、红黑树的性质
红黑树具有以下性质:
- 每个节点非红即黑。
- 根节点是黑色的。
- 每个叶子节点(NIL)是黑色的。
- 如果一个节点是红色的,则它的子节点必须是黑色的(不能有两个连续的红色节点)。
- 从任一节点到其每个叶子节点的所有路径都包含相同数量的黑色节点。
二、红黑树的旋转操作
红黑树通过旋转操作来维护树的平衡。旋转分为左旋和右旋两种操作。
- 左旋:将某个节点作为旋转中心,将其右子节点向上移动,代替该节点的位置,然后将该节点作为左子节点。
- 右旋:将某个节点作为旋转中心,将其左子节点向上移动,代替该节点的位置,然后将该节点作为右子节点。
三、红黑树的插入操作
红黑树的插入操作包括以下几个步骤:
- 执行二叉查找树的插入操作,将新节点涂成红色。
- 调整红黑树,使其满足红黑树的性质。
插入操作可能导致以下几种情况: - 叔叔节点是红色:此时,只需重新着色即可。
- 叔叔节点是黑色或不存在:
(1)当前节点是父节点的左子节点,且父节点是祖父节点的左子节点,或者当前节点是父节点的右子节点,且父节点是祖父节点的右子节点。此时,只需进行一次旋转操作。
(2)当前节点是父节点的左子节点,且父节点是祖父节点的右子节点,或者当前节点是父节点的右子节点,且父节点是祖父节点的左子节点。此时,需要进行两次旋转操作。
四、红黑树的删除操作
红黑树的删除操作包括以下几个步骤:
- 执行二叉查找树的删除操作。
- 调整红黑树,使其满足红黑树的性质。
删除操作可能导致以下几种情况: - 被删除节点是红色:直接删除,不影响红黑树的性质。
- 被删除节点是黑色:
(1)被删除节点的子节点是红色:直接删除,并将子节点涂成黑色。
(2)被删除节点的子节点是黑色:需要进行调整,以保持红黑树的性质。
五、红黑树的应用
红黑树在计算机科学中有着广泛的应用,例如在Java的TreeMap和TreeSet中,以及C++的STL中。红黑树能够提供高效的查找、插入和删除操作,因此在需要维护有序数据集合的场景中,红黑树是一个很好的选择。
六、C语言模拟红黑树
以下是一个简单的C语言实现红黑树的示例:
#include <stdio.h>
#include <stdlib.h>
typedef enum {RED, BLACK} Color;
typedef struct Node {
int data;
Color color;
struct Node *parent;
struct Node *left;
struct Node *right;
} Node;
/**
* 创建并初始化一个节点。
* @param data 节点中要存储的数据。
* @return 返回指向新创建节点的指针。
*/
Node *createNode(int data) {
// 分配内存给新节点
Node *newNode = (Node *)malloc(sizeof(Node));
if (newNode == NULL) {
printf("Memory allocation failed!\n"); // 内存分配失败处理
exit(1);
}
newNode->data = data; // 设置节点数据
newNode->color = RED; // 新插入的节点默认为红色
newNode->parent = NULL; // 初始化父节点为NULL
newNode->left = NULL; // 初始化左子节点为NULL
newNode->right = NULL; // 初始化右子节点为NULL
return newNode; // 返回新创建的节点
}
/**
* 左旋操作,用于平衡二叉搜索树(BST)。
*
* @param root 指向当前树根节点的指针的地址。
* @param x 需要进行左旋操作的节点。
* 说明:函数不返回任何值,但会修改树的结构。
*/
void leftRotate(Node **root, Node *x) {
Node *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; // 如果x是根节点,更新根节点。
else if (x == x->parent->left)
x->parent->left = y; // 如果x是其父节点的左子节点,更新左子节点。
else
x->parent->right = y; // 如果x是其父节点的右子节点,更新右子节点。
y->left = x;
x->parent = y;
}
/**
* 右旋操作
*
* @param root 指向当前树根节点的指针的地址。
* @param x 需要进行右旋操作的节点。
* 说明:函数不返回任何值,但会修改树的结构。
*/
void rightRotate(Node **root, Node *y) {
Node *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;
}
/**
* 插入修复函数
* 该函数用于在红黑树中插入节点后,维护红黑树的性质。主要通过旋转和颜色的变换来保持红黑树的性质。
*
* @param root 指向红黑树根节点的指针的地址
* @param z 待插入的节点
*/
void insertFixup(Node **root, Node *z) {
while (z != *root && z->parent->color == RED) {
if (z->parent == z->parent->parent->left) {
Node *y = z->parent->parent->right;
if (y != NULL && y->color == RED) {
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;
z = z->parent->parent;
} else {
if (z == z->parent->right) {
z = z->parent;
leftRotate(root, z);
}
z->parent->color = BLACK;
z->parent->parent->color = RED;
rightRotate(root, z->parent->parent);
}
} else {
Node *y = z->parent->parent->left;
if (y != NULL && y->color == RED) {
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;
z = z->parent->parent;
} else {
if (z == z->parent->left) {
z = z->parent;
rightRotate(root, z);
}
z->parent->color = BLACK;
z->parent->parent->color = RED;
leftRotate(root, z->parent->parent);
}
}
}
(*root)->color = BLACK;
}
/**
* 向红黑树中插入一个新节点
*
* @param root 指向红黑树根节点的指针的地址
* @param data 需要插入的数据
*/
void insert(Node **root, int data) {
Node *z = createNode(data);
Node *y = NULL;
Node *x = *root;
while (x != NULL) {
y = x;
if (z->data < x->data)
x = x->left;
else
x = x->right;
}
z->parent = y;
if (y == NULL)
*root = z;
else if (z->data < y->data)
y->left = z;
else
y->right = z;
insertFixup(root, z);
}
void inorderTraversal(Node *root) {
if (root != NULL) {
inorderTraversal(root->left);
printf("%d ", root->data);
inorderTraversal(root->right);
}
}
int main() {
Node *root = NULL;
insert(&root, 10);
insert(&root, 20);
insert(&root, 30);
insert(&root, 15);
insert(&root, 18);
inorderTraversal(root);
printf("\n");
return 0;
}
完整代码(包括红黑树节点输出以及删除节点操作)【链接】
结果
七、总结
红黑树是一种自平衡二叉查找树,它能够保持树的平衡,从而保证查找、插入和删除的最坏情况时间复杂度为O( l o g n log_n logn)。红黑树通过旋转操作和颜色变更来维护树的性质,适用于需要维护有序数据集合的场景。在Java和C++的STL中,红黑树得到了广泛的应用。