红黑树的性质
红黑树满足以下性质:
- 每个节点是红色或黑色。
- 根节点是黑色。
- 每个叶子节点(NIL节点)是黑色。
- 如果一个节点是红色的,那么它的两个子节点都是黑色的。(即不存在两个连续的红色节点)
- 从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点。
这些性质确保了红黑树的高度是平衡的,从而保证了基本操作的时间复杂度是 O(log n)。
红黑树的辅助操作:左旋、右旋、变色及其他
红黑树(Red-Black Tree)是一种自平衡二叉查找树,确保在插入和删除节点后,树的高度保持在对数级别,从而保证高效的查找、插入和删除操作。本文将详细介绍红黑树的辅助操作,包括左旋、右旋、变色、前驱节点和后继节点的查找等。
左旋操作
左旋是红黑树的一种基本操作,用于调整树的平衡。在左旋操作中,一个节点及其右子节点进行旋转,右子节点成为其父节点,而原节点成为新父节点的左子节点。
实现代码
void leftRotate(RBTree* p) {
if (!p) return;
RBTree* right = p->right; // 将p结点的右孩子用right表示
p->right = right->left; // 旋转后p的右孩子变为right的左孩子
if (right->left) { // 如果right的左孩子存在
right->left->parent = p; // 那么左孩子的双亲结点设置为p
}
right->parent = p->parent; // 左旋后,right的双亲变为p的双亲
if (!p->parent) { // 如果p就是根结点
root = right; // 那么旋转后right就是根结点了
} else if (p->parent->left == p) { // 如果p不是根结点,那么如果p是其双亲的左孩子
p->parent->left = right; // 对应现在的左孩子就应该是right
} else {
p->parent->right = right; // 对应右孩子是right
}
right->left = p; // 旋转后right的左孩子变为p
p->parent = right; // 旋转后p的双亲变为right
}
右旋操作
右旋是另一种基本操作,与左旋相对。在右旋操作中,一个节点及其左子节点进行旋转,左子节点成为其父节点,而原节点成为新父节点的右子节点。
实现代码
void rightRotate(RBTree* p) {
if (!p) return;
RBTree* left = p->left; // 将p结点的左孩子用left表示
p->left = left->right; // 旋转后p的左孩子变为left的右孩子
if (left->right) { // 如果left的右孩子存在
left->right->parent = p; // 那么右孩子的双亲结点设置为p
}
left->parent = p->parent; // 右旋后,left的双亲变为p的双亲
if (!p->parent) { // 如果p就是根结点
root = left; // 那么旋转后left就是根结点了
} else if (p->parent->left == p) { // 如果p不是根结点,那么如果p是其双亲的左孩子
p->parent->left = left; // 对应现在的左孩子就应该是left
} else {
p->parent->right = left; // 对应右孩子是left
}
left->right = p; // 旋转后left的右孩子变为p
p->parent = left; // 旋转后p的双亲变为left
}
变色操作
变色操作用于调整节点的颜色,以维护红黑树的性质。
实现代码
void setColor(RBTree* p, Color color) {
p->color = color;
}
前驱节点的查找
前驱节点是指在二叉搜索树中小于给定节点的最大节点。
实现代码
RBTree* predecessor(RBTree* e) {
if (!e) return NULL;
RBTree* p = NULL; // 工作指针
// 当前结点有左孩子,那么前驱是左子树的最右结点
if (e->left) {
p = e->left;
while (p->right) {
p = p->right;
}
return p;
}
// 当前结点没有左孩子,如果当前结点为右孩子,那么前驱是就是它的父亲结点
if (e == e->parent->right) return e->parent;
else {
// 当前结点为左孩子,那么其后继结点为向上身为第一颗右子树结点的父亲结点
p = e->parent;
while (p && p != p->parent->right) {
p = p->parent;
}
return p->parent;
}
return NULL;
}
后继节点的查找
后继节点是指在二叉搜索树中大于给定节点的最小节点。
实现代码
RBTree* successor(RBTree* e) {
if (!e) return NULL;
RBTree* p = NULL; // 工作指针
// 有右孩子,那么其后继结点就是其右孩子的最左结点
if (e->right) {
p = e->right;
while (p->left) {
p = p->left;
}
return p;
} else {
// 无右孩子,如果当前结点为左孩子,那么其后继结点为其父节点
if (e == e->parent->left) return e->parent;
else {
// 无右孩子,如果当前结点为右孩子,那么其后继结点为向上身为第一颗左子树结点的父亲结点
p = e->parent;
while (p && p != p->parent->left) {
p = p->parent;
}
return p->parent;
}
}
return NULL;
}
节点查找
在红黑树中,通过给定的键值查找相应的节点。
实现代码
RBTree* getNode(const char *key) {
RBTree* p = root; // 工作指针
while (p) {
if (atoi(p->key) == atoi(key)) {
return p;
} else if (atoi(p->key) < atoi(key)) {
p = p->right;
} else {
p = p->left;
}
}
return NULL;
}
总结
通过对红黑树的左旋、右旋、变色操作,以及前驱节点和后继节点的查找,我们能够维护红黑树的平衡性,确保其高效的查找、插入和删除操作。本文详细介绍了这些辅助操作的实现代码,为深入理解红黑树提供了基础。
红黑树的插入和调整
红黑树是一种平衡二叉搜索树,确保在插入和删除节点后,树的高度始终保持对数级别,从而保证高效的查找、插入和删除操作。本文将详细讲解红黑树的插入操作及其相应的调整机制。
代码分析
以下是红黑树插入操作及其调整的具体实现。
创建新节点
首先,定义了一个函数 createNP
用于创建一个新的红黑树节点:
RBTree* createNP(const char *key, Color color) {
RBTree* newP = (RBTree*)malloc(sizeof(RBTree));
newP->key = key;
newP->left = NULL;
newP->right = NULL;
newP->parent = NULL;
newP->color = color;
return newP;
}
这个函数根据传入的键值和颜色创建一个新的节点,并将其左、右子节点和父节点初始化为 NULL
。
插入节点
接下来,实现了插入节点的操作:
void insert(const char *key) {
if (!root) {
root = createNP(key, BLACK);
return;
}
RBTree* p = root;
RBTree* pre = NULL;
while (p) {
if (atoi(p->key) == atoi(key)) {
return;
} else if (atoi(p->key) < atoi(key)) {
pre = p;
p = p->right;
} else {
pre = p;
p = p->left;
}
}
RBTree* newP = createNP(key, RED);
newP->parent = pre;
if (atoi(pre->key) > atoi(key)) {
pre->left = newP;
} else {
pre->right = newP;
}
fixAfterInsert(newP);
}
该函数首先检查树是否为空,如果为空,则直接将新节点作为根节点插入,并将其颜色设置为黑色。否则,通过比较节点值,找到合适的插入位置,并将新节点插入到树中。新节点默认颜色为红色。插入后,调用 fixAfterInsert
函数对树进行调整。
插入后的调整
插入新节点后,为了维护红黑树的性质,需要进行相应的调整:
void fixAfterInsert(RBTree* e) {
if (e->parent->color == BLACK) {
return;
}
while (e) {
if (!e->parent) {
setColor(e, BLACK);
return;
} else if (e->parent && e->parent->color == RED) {
RBTree* p = e->parent;
RBTree* pp = e->parent->parent;
RBTree* up = p == pp->left ? pp->right : pp->left;
if (up && up->color == RED) {
setColor(p, BLACK);
setColor(up, BLACK);
setColor(pp, RED);
e = pp;
} else if (!up || up->color == BLACK) {
if (pp->left == p && p->left == e) {
p->color = BLACK;
pp->color = RED;
rightRotate(pp);
break;
}
if (pp->left == p && p->right == e) {
leftRotate(p);
e = p;
}
if (pp->right == p && p->right == e) {
p->color = BLACK;
pp->color = RED;
leftRotate(pp);
break;
}
if (pp->right == p && p->left == e) {
rightRotate(p);
e = p;
}
}
} else {
break;
}
}
}
在 fixAfterInsert
函数中,根据红黑树的性质,对插入节点后可能导致的不平衡情况进行调整:
- 父节点为黑色:无需调整,直接返回。
- 父节点为红色:需要进行调整。调整逻辑如下:
- 如果父节点的兄弟节点(即叔父节点)是红色,将父节点和叔父节点染黑,祖父节点染红,然后将祖父节点作为新的节点,继续调整。
- 如果父节点的兄弟节点是黑色或为空,则根据具体情况进行旋转和变色操作,分为左左、左右、右右和右左四种情况处理。
旋转操作
在调整过程中,涉及左旋和右旋操作,具体实现如下:
void leftRotate(RBTree* e) {
RBTree* p = e->right;
e->right = p->left;
if (p->left) p->left->parent = e;
p->parent = e->parent;
if (!e->parent) root = p;
else if (e == e->parent->left) e->parent->left = p;
else e->parent->right = p;
p->left = e;
e->parent = p;
}
void rightRotate(RBTree* e) {
RBTree* p = e->left;
e->left = p->right;
if (p->right) p->right->parent = e;
p->parent = e->parent;
if (!e->parent) root = p;
else if (e == e->parent->right) e->parent->right = p;
else e->parent->left = p;
p->right = e;
e->parent = p;
}
左旋和右旋操作通过调整节点的指针关系来实现,确保树的结构符合红黑树的性质。
总结
红黑树通过在插入和删除节点后进行必要的旋转和变色操作,确保树的高度始终保持平衡,从而保证高效的查找、插入和删除操作。本文详细讲解了红黑树的插入操作及其调整机制,通过具体代码展示了实现过程。理解红黑树的这些基本操作对于掌握高级数据结构和算法具有重要意义。
红黑树的删除操作详解
红黑树(Red-Black Tree)是一种自平衡二叉查找树,删除操作相比插入操作复杂得多,因为删除节点后需要进行复杂的调整来保持红黑树的平衡性。本文详细介绍红黑树中删除节点的操作及其相应的调整方法。
删除节点的基本思想
删除节点的过程分为三种情况:
- 删除叶子节点:直接删除,不需要调整。
- 删除有一个子节点的节点:用其子节点替代该节点。
- 删除有两个子节点的节点:找到其前驱或后继节点,用该节点替代要删除的节点,然后转化为删除该替代节点。
删除节点的实现代码
头文件及必要函数声明
#include <stdio.h>
#include <stdlib.h>
#include "RBTree.h"
void fixAfterDel(RBTree* e);
删除节点的主函数
void delNode(const char *key) {
RBTree* node = getNode(key); // 找到要删除的结点
if (!node)
return; // 如果没有找到节点,直接返回
if (!node->parent && !node->left && !node->right) {
root = NULL; // 如果要删除的是根节点且它没有子节点,直接置空并返回
return;
}
while (node) {
// 情况3:如果节点有两个子节点,找前驱或后继节点进行替代
if (node->left && node->right) {
RBTree* pre = predecessor(node); // 找到前驱结点
node->key = pre->key; // 用前驱结点的信息覆盖要删除结点的信息
node = pre; // 转化为删除前驱结点
}
// 情况2:节点只有一个子节点,用子节点替代
RBTree* rep = node->left ? node->left : node->right;
if (rep) {
node->key = rep->key;
node = rep;
} else {
// 情况1:节点是叶子节点
if (!node->parent) {
root = NULL; // 如果要删除根结点,直接置空并返回
return;
}
// 如果是黑色节点,需要进行调整
if (node->color == BLACK) {
fixAfterDel(node);
}
// 删除节点
if (node->parent->left == node) {
node->parent->left = NULL;
} else {
node->parent->right = NULL;
}
node = NULL;
}
}
}
删除节点后的调整函数
int rotateType(RBTree* bro) {
if (bro->parent->left == bro && bro->left && bro->left->color == RED) {
return 1; // LL类型
}
if (bro->parent->right == bro && bro->right && bro->right->color == RED) {
return 3; // RR类型
}
if (bro->parent->left == bro && bro->right && bro->right->color == RED) {
return 2; // LR类型
}
if (bro->parent->right == bro && bro->left && bro->left->color == RED) {
return 4; // RL类型
}
return 0;
}
void fixAfterDel(RBTree* e) {
RBTree* bro, * p, * nl, * nr;
while (e) {
if (!e->parent) {
setColor(e, BLACK); // 是根结点,直接染黑
return;
}
int direBro = e->parent->right == e ? 1 : 2; // 判断兄弟是左孩子还是右孩子
p = e->parent;
bro = e->parent->right == e ? e->parent->left : e->parent->right; // 得到兄弟结点
nr = bro->right;
nl = bro->left;
if (bro->color == RED) {
direBro == 1 ? nr->color = RED : nl->color = RED; // 兄弟是红色
bro->color = BLACK; // 兄弟变为黑色
direBro == 1 ? rightRotate(p) : leftRotate(p); // 右旋或左旋
break;
} else {
if (!bro->left && !bro->right) {
if (p->color == RED) {
p->color = BLACK;
bro->color = RED;
break;
} else {
bro->color = RED;
e = p; // 递归向上调整
}
} else {
int type = rotateType(bro);
if (type == 1) {
nl->color = bro->color;
bro->color = p->color;
p->color = e->color;
rightRotate(e->parent);
} else if (type == 2) {
nr->color = p->color;
p->color = e->color;
leftRotate(bro);
rightRotate(p);
} else if (type == 3) {
nr->color = bro->color;
bro->color = p->color;
p->color = e->color;
leftRotate(e->parent);
} else if (type == 4) {
nl->color = p->color;
p->color = e->color;
rightRotate(bro);
leftRotate(p);
}
break;
}
}
}
}
总结
红黑树的删除操作比插入操作复杂得多,需要考虑多种情况。删除节点后,可能需要进行多种旋转和变色操作,以确保红黑树的性质不被破坏。本文详细介绍了删除节点的实现和删除后的调整过程,希望对深入理解红黑树的操作有所帮助。
红黑树创建、插入及删除操作测试
本文将通过一个测试文件来演示如何在红黑树上进行创建、插入和删除操作。我们将使用一个数组中的数据来创建红黑树,并对其进行插入和删除操作,验证红黑树的自平衡特性。
测试代码实现
头文件引入
#include <stdio.h>
#include <stdlib.h>
#include "RBTree.h"
RBTree* root = NULL;
主函数
int main() {
// 从数组中获取数据进行创建
const char arr[][3] = { "20", "3", "44", "5", "6", "75", "8", "19", "60", "50", "12", "1" };
// 插入数组中的数据到红黑树中
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
insert(arr[i]);
printRBTree();
}
// 进行删除操作并打印红黑树
/*delNode("75");
printRBTree();
delNode("60");
printRBTree();
delNode("20");
printRBTree();*/
return 0;
}
主要函数的实现
在上面的代码中,我们主要完成了红黑树的创建和插入操作,删除操作被注释掉。下面是对每个主要函数的简要描述:
insert
函数
用于将新节点插入红黑树中,确保插入后红黑树仍然保持平衡。
printRBTree
函数
用于打印红黑树的结构,便于我们观察树的变化。
delNode
函数
用于删除红黑树中的节点,删除后需进行调整以保持树的平衡。
测试运行结果
我们将数组中的数据依次插入红黑树,每次插入后打印红黑树的结构:
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
insert(arr[i]);
printRBTree();
}
然后可以进行删除操作并观察红黑树的变化:
delNode("75");
printRBTree();
delNode("60");
printRBTree();
delNode("20");
printRBTree();
插入操作结果
插入操作会根据红黑树的特性进行相应的旋转和变色操作,确保树的平衡。
删除操作结果
删除操作会根据红黑树的特性进行相应的调整,确保删除节点后树的平衡性。
总结
通过这个测试文件,我们可以直观地观察到红黑树在插入和删除操作后的变化,验证红黑树的自平衡特性。希望本文对理解红黑树的创建、插入及删除操作有所帮助。