红黑树是一种二叉搜索树,但在每个结点上增加了一个存储位用于表示结点的颜色,这个颜色可以是红色的,也可以是黑色的,因此我们称之为红黑树。
红黑树通过对任何一条从根到叶子的路径上各个结点着色方式的限制,确保没有一条路径会比其他路径长出两倍,因此红黑树是近似平衡的。
红黑树有以下五点性质:
1. 每个结点不是红色就是黑色。
2. 根结点是黑色的。
3. 如果一个结点是红色的,则它的两个孩子结点是黑色的。
4. 对于每个结点,从该结点到其所有后代叶子结点的简单路径上,均包含相同数目的黑色结点。
5. 每个叶子结点都是黑色的(此处的叶子结点指定是空结点)。
我们这里直接实现C语言的红黑树,为了方便后序的旋转操作,将红黑树的结点定义为三叉链结构,除此之外还新加入了一个成员变量,用于表示结点的颜色。
typedef enum _Color_
{
RED,
BLACK
}Color;
typedef struct _RBTreeNode_
{
struct _RBTreeNode_ *_left;
struct _RBTreeNode_ *_right;
struct _RBTreeNode_ *_parent;
int _data;
Color _color;
} RBTreeNode;
为什么构造结点时,默认将结点的颜色设置为红色?
当我们向红黑树插入结点时,若我们插入的是黑色结点,那么插入路径上黑色结点的数目就比其他路径上黑色结点的数目多了一个,即破坏了红黑树的性质4,此时我们就需要对红黑树进行调整。
若我们插入红黑树的结点是红色的,此时如果其父结点也是红色的,那么表明出现了连续的红色结点,即破坏了红黑树的性质3,此时我们需要对红黑树进行调整;但如果其父结点是黑色的,那我们就无需对红黑树进行调整,插入后仍满足红黑树的要求。
总结一下:
权衡利弊后,我们在构造结点进行插入时,默认将结点的颜色设置为红色。
红黑树插入结点的逻辑分为三步:
红黑树在插入结点后是如何调整的?
实际上,在插入结点后并不是一定会对红黑树进行调整,若插入结点的父结点是黑色的,那么我们就不用对红黑树进行调整,因为本次结点的插入并没有破坏红黑树的五点性质。
只有当插入结点的父结点是红色时才需要对红黑树进行调整,因为我们默认插入的结点就是红色的,如果插入结点的父结点也是红色的,那么此时就出现了连续的红色结点,因此需要对红黑树进行调整。
因为插入结点的父结点是红色的,说明父结点不是根结点(根结点是黑色的),因此插入结点的祖父结点(父结点的父结点)就一定存在。
红黑树调整时具体应该如何调整,主要是看插入结点的叔叔(插入结点的父结点的兄弟结点),根据插入结点叔叔的不同,可将红黑树的调整分为三种情况。
此时为了避免出现连续的红色结点,我们可以将父结点变黑,但为了保持每条路径黑色结点的数目不变,因此我们还需要将祖父结点变红,再将叔叔变黑。这样一来既保持了每条路径黑色结点的数目不变,也解决了连续红色结点的问题。
但调整还没有结束,因为此时祖父结点变成了红色,如果祖父结点是根结点,那我们直接再将祖父结点变成黑色即可,此时相当于每条路径黑色结点的数目都增加了一个。
但如果祖父结点不是根结点的话,我们就需要将祖父结点当作新插入的结点,再判断其父结点是否为红色,若其父结点也是红色,那么又需要根据其叔叔的不同,进而进行不同的调整操作。
因此,情况一的抽象图表示如下:
注意: 叔叔存在且为红时,cur结点是parent的左孩子还是右孩子,调整方法都是一样的。
需要注意:
抽象图表示如下:
说明一下: 当直线关系为,parent是grandfather的右孩子,cur是parent的右孩子时,就需要先进行左单旋操作,再进行颜色调整。
若祖孙三代的关系是折现(cur、parent、grandfather这三个结点为一条折现),则我们需要先进行双旋操作,再进行颜色调整,颜色调整后这棵被旋转子树的根是黑色的,因此无需继续往上进行处理。
抽象图表示如下:
说明一下: 当折现关系为,parent是grandfather的右孩子,cur是parent的左孩子时,就需要先进行右左双旋操作,再进行颜色调整。
在这种情况下的cur结点一定是新插入的结点,而不可能是由情况一变化而来的,因为叔叔不存在说明在parent的下面不可能再挂黑色结点了,如下图:
如果插入前parent下面再挂黑色结点,就会导致图中两条路径黑色结点的数目不相同,而parent是红色的,因此parent下面自然也不能挂红色结点,所以说这种情况下的cur结点一定是新插入的结点。
和情况二一样,若祖孙三代的关系是直线(cur、parent、grandfather这三个结点为一条直线),则我们需要先进行单旋操作,再进行颜色调整,颜色调整后这棵被旋转子树的根结点是黑色的,因此无需继续往上进行处理。
抽象图表示如下:
说明一下: 当直线关系为,parent是grandfather的右孩子,cur是parent的右孩子时,就需要先进行左单旋操作,再进行颜色调整。
若祖孙三代的关系是折现(cur、parent、grandfather这三个结点为一条折现),则我们需要先进行双旋操作,再进行颜色调整,颜色调整后这棵被旋转子树的根是黑色的,因此无需继续往上进行处理。
抽象图表示如下:
说明一下: 当折现关系为,parent是grandfather的右孩子,cur是parent的左孩子时,就需要先进行右左双旋操作,再进行颜色调整。
场景:祖父节点.右孩子 = 父亲节点 父亲节点.右孩子 = 当前节点
此时以父亲节点为根进行变换
该图未画出来,建议读者自己画出该图,进行分析
// 左单旋 g.right = p p.right = cur
void leftrotate(RBTreeNode *grandparent) // g
{
RBTreeNode *parent = grandparent->_right; // p
RBTreeNode *pgrandparent = grandparent->_parent; // g的parent
grandparent->_right = parent->_left;
if(parent->_left)
{
parent->_left->_parent = grandparent;
}
parent->_left = grandparent; // p的左孩子设置为g
grandparent->_parent = parent; // g的parent设置为p
if (pgrandparent == NULL) // 如果g的parent == null
{
_root = parent; // 根节点设置为p
_root->_parent = NULL; // 根节点parent设NULL
}
else
{
if (grandparent == pgrandparent->_left) // 如果g为左孩子
{
pgrandparent->_left = parent; // 设置g的parent的左孩子为p
}
else
{
pgrandparent->_right = parent; // 设置g的parent的右孩子为p
}
parent->_parent = pgrandparent; // 设置p的parent为g的parent
}
}
场景:祖父节点.左孩子 = 父亲节点 父亲节点.左孩子 = 当前节点
此时以父亲节点为根进行变换
右旋代码中的注释与图1是对应的
// 右单旋 g.left = p p.left = cur
void rightrotate(RBTreeNode *grandparent) // g
{
RBTreeNode *parent = grandparent->_left; // p
RBTreeNode *uncle = grandparent->_right; // u
RBTreeNode *pgrandparent = grandparent->_parent; // g的parent
grandparent->_left = parent->_right; // c变成g的左孩子
if(parent->_right) // 如果c不为空, c的parent置为 g
{
parent->_right->_parent = grandparent;
}
parent->_right = grandparent; // p的右孩子设置为g
grandparent->_parent = parent; // g的parent设置为p
if (pgrandparent == NULL) // 如果g的parent == null
{
_root = parent; // 根节点设置为p
_root->_parent = NULL; // 根节点parent设NULL
}
else
{
if (grandparent == pgrandparent->_left) // 如果g为左孩子
{
pgrandparent->_left = parent; // 设置g的parent的左孩子为p
}
else
{
pgrandparent->_right = parent; // 设置g的parent的右孩子为p
}
parent->_parent = pgrandparent; // 设置p的parent为g的parent
}
}
全部代码如下,可在linux下进行编译运行,gcc rbtree.c即可
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <stdbool.h>
typedef enum _Color_
{
RED,
BLACK
}Color;
typedef struct _RBTreeNode_
{
struct _RBTreeNode_ *_left;
struct _RBTreeNode_ *_right;
struct _RBTreeNode_ *_parent;
int _data;
Color _color;
} RBTreeNode;
RBTreeNode *_root = NULL;
//中序遍历子函数
void _Inorder(RBTreeNode* root)
{
if (root == NULL)
return;
_Inorder(root->_left);
printf("%d ", root->_data);
_Inorder(root->_right);
}
void Inorder()
{
_Inorder(_root);
printf("\n");
}
//判断是否为红黑树的子函数
bool _ISRBTree(RBTreeNode* root, int count, int BlackCount)
{
if (root == NULL) //该路径已经走完了
{
if (count != BlackCount)
{
printf("error: 黑色结点的数目不相等");
return false;
}
return true;
}
if (root->_color == RED&&root->_parent->_color == RED)
{
printf("error: 存在连续的红色结点");
return false;
}
if (root->_color == BLACK)
{
count++;
}
return _ISRBTree(root->_left, count, BlackCount) && _ISRBTree(root->_right, count, BlackCount);
}
bool ISRBTree()
{
bool ret = false;
if (_root == NULL) //空树是红黑树
{
return true;
}
if (_root->_color == RED)
{
printf("error: 根结点为红色");
return false;
}
//找最左路径作为黑色结点数目的参考值
RBTreeNode* cur = _root;
int BlackCount = 0;
while (cur)
{
if (cur->_color == BLACK)
BlackCount++;
cur = cur->_left;
}
int count = 0;
ret = _ISRBTree(_root, count, BlackCount);
printf("this is%sa rb tree\n", ret ? " ":" not ");
return ret;
}
// 左单旋 g.right = p p.right = cur
void leftrotate(RBTreeNode *grandparent) // g
{
RBTreeNode *parent = grandparent->_right; // p
RBTreeNode *pgrandparent = grandparent->_parent; // g的parent
grandparent->_right = parent->_left;
if(parent->_left)
{
parent->_left->_parent = grandparent;
}
parent->_left = grandparent; // p的左孩子设置为g
grandparent->_parent = parent; // g的parent设置为p
if (pgrandparent == NULL) // 如果g的parent == null
{
_root = parent; // 根节点设置为p
_root->_parent = NULL; // 根节点parent设NULL
}
else
{
if (grandparent == pgrandparent->_left) // 如果g为左孩子
{
pgrandparent->_left = parent; // 设置g的parent的左孩子为p
}
else
{
pgrandparent->_right = parent; // 设置g的parent的右孩子为p
}
parent->_parent = pgrandparent; // 设置p的parent为g的parent
}
}
// 右单旋 g.left = p p.left = cur
void rightrotate(RBTreeNode *grandparent) // g
{
RBTreeNode *parent = grandparent->_left; // p
RBTreeNode *uncle = grandparent->_right; // u
RBTreeNode *pgrandparent = grandparent->_parent; // g的parent
grandparent->_left = parent->_right; // c变成g的左孩子
if(parent->_right) // 如果c不为空, c的parent置为 g
{
parent->_right->_parent = grandparent;
}
parent->_right = grandparent; // p的右孩子设置为g
grandparent->_parent = parent; // g的parent设置为p
if (pgrandparent == NULL) // 如果g的parent == null
{
_root = parent; // 根节点设置为p
_root->_parent = NULL; // 根节点parent设NULL
}
else
{
if (grandparent == pgrandparent->_left) // 如果g为左孩子
{
pgrandparent->_left = parent; // 设置g的parent的左孩子为p
}
else
{
pgrandparent->_right = parent; // 设置g的parent的右孩子为p
}
parent->_parent = pgrandparent; // 设置p的parent为g的parent
}
}
//左右双旋
void lr_rotate(RBTreeNode* grandparent)
{
leftrotate(grandparent->_left); // 以 parent为grandparent进行左旋
rightrotate(grandparent);
}
//右左双旋
void rl_rotate(RBTreeNode* grandparent)
{
rightrotate(grandparent->_right);
leftrotate(grandparent);
}
void insertdata(int data)
{
if (_root == NULL) //若红黑树为空树,则插入结点直接作为根结点
{
_root = malloc(sizeof(RBTreeNode));
_root->_data = data;
_root->_color = BLACK; //根结点必须是黑色
_root->_left = NULL;
_root->_right = NULL;
_root->_parent = NULL;
return; //插入成功
}
//1、按二叉搜索树的插入方法,找到待插入位置
RBTreeNode* cur = _root;
RBTreeNode* parent = NULL;
while (cur)
{
if (data < cur->_data) //待插入结点的key值小于当前结点的key值
{
//往该结点的左子树走
parent = cur;
cur = cur->_left;
}
else if (data > cur->_data) //待插入结点的key值大于当前结点的key值
{
//往该结点的右子树走
parent = cur;
cur = cur->_right;
}else
{
return;
}
}
//2、将待插入结点插入到树中
cur = malloc(sizeof(RBTreeNode));
cur->_data = data;
cur->_color = RED;
cur->_left = NULL;
cur->_right = NULL;
cur->_parent = NULL;
RBTreeNode* newnode = cur; //记录新插入的结点(便于后序返回)
if (data < parent->_data) //新结点的key值小于parent的key值
{
//插入到parent的左边
parent->_left = cur;
cur->_parent = parent;
}
else //新结点的key值大于parent的key值
{
//插入到parent的右边
parent->_right = cur;
cur->_parent = parent;
}
//3、若插入结点的父结点是红色的,则需要对红黑树进行调整
while (parent&&parent->_color == RED)
{
RBTreeNode* grandfather = parent->_parent; //parent是红色,则其父结点一定存在
if (parent == grandfather->_left) //parent是grandfather的左孩子
{
RBTreeNode* uncle = grandfather->_right; //uncle是grandfather的右孩子
if (uncle&&uncle->_color == RED) //情况1:uncle存在且为红
{
//颜色调整
parent->_color = uncle->_color = BLACK;
grandfather->_color = RED;
//继续往上处理
cur = grandfather;
parent = cur->_parent;
}
else //情况2+情况3:uncle不存在 + uncle存在且为黑
{
if (cur == parent->_left)
{
rightrotate(grandfather); //右单旋
//颜色调整
grandfather->_color = RED;
parent->_color = BLACK;
}
else //cur == parent->_right
{
lr_rotate(grandfather); //左右双旋
//颜色调整
grandfather->_color = RED;
cur->_color = BLACK;
}
break; //子树旋转后,该子树的根变成了黑色,无需继续往上进行处理
}
}
else //parent是grandfather的右孩子
{
RBTreeNode* uncle = grandfather->_left; //uncle是grandfather的左孩子
if (uncle&&uncle->_color == RED) //情况1:uncle存在且为红
{
//颜色调整
uncle->_color = parent->_color = BLACK;
grandfather->_color = RED;
//继续往上处理
cur = grandfather;
parent = cur->_parent;
}
else //情况2+情况3:uncle不存在 + uncle存在且为黑
{
if (cur == parent->_left)
{
rl_rotate(grandfather); //右左双旋
//颜色调整
cur->_color = BLACK;
grandfather->_color = RED;
}
else //cur == parent->_right
{
leftrotate(grandfather); //左单旋
//颜色调整
grandfather->_color = RED;
parent->_color = BLACK;
}
break; //子树旋转后,该子树的根变成了黑色,无需继续往上进行处理
}
}
}
_root->_color = BLACK; //根结点的颜色为黑色(可能被情况一变成了红色,需要变回黑色)
return; //插入成功
}
int main()
{
int i = 0;
for (i = 0; i < 5; i++)
{
insertdata(i);
}
Inorder();
ISRBTree();
return 0;
}