C语言基础进阶——红黑树 红黑树是一类平衡二叉排序树
基本介绍
五个条件
- 每个节点非黑即红
- 根节点是黑色
- 叶节点(NIL)是黑色
- NIL即NULL is legal
- 是一个结点,访问是合法的
- 在图中一般不会画出来
- 如果一个节点是红色,则它的两个子结点都是黑色的
- 辅助控制红黑树的次要条件
- 从根节点出发到所有叶节点路径上,黑色节点数量相同
- 控制红黑树的关键条件
- 最长路径最多为最短路径的两倍
- 对于红黑树的某高度下最少结点数目low(H) = low(H - 1) + low(H / 2) + 1
- 控制了最短边和最长边的关系,整体高度增加为log(n)级别
- 插入调整是在祖父结点处进行调整,看它以下两层有没有出现双红情况
- 删除调整站在父节点,看下面一层是否有双黑节点
插入调整 注意在插入时候,节点作为红色插入
情况一: 双红失衡,且x的叔父节点是红色的,如下两图插入所示(根据x位置不同会有4种小情况,操作同理)
文章图片
文章图片
此时x祖父节点一定是黑色的,因此将祖父节点的黑色变为红色,并将x的父节点和叔父节点改为黑色,之后将x的祖父节点作为x继续向上迭代,即红色上顶
文章图片
情况2: 双红失衡,且x的叔父节点为黑色,x节点在祖父节点的LL处,如下图所示(RR处类似)
文章图片
首先对15节点进行右旋操作,并分析颜色确定的节点,除17号节点之外的颜色均确定,且注意调整完后不能改变路径上黑色节点数目,类比红色上顶使用红色下沉操作,将15号节点改为黑色,20号节点改为红色,完成调整
文章图片
情况3: 双红失衡,且x的叔父节点为黑色,x点在祖父节点LR处(RL处类似)
文章图片
则首先对x处进行右旋操作,得到与情况2开始相同的结果,然后进行情况2操作即可
- 对红色节点进行左旋/右旋操作不会影响路径上的黑色节点数目
情况1: 度为1的节点的删除,该结点一定为黑色,子结点为红色,删除后将连接上的子结点设置为黑色。
情况2: 度为0的红色结点,直接删除,没有影响
待删除结点和子结点都为黑色,首先将待删除的子结点替换到待删除节点上,标记为x,并将x的兄弟结点标记为w,此时x具有双重黑色属性,也就是说在计算路径上的黑色结点时,x要两次计数
情况3: x的兄弟节点为红色,如下图所示
文章图片
此时对兄弟结点进行右旋操作,并改变兄弟结点为黑色,父节点为红色,得到下图所示红黑树,此时x兄弟结点为黑色,按照情况4/5/6进行处理
文章图片
【暑期实训|1.4.C语言基础进阶——红黑树】情况4: x的兄弟结点是黑色,并且w的两个子结点为黑色
文章图片
去掉x的一重黑色,并将w改为红色,在x父节点加上额外一重黑色(红色->黑色 / 黑色->双重黑色),之后将x父结点当作x,递归进行继续后续的调整操作
文章图片
情况5: x的兄弟结点w是黑色的,w的左孩子是红色的,w的右孩子是黑色的
文章图片
此时交换w和它的左孩子的颜色,然后对w进行右旋操作。现在w的右孩子为红色,可以按照情况6进行处理
文章图片
情况6: x的兄弟结点w是黑色,且w的右孩子是红色的,左孩子为红色或者黑色
文章图片
此时,调整w及其父节点和右孩子的颜色,并对w的父节点进行一次左旋,可以使x去掉一重黑色,这时以根节点作为x,继续进行判断,具体来说,51改成其父节点38颜色,38结点一定需要改成黑色
文章图片
代码演示
#include
#include
#include
#include >using namespace std;
#define L(n) (n->lchild)
#define R(n) (n->rchild)
#define K(n) (n->key)
#define C(n) (n->color)
//结构定义
typedef struct Node{
int key;
int color;
//0 red, 1 black, 2 double black
struct Node *lchild, *rchild;
}Node;
//虚拟空节点
Node __NIL;
#define NIL (&__NIL)
//设置函数属性在主函数之前运行
//NIL的初始化
__attribute__((constructor))
void init_NIL(){
NIL->key = 0;
NIL->color = 1;
NIL->lchild = NIL->rchild = NIL;
return ;
}
//返回一个红黑树节点
Node *getNewNode(int key){
Node *p = (Node *)malloc(sizeof(Node));
p->key = key;
//默认颜色为红色
p->color = 0;
//程序中没有空节点,取而代之的是NIL
p->lchild = p->rchild = NIL;
return p;
}
//左旋
Node *left_rotate(Node *root){
Node *temp = root->rchild;
root->rchild = temp->lchild;
temp->lchild = root;
return temp;
}
//右旋
Node *right_rotate(Node *root){
Node *temp = root->lchild;
root->lchild = temp->rchild;
temp->rchild = root;
return temp;
}
//一个节点是否有红色子结点
int hasRed(Node *root){
return C(L(root)) == 0 || C(R(root)) == 0;
}
//插入调整,返回插入之后新的根节点
Node *insert_maintain(Node *root){
if(!hasRed(root)) return root;
int flag = 0;
//说明冲突在左子树发生
if(C(L(root)) == 0 && hasRed(L(root))) flag = 1;
//说明冲突在右子树发生
if(C(R(root)) == 0 && hasRed(R(root))) flag = 2;
if(flag == 0) return root;
if(C(L(root)) == 0 && C(R(root)) == 0) goto insert_end;
if(flag == 1){
if(C(R(L(root))) == 0){
root->lchild = left_rotate(root->lchild);
}
root = right_rotate(root);
}else{
if(C(L(R(root))) == 0){
root->rchild = right_rotate(root->rchild);
}
root = left_rotate(root);
}
insert_end:
C(root) = 0;
C(L(root)) = C(R(root)) = 1;
return root;
}
Node *__insert(Node *root, int key){
if(root == NIL) return getNewNode(key);
if(key == root->key) return root;
if(key < root->key) root->lchild = __insert(root->lchild, key);
else root->rchild = __insert(root->rchild, key);
//插入之后进行插入调整
return insert_maintain(root);
}//插入操作,返回插入之后的跟节点
Node *insert(Node *root, int key){
root = __insert(root, key);
root->color = 1;
return root;
}
//返回前驱节点
Node *predecessor(Node *root){
Node *temp = root->lchild;
while(temp->rchild != NIL) temp = temp->rchild;
return temp;
}
//删除调整
Node *erase_maintain(Node *root){
//没有冲突
if(C(L(root)) != 2 && C(R(root)) != 2) return root;
//双黑节点的兄弟节点是红色节点
if(hasRed(root)){
root->color = 0;
if(C(L(root)) == 0) {
root = right_rotate(root);
root->rchild = erase_maintain(root->rchild);
}
else {
root = left_rotate(root);
root->lchild = erase_maintain(root->lchild);
}
root->color = 1;
return root;
}
//双黑节点的兄弟节点是黑色
if(C(L(root)) ==1){
C(R(root)) -= 1;
if(!hasRed(L(root))){
C(root) += 1;
C(L(root)) -= 1;
return root;
}
if(C(L(L(root))) != 0){
root->lchild->color = 0;
root->lchild = left_rotate(root->lchild);
root->lchild->color = 1;
}
root->lchild->color = root->color;
root = right_rotate(root);
}else{
C(L(root)) -= 1;
if(!hasRed(R(root))){
C(root) += 1;
C(R(root)) -= 1;
return root;
}
if(C(R(R(root))) != 0){
root->rchild->color = 0;
root->rchild = right_rotate(root->rchild);
root->rchild->color = 1;
}
root->rchild->color = root->color;
root = left_rotate(root);
}
C(L(root)) = C(R(root)) = 1;
return root;
}
Node *__erase(Node *root, int key){
if(root == NIL) return root;
if(key < root->key){
root->lchild = __erase(root->lchild, key);
}else if(key > root->key){
root->rchild = __erase(root->rchild, key);
}else{
if(root->lchild == NIL || root->rchild == NIL){
Node *temp = root->lchild == NIL ? root->rchild : root->lchild;
temp->color += root->color;
free(root);
return temp;
}else{
Node *temp = predecessor(root);
root->key = temp->key;
root->lchild = __erase(root->lchild, temp->key);
}
}
return erase_maintain(root);
}
//删除的入口函数
Node *erase(Node *root, int key){
root = __erase(root, key);
root->color = 1;
return root;
}
//销毁操作
void clear(Node *root){
if(root == NIL) return;
clear(root->lchild);
clear(root->rchild);
free(root);
return;
}
void output(Node *root){
if(root == NIL) return ;
printf("(%d : %d | %d, %d)\n", C(root), K(root), K(L(root)), K(R(root)));
output(root->lchild);
output(root->rchild);
return ;
}
int main(){
int op, val;
Node *root = NIL;
while(~scanf("%d%d", &op, &val)){
switch(op){
case 1:root = insert(root, val);
break;
case 2:root = erase(root, val);
break;
}
printf("========= red black tree =========\n");
output(root);
printf("=========== end tree =============\n");
}
clear(root);
return 0;
}
后记.1: 红黑树和AVL树的查询、插入、修改操作的时间复杂度是一样的,单纯查询效率上看,红黑树略慢于AVL树,但是从整体搭建上面看,红黑树的很多插入删除操作可以通过修改颜色完成,而不需要频繁进行左旋右旋操作,而左旋右旋操作时比较费时的,所以红黑树实际运行效率要比AVL更高,在很多代码中都应用到了红黑树,例如C++ STL中的set和map
推荐阅读
- 算法刷题打卡|算法学习 第三十天 有序数组构造二叉搜索树
- 如何在C ++中打印Pascal三角形
- 如何解决C++错误C4996’getch’(不建议使用此项目的POSIX名称。而是使用符合ISO C和C ++的名称:_getch)
- javaSE|二叉树——二叉查找树、平衡二叉树(红黑树)
- 数据结构与算法|一看就懂的二叉查找树和平衡二叉查找树
- 算法|二叉树、二叉搜索树、AVL树、B树、红黑树
- c++|Python 什么时候会被取代()
- 老猿Python|Python+Pycharm和 VisualStudio C++社区版使用PK及易混淆的语法问题
- C++|Visual Studio高效调试手段与技巧总结(经验分享)