红黑树的简单实现
目录
- 前言
- 红黑树的基本规则
- 红黑树的基本框架
- 红黑树的插入
- 全部代码
- 红黑树的总结
1.前言
我们都应该了解二叉搜索树,二叉平衡树吧(AVL树),而红黑树就是建于这两中树的基础上的树了,它需要用到二叉搜索树的排序的原理,又需要用到二叉平衡树的旋转的功能,这也就是为什么说红黑树是建立在这两种树的基础之上了
2.红黑树的基本规则
作为一种特殊的树,肯定是有着自己的一套独特的规则, 例如:
二叉搜索树:小于树上的根(包括子树)的值域,就放到该根的左子树上,反之则放在右子树上
二叉平衡树:首先依然有二叉搜索树的规则:小于树上的根(包括子树)的值域,就放到该根的左子树上,反之则放在右子树上.其次当树两端的深度差的超过1则需要进行调整,直到深度差不超过1
红黑树:红黑树的规则除了二叉搜索树的外,还有:
(1).根节点和所有外部节点都是黑色
(2).在根到外部节点的路径上,不能有两个连续的红色节点
(3).所有从根到外部节点上的路径上,黑色节点个数都相同
我们就来看一下一个简单的红黑树:
我们下面就对着规则一个一个进行讲解:
(1).规则一:根节点和所有外部节点都是黑色
首先我们要知道外部节点其实就是空节点
我们可以看见这些绿色框框,这些绿色框框住的就是根节点和外部节点,我们就可以很轻松的看出来规则一符合
(2).规则二:在根到外部节点的路径上,不能有两个连续的红色节点
规则二这个直接看图我们就能知道这个规则二的内容
我们通过直接观察,我们就能知道红色节点不会连续
(3).规则三:所有从根到外部节点上的路径上,黑色节点个数都相同
这里面的绿色框框里的就是根节点到外部节点上的黑色节点的数量,我们通过观察发现这些黑色节点数量都是2,所以也符合我们的规则三
注意:当我们破环这三点规则的其中的任何一个规则的时候,都不能称这个树是红黑树
3.红黑树的基本框架
下面就是我们开始构建红黑树的基本框架了
(1).红黑树在插入节点的时候是全部都是黑色?还是红色?
这个问题我们可以对这个进行讨论一下
i.假如都是黑色
还是以上面的这个红黑树为例子:
我们选择插入一个为5的节点
我们就会发现红黑树的规则三(所有从根到外部节点上的路径上,黑色节点个数都相同)被破坏了,我们通过自己的仔细思考就会发现当插入的节点都是黑色的话,那么就规则三就一定会被破坏
ii.假如都是红色
这个我们选择插入一个还是值域为5的节点和一个值域为75的节点
首先,我们可以看到5的这个节点,我们可以很快的发现这个红黑树的任何一个规则都没有被破坏,所以不需要调整
其次,我们可以看向75的这个节点,我们就会发现这个节点处只有规则二破坏了,所以这个地方需要我们对其进行一个调整
小结一下:
插入的节点都是黑色的话,那么规则三一定会被破坏
插入的节点都是红色的话,规则二可能会被破坏
所以经过简单的对比,我们就可以知道我们插入的节点开始都是红色更好一点
对于像假如我们插入的时候这个节点刚好要是根节点该怎么办?
这个我们仅需要在最后将根节点的颜色变成黑色就可以了
(2).红黑树的基本框架
i.红黑树的定义
typedef struct rbtree_node
{
int val;//数值域
struct rbtree_node* left;//左孩子
struct rbtree_node* right;//右孩子
struct rbtree_node* parent;//父节点
int color;//颜色
}rbNode;
typedef struct rbtree
{
rbNode* root;//表示根节点
rbNode* nil;//外部节点
}rbtree;
ii.颜色的定义
//颜色的定义有两种方式:宏定义,枚举
//宏定义
#define RED 0
#define BLACK 1
//枚举
enum color
{
red,
black
};
//下面我就以枚举对红黑树进行说明
iii.创建新节点
//创建下面就是开始创建新节点了
rbNode* newNode(rbtree* rb, int val)
{
//开辟空间
rbNode* root = (rbNode*)malloc(sizeof(rbNode));
//设置好节点的关系
root->left = rb->nil;
root->right = rb->nil;
root->parent = rb->nil;
//初始化颜色为红色
root->color = red;
//初始化节点的数值域
root->val = val;
return root;
}
iiii.左旋和右旋
//注意红黑树中虽然也有左旋右旋的概念
//但是跟二叉平衡树的有所区别
//因为多了父节点,所以最后我们还需要对需要更改的父节点进行修改
//左旋函数
void leftRoate(rbtree* rb, rbNode* root)
{
rbNode* newroot = root->right;
rbNode* T2 = newroot->left;
rbNode* parent = root->parent;//当然这个节点也可以不设置,这里是为了让大家更好理解
root->right = T2;
//修改父节点:T2(如果不为rb->nil),newroot
if(T2 != rb->nil)
T2->parent = root;
if(parent == rb->nil)
{
//说明原本的root为整棵树的根节点
newroot->parent = rb->nil;
rb->root = newroot;
}
else if(parent->right == root)
{
//根节点在父节点的右子树上
newroot->parent = parent;
parent->right = newroot;
}
else if(parent->left == root)
{
//根节点在父节点的左子树上
newroot->parent = parent;
parent->left = newroot;
}
root->parent = newroot;//把这一个放到下面就是为了能够完成上面的工作
newroot->left = root;
root = newroot;
}
//右旋函数跟左旋对称
void rightRoate(rbtree* rb, rbNode* root)
{
rbNode* newroot = root->left;
rbNode* T2 = newroot->right;
rbNode* parent = root->parent;//当然这个节点也可以不设置,这里是为了让大家更好理解
root->left = T2;
//修改父节点:T2(如果不为rb->nil),newroot
if(T2 != rb->nil)
T2->parent = root;
if(parent == rb->nil)
{
//说明原本的root为整棵树的根节点
newroot->parent = rb->nil;
rb->root = newroot;
}
else if(parent->right == root)
{
//根节点在父节点的右子树上
newroot->parent = parent;
parent->right = newroot;
}
else if(parent->left == root)
{
//根节点在父节点的左子树上
newroot->parent = parent;
parent->left = newroot;
}
root->parent = newroot;//把这一个放到下面就是为了能够完成上面的工作
newroot->right = root;
root = newroot;
}
iiiii.先序和中序遍历
//我们知道通过先序和中序遍历可以确定一棵唯一的树
//通过这个性质,我们可以验证我们的树是否正确
//先序遍历
void preTraversal(rbtree* rb, rbNode* root)
{
if(root == rb->nil)
return;
printf("数值域: %d , 颜色: %d\n",root->val, root->color);
preTraversal(rb, root->left);
preTraversal(rb, root->right);
}
//中序遍历
void midTraversal(rbtree* rb, rbNode* root)
{
if(root == rb->nil)
return;
midTraversal(rb, root->left);
printf("数值域: %d , 颜色: %d\n",root->val, root->color);
midTraversal(rb, root->right);
}
iiiiii.红黑树的销毁
//为了防止内存泄漏,我们需要手动的对开辟的空间进行释放
//这里我选择的是后序遍历进行销毁
void rbtreeDestroy(rbtree* rb, rbNode* root)
{
if(root == rb->nil)
return;
rbtreeDestroy(rb, root->left);
rbtreeDestroy(rb, root->right);
free(root);
root = NULL;
}
这样我们的红黑树的基本框架就基本结束了
4.红黑树的插入
接下来就是插入的问题了
(1).插入问题的分类
i.没有问题
还是这个图,我们可以看到5的这个节点,我们就可以知道,当父节点为黑的时候,我们直接插入就可以了,不许做多余的操作
ii.LXr型
第一个L指的是刚插入的节点的父节点是其祖父节点的左子树,第二个X指的就是刚插入的节点是其父节点的左子树还是右子树的问题,由于处理方式一样,所以这里将这两种情况放到一起,最后一个r指的就是刚插入的这个节点的叔叔节点的颜色是红色的
我在这里就先将处理方式举出来:
- 将父亲节点变成黑色
- 将叔叔节点变成黑色
- 将祖父节点变成红色
- 由于祖父节点可能是整棵树的根节点,这时候可以通过递归再对祖父节点进行检查
举一个例子:
这里我就不把空节点给画出来了,我们这里就能看出来这明显是LLr型,然后我们跟着上面的步骤开始
首先,将父亲节点变成黑色
然后,将叔叔节点变成黑色
最后,将祖父节点变成红色
然后再将root这个指针移向祖父节点
然后再继续向上进行检测,重复这个过程即可
此时我们可以对最开始的根节点到外部节点的黑色节点数和现在的根节点到外部节点的黑色节点数进行比较
最开始:
最后:
我们可以发现这些黑色节点数没有发生改变,及规则三不会被破坏
下面我们就一起来看看LRr:
首先,将父亲节点变成黑色
然后,将叔叔节点变成黑色
最后,将祖父节点变成红色
然后再将root这个指针移向祖父节点
然后再继续向上进行检测,重复这个过程即可
此时我们可以对最开始的根节点到外部节点的黑色节点数和现在的根节点到外部节点的黑色节点数进行比较
最开始:
最后:
这里依然成立
我们可以发现这个操作只是简单改变颜色,不需要对子树进行旋转的各种操作,这也就是为什么LLr和LRr放在一起了
iii.LLb型和LRb型
从这里开始操作就开始复杂了,我们先来看一下LLb型
LLb
我们可以先一起了解一下其解决方法:
- 将父节点变成黑色
- 将祖父节点变成红色
- 对祖父节点进行右旋
由于最后这棵子树(或树)的的根节点变成了黑色,所以不需要向上继续检测
下面我们就一起来使用这个方法来解决个问题来:
首先,将父节点变成黑色
然后,将祖父节点变成红色
最后,对祖父节点进行右旋
我们就可以对比一下前后从根到外部节点的黑色节点的数量
最开始:
最后:
这样我们也可以保证我们树的规则三不被破坏
LRb
我们也是先来一起来看一下这个操作方式:
先将LRb型转化为LLb型
(1).将root从刚刚插入的节点变成其父节点
(2).对root这棵子树进行左旋的操作
将父节点变成黑色
将祖父节点变成红色
对祖父节点进行右旋
我们看着可能有点迷,下面我们就借一个问题来一起解决疑惑:
首先,将root从刚刚插入的节点变成其父节点
然后,对root这棵子树进行左旋的操作
然后我们就能发现这个跟上面的LLb情况完全相同,所以这里就不再继续讲述了
我们这个问题差不多就是到这里了,因为关于R开头的问题其实就是跟这里的对称就是我们想要的结果了
变色一样
方向对称
(2).代码实现
//插入节点函数
void rbnodeInsert(rbtree* rb, rbNode* root)
{
//这里我使用的是迭代法,当然也可以使用递归
if(rb->root == NULL)
{
rb->root = root;
rb->root->color = black;
return;
}
rbNode* cur = rb->root;//用于寻找空位
rbNode* y = NULL;//用于记录cur的父节点
while(cur != rb->nil)
{
y = cur;
if(cur->val < root->val)
cur = cur->right;
else if(cur->val > root->val)
cur = cur->left;
else if(cur->val == root->val)
return;//红黑树中不允许出现两个相同的节点
}
cur = root;
cur->parent = y;
//修正y的子树
if (y->val > cur->val)
{
y->left = root;
}
else if (y->val < cur->val)
{
y->right = root;
}
//对这棵红黑树进行检查并修正
rbtreeInsertFixup(rb,root);
}
//检查红黑树并修正
void rbtreeInsertFixup(rbtree* rb, rbNode* root)
{
if(root->parent->color == black)
return;
//LXx型
if(root->parent->parent->left == root->parent)
{
//LXr型
if(root->parent->parent->right->color == red)
{
//将父节点的颜色变为黑色
root->parent->color = black;
//将叔叔节点颜色变为黑色
root->parent->parent->right->color = black;
//将祖父节点的颜色变为红色
root->parent->parent->color = red;
//向上检查
rbtreeInsertFixup(rb, root->parent->parent);
}
//LXb型
else if(root->parent->parent->right->color == black)
{
//如果是LRb型
if(root->parent->right == root)
{
//将root变成其父节点
root = root->parent;
//对root的这棵树左旋
leftRoate(rb, root);
}
//LLb型
//将其父节点的颜色变为黑色
root->parent->color = black;
//将其祖父节点的颜色变为红色
root->parent->parent->color = red;
//对root的祖父节点的这棵树进行右旋
rightRoate(rb, root->parent->parent);
}
}
//RXx型
else if(root->parent->parent->right == root->parent)
{
//RXr型
if(root->parent->parent->left->color == red)
{
//将父节点的颜色变为黑色
root->parent->color = black;
//将叔叔节点颜色变为黑色
root->parent->parent->left->color = black;
//将祖父节点的颜色变为红色
root->parent->parent->color = red;
//向上检查
rbtreeInsertFixup(rb, root->parent->parent);
}
//RXb型
else if(root->parent->parent->left->color == black)
{
//如果是RLb型
if(root->parent->left == root)
{
//将root变成其父节点
root = root->parent;
//对root的这棵树左旋
rightRoate(rb, root);
}
//RRb型
//将其父节点的颜色变为黑色
root->parent->color = black;
//将其祖父节点的颜色变为红色
root->parent->parent->color = red;
//对root的祖父节点的这棵树进行右旋
leftRoate(rb, root->parent->parent);
}
}
//最后再对整棵红黑树的根节点的颜色变为黑色
rb->root->color = black;
}
5.全部代码
//红黑树结构体的定义
typedef struct rbtree_node
{
int val;//数值域
struct rbtree_node* left;//左孩子
struct rbtree_node* right;//右孩子
struct rbtree_node* parent;//父节点
int color;//颜色
}rbNode;
typedef struct rbtree
{
rbNode* root;//表示根节点
rbNode* nil;//外部节点
}rbtree;
//枚举
enum color
{
red,
black
};
//下面我就以枚举对红黑树进行说明
//创建新节点
rbNode* newNode(rbtree* rb, int val)
{
//开辟空间
rbNode* root = (rbNode*)malloc(sizeof(rbNode));
//设置好节点的关系
root->left = rb->nil;
root->right = rb->nil;
root->parent = rb->nil;
//初始化颜色为红色
root->color = red;
//初始化节点的数值域
root->val = val;
return root;
}
//左旋函数
void leftRoate(rbtree* rb, rbNode* root)
{
rbNode* newroot = root->right;
rbNode* T2 = newroot->left;
rbNode* parent = root->parent;//当然这个节点也可以不设置,这里是为了让大家更好理解
root->right = T2;
//修改父节点:T2(如果不为rb->nil),newroot
if(T2 != rb->nil)
T2->parent = root;
if(parent == rb->nil)
{
//说明原本的root为整棵树的根节点
newroot->parent = rb->nil;
rb->root = newroot;
}
else if(parent->right == root)
{
//根节点在父节点的右子树上
newroot->parent = parent;
parent->right = newroot;
}
else if(parent->left == root)
{
//根节点在父节点的左子树上
newroot->parent = parent;
parent->left = newroot;
}
root->parent = newroot;//把这一个放到下面就是为了能够完成上面的工作
newroot->left = root;
root = newroot;
}
//右旋函数跟左旋对称
void rightRoate(rbtree* rb, rbNode* root)
{
rbNode* newroot = root->left;
rbNode* T2 = newroot->right;
rbNode* parent = root->parent;//当然这个节点也可以不设置,这里是为了让大家更好理解
root->left = T2;
//修改父节点:T2(如果不为rb->nil),newroot
if(T2 != rb->nil)
T2->parent = root;
if(parent == rb->nil)
{
//说明原本的root为整棵树的根节点
newroot->parent = rb->nil;
rb->root = newroot;
}
else if(parent->right == root)
{
//根节点在父节点的右子树上
newroot->parent = parent;
parent->right = newroot;
}
else if(parent->left == root)
{
//根节点在父节点的左子树上
newroot->parent = parent;
parent->left = newroot;
}
root->parent = newroot;//把这一个放到下面就是为了能够完成上面的工作
newroot->right = root;
root = newroot;
}
//先序遍历
void preTraversal(rbtree* rb, rbNode* root)
{
if(root == rb->nil)
return;
printf("数值域: %d , 颜色: %d\n",root->val, root->color);
preTraversal(rb, root->left);
preTraversal(rb, root->right);
}
//中序遍历
void midTraversal(rbtree* rb, rbNode* root)
{
if(root == rb->nil)
return;
midTraversal(rb, root->left);
printf("数值域: %d , 颜色: %d\n",root->val, root->color);
midTraversal(rb, root->right);
}
//这里我选择的是后序遍历进行销毁
void rbtreeDestroy(rbtree* rb, rbNode* root)
{
if(root == rb->nil)
return;
rbtreeDestroy(rb, root->left);
rbtreeDestroy(rb, root->right);
free(root);
root = NULL;
}
//检查红黑树并修正
void rbtreeInsertFixup(rbtree* rb, rbNode* root)
{
if(root->parent->color == black)
return;
//LXx型
if(root->parent->parent->left == root->parent)
{
//LXr型
if(root->parent->parent->right->color == red)
{
//将父节点的颜色变为黑色
root->parent->color = black;
//将叔叔节点颜色变为黑色
root->parent->parent->right->color = black;
//将祖父节点的颜色变为红色
root->parent->parent->color = red;
//向上检查
rbtreeInsertFixup(rb, root->parent->parent);
}
//LXb型
else if(root->parent->parent->right->color == black)
{
//如果是LRb型
if(root->parent->right == root)
{
//将root变成其父节点
root = root->parent;
//对root的这棵树左旋
leftRoate(rb, root);
}
//LLb型
//将其父节点的颜色变为黑色
root->parent->color = black;
//将其祖父节点的颜色变为红色
root->parent->parent->color = red;
//对root的祖父节点的这棵树进行右旋
rightRoate(rb, root->parent->parent);
}
}
//RXx型
else if(root->parent->parent->right == root->parent)
{
//RXr型
if(root->parent->parent->left->color == red)
{
//将父节点的颜色变为黑色
root->parent->color = black;
//将叔叔节点颜色变为黑色
root->parent->parent->left->color = black;
//将祖父节点的颜色变为红色
root->parent->parent->color = red;
//向上检查
rbtreeInsertFixup(rb, root->parent->parent);
}
//RXb型
else if(root->parent->parent->left->color == black)
{
//如果是RLb型
if(root->parent->left == root)
{
//将root变成其父节点
root = root->parent;
//对root的这棵树左旋
rightRoate(rb, root);
}
//RRb型
//将其父节点的颜色变为黑色
root->parent->color = black;
//将其祖父节点的颜色变为红色
root->parent->parent->color = red;
//对root的祖父节点的这棵树进行右旋
leftRoate(rb, root->parent->parent);
}
}
//最后再对整棵红黑树的根节点的颜色变为黑色
rb->root->color = black;
}
//插入节点函数
void rbnodeInsert(rbtree* rb, rbNode* root)
{
//这里我使用的是迭代法,当然也可以使用递归
if(rb->root == NULL)
{
rb->root = root;
rb->root->color = black;
return;
}
rbNode* cur = rb->root;//用于寻找空位
rbNode* y = NULL;//用于记录cur的父节点
while(cur != rb->nil)
{
y = cur;
if(cur->val < root->val)
cur = cur->right;
else if(cur->val > root->val)
cur = cur->left;
else if(cur->val == root->val)
return;//红黑树中不允许出现两个相同的节点
}
cur = root;
cur->parent = y;
//修正y的子树
if (y->val > cur->val)
{
y->left = root;
}
else if (y->val < cur->val)
{
y->right = root;
}
//对这棵红黑树进行检查并修正
rbtreeInsertFixup(rb,root);
}
int main()
{
rbtree* rb = (rbtree*)malloc(sizeof(rbtree));
rb->nil = (rbNode*)malloc(sizeof(rbNode));
rb->nil->color = black;
rb->root = NULL;
int arr[] = { 10,50,60,62,65,70};
for(int i=0; i<6; i++)
{
rbNode* node = newNode(rb, arr[i]);
rbnodeInsert(rb, node);
}
printf("先序遍历:\n");
preTraversal(rb, rb->root);
printf("中序遍历:\n");
midTraversal(rb, rb->root);
rbtreeDestroy(rb, rb->root);
free(rb);
rb = NULL;
return 0;
}
运行结果:
通过简单的推演,我们就可以将这棵树画出来:
与我们开始的那棵树一样,这就说明我们的红黑树构建成功了
至于删除节点的函数由于太复杂了,以后有时间了一定会出删除节点的博客
6.红黑树的总结
我们构建红黑树的时候需要注意这里的左旋右旋与平衡二叉树的区别,还有就是插入节点的时候的各种情况的注意,那么期待在下一站的相遇!!!加纳!