目录
00.引入
和AVL树一样,红黑树也是一种自平衡的二叉搜索树。AVL树通过平衡因子调整子树的高度,确保树的高度差在 -1 到 1 之间。而红黑树通过在每个节点上增加一个存储位表示节点的颜色,可以是RED或BLACK。通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有任何一条路径会是其他路径的两倍以上长,因而接近平衡。
01.红黑树的性质
红黑树具有以下几种性质,这些性质确保了树的平衡性和高效性:
1.节点颜色:每个节点要么是红色,要么是黑色。
2.根节点颜色:根节点始终是黑色。
3.红色节点限制:没有两个红色节点可以相连。
4.黑色节点数量:从任何节点到其每个叶子节点的所以路径包含相同数量的黑色节点。
5.叶子节点:所有的叶子节点(此处都表示为NULL节点)都是黑色。
其实还有一条潜规则:每一个新插入的节点初始都为红色。
为什么以上性质就可以保证最长路径的节点个数不会超过最短路径节点个数的两倍?
通过性质1/2/5可知,在判断性质4时(计算路径上黑色节点的数量),不需要考虑头节点以及叶子节点,只需要考虑中间节点即可,再根据性质3,可以列举以下几种情况:
1.中间没有黑色节点:
路径: 黑->黑,黑->红->黑。
2.中间只有1个黑色节点:
路径: 黑->黑->黑,黑->红->黑->黑,黑->黑->红->黑,黑->红->黑->红->黑
3.中间有2个黑色节点:
路径: 黑->黑->黑->黑,黑->红->黑->黑->黑,黑->黑->红->黑->黑,黑->黑->黑->红->黑
。。。黑->红->黑->红->黑->红->黑
可以看出,最短路径就是全黑的;最长路径是黑、红相间的。因而我们可以找到规律:
假设中间有n个黑色节点:
最短路径:n+2(中间节点加上首尾)
最长路径:n+(n+1)+2=2n+3(中间有n个黑色节点最多可以有n+1个红色节点)
所以最长路径(2n+3)永远比最短路径的2倍(2n+4)小1。
02.红黑树的定义
红黑树的底层结构在代码实现中使用节点结构体和数来表示。节点结构体 保存每个节点的数据、指向左右子节点、父节点的指针、以及平衡因子;树类管理插入、删除等操作。
// 节点的颜色
enum Color { RED, BLACK };
// 红黑树节点的定义
template<class T>
struct RBTreeNode
{
RBTreeNode(const T& data = T(), Color color = RED)
: _pLeft(nullptr)
, _pRight(nullptr)
, _pParent(nullptr)
, _data(data)
, _color(color)
{}
RBTreeNode<T>* _pLeft; // 节点的左孩子
RBTreeNode<T>* _pRight; // 节点的右孩子
RBTreeNode<T>* _pParent; // 节点的双亲
T _data; // 节点的值域
Color _color; // 节点的颜色
};
在节点的定义中,我们将节点默认颜色给成红色,这是因为如果定义成黑色的话,根据性质4,每一次插入节点都需要重新调整树,而定义成红色,就只需要在父节点也为红色时进行调整,一定程度上保证了插入的效率。
03.红黑树的插入
红黑树是在二叉搜索树的基础上加上平衡限制条件的,因此红黑树的插入可以分为两步:
1.按照二叉搜索树的规则插入新节点
// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false
// 注意:为了简单起见,本次实现红黑树不存储重复性元素
bool Insert(const T& data)
{
if (!_pRoot) // 根节点为空直接插入
{
_pRoot = new Node(data);
_pRoot->_color = BLACK; // 设置根节点为黑色
return true;
}
Node* pParent = nullptr;
Node* pCur = _pRoot;
while (pCur)
{
pParent = pCur;
if (data == pCur->_data) // 元素重复,不进行插入
return false;
else if (data > pCur->_data) // 大于向右走
pCur = pCur->_pRight;
else // 小于向左走
pCur = pCur->_pLeft;
}
pCur = new Node(data);
// 更新父节点的子节点指针
if (data < pParent->_data)
pParent->_pLeft = pCur;
else
pParent->_pRight = pCur;
pCur->_pParent = pParent;
return true;
}
二叉搜索树的插入过程不在赘述,详细可跳转这篇博客【C++】二叉搜索树。
2.检测新节点插入后,是否满足红黑树的性质
因为新插入节点默认为红色,因此如果双亲节点是黑色,则满足性质,不需要调整;如果双亲节点为红色,违反了性质3,需要进行调整,调整过程需要进行分类讨论:
第一种情况:
我们调整红黑树的底层逻辑是通过改变某些节点的颜色,使整棵树符合性质,看下面这棵树:
cur表示新插入节点,parent为父节点,grandpa为父节点的父节点,uncle为grandpa的另一个节点
(下面用c,p,g,u分别表示cur,parent,grandpa,uncle)
此时c和p是连续的红色节点。破坏了性质3,那么我直接把p的颜色改成黑色,性质3被修复,但同时又破坏了性质4,于是我把u的颜色也改成黑色,这样以g为根节点的这棵树确实被修复了。
但要注意,如果g不是根节点呢
此时性质4还是被破坏了,为此,把g的颜色改成红色
这样就完成了修复。但如果是下面这种情况呢
g的双亲也为红色,此时需要继续向上调整。。
所以我们得到了第一种情况:
1.uncle节点存在且为红色
a.祖父节点为根 或 祖父的双亲为黑色
只需要修改u和p的颜色即可
b.祖父节点不为根 且 祖父的双亲为红色
此时需要进一步向上调整
将cur的位置换到grandpa,p、u、g一并转换,此时u一定存在,如果u不存在,则违反了性质4
第二种情况:
2.uncle节点不存在
1.单旋的情况
学习过AVL树可以知道,对于这样的一棵树我们需要通过旋转的方法调整高度,关于旋转的原理,可以调整这篇博客【C++】AVL树
旋转完成之后,我们需要调整颜色,此时p成为了这棵树的根节点,为了满足条件4,我们把p颜色改成黑,g颜色改成红:
这是左单旋的情况,右单旋同理
2.双旋的情况
双旋分为 右左 与 左右 两种情况,右左即p是g的右孩子,c是p的左孩子。右左情况下我们需要对p进行右单旋,在对g进行左单旋,最后调整c和g的颜色,过程如下:
左右的情况同理
3.uncle节点存在且为黑色
a.单旋的情况
此时cur一定不是新插入节点,这种情况就是1.b向上调整的后续了,此时依旧要用到旋转操作,和情况2同理:
b.双旋的情况
由此看来,情况2和情况3其实可以合并。
下面给出插入以及旋转的完整代码:
template<class T>
class RBTree
{
typedef RBTreeNode<T> Node;
public:
RBTree()
{
_pRoot = nullptr;
}
// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false
// 注意:为了简单起见,本次实现红黑树不存储重复性元素
bool Insert(const T& data)
{
if (!_pRoot) // 根节点为空直接插入
{
_pRoot = new Node(data);
_pRoot->_color = BLACK; // 设置根节点为黑色
return true;
}
Node* pParent = nullptr;
Node* pCur = _pRoot;
while (pCur)
{
pParent = pCur;
if (data == pCur->_data) // 元素重复,不进行插入
return false;
else if (data > pCur->_data) // 大于向右走
pCur = pCur->_pRight;
else // 小于向左走
pCur = pCur->_pLeft;
}
pCur = new Node(data);
// 更新父节点的子节点指针
if (data < pParent->_data)
pParent->_pLeft = pCur;
else
pParent->_pRight = pCur;
pCur->_pParent = pParent;
while (pParent && pParent->_color == RED)
{
Node* pGrandpa = pParent->_pParent;
if (!pGrandpa)
pParent->_color = BLACK;
else {
if (pParent == pGrandpa->_pRight)
{
Node* pUncle = pGrandpa->_pLeft;
// uncle存在且为红
if (pUncle && pUncle->_color == RED)
{
pUncle->_color = pParent->_color = BLACK;
pGrandpa->_color = RED;
// 继续向上调整
pCur = pGrandpa;
pParent = pCur->_pParent;
}
// uncle不存在或者uncle为黑
else
{
// 右右的情况
if (pCur == pParent->_pRight)
{
RotateL(pGrandpa);
pParent->_color = BLACK;
pGrandpa->_color = RED;
}
// 右左的情况
else
{
RotateR(pParent);
RotateL(pGrandpa);
pCur->_color = BLACK;
pGrandpa->_color = RED;
}
break;
}
}
else // pParent = pGrandpa->_pLeft
{
Node* pUncle = pGrandpa->_pRight;
// uncle存在且为红
if (pUncle && pUncle->_color == RED)
{
pUncle->_color = pParent->_color = BLACK;
pGrandpa->_color = RED;
// 继续向上调整
pCur = pGrandpa;
pParent = pCur->_pParent;
}
// uncle不存在或者uncle为黑
else
{
// 左左的情况
if (pCur == pParent->_pLeft)
{
RotateR(pGrandpa);
pParent->_color = BLACK;
pGrandpa->_color = RED;
}
// 左右的情况
else
{
RotateL(pParent);
RotateR(pGrandpa);
pCur->_color = BLACK;
pGrandpa->_color = RED;
}
break;
}
}
}
}
_pRoot->_color = BLACK;
return true;
}
private:
// 左单旋
void RotateL(Node* pParent)
{
Node* pCur = pParent->_pRight;
pParent->_pRight = pCur->_pLeft; // cur的左孩子作为parent的右孩子
if (pCur->_pLeft)
pCur->_pLeft->_pParent = pParent;
if (pParent->_pParent) // parent不是根节点
{
pCur->_pParent = pParent->_pParent;
if (pParent->_pParent->_pLeft == pParent)
pParent->_pParent->_pLeft = pCur;
else
pParent->_pParent->_pRight = pCur;
}
else // parent是根节点
{
_pRoot = pCur;
pCur->_pParent = nullptr;
}
pParent->_pParent = pCur;
pCur->_pLeft = pParent;
}
// 右单旋
void RotateR(Node* pParent)
{
Node* pCur = pParent->_pLeft;
pParent->_pLeft = pCur->_pRight; // cur的右孩子作为parent的左孩子
if (pCur->_pRight)
pCur->_pRight->_pParent = pParent;
if (pParent->_pParent) // parent不是根节点
{
pCur->_pParent = pParent->_pParent;
if (pParent->_pParent->_pLeft == pParent)
pParent->_pParent->_pLeft = pCur;
else
pParent->_pParent->_pRight = pCur;
}
else // parent是根节点
{
_pRoot = pCur;
pCur->_pParent = nullptr;
}
pParent->_pParent = pCur;
pCur->_pRight = pParent;
}
private:
Node* _pRoot;
};
04.验证红黑树
检验上述代码,就得从红黑树的5条性质入手,性质1、5不需要验证,性质2也很容易验证,实际上我们主要验证的是性质3(没有连续的红色节点)以及性质4(每条路径上黑色节点数量一致)
对于性质4,我们可以先记录某一条路径(这里选取最左叶节点这条路径)的黑色节点数量,再遍历每一条路径,只要有一条路径上的黑色节点数量与前者不一样,就返回false,这里选择用递归的方式遍历红黑树。
对于性质3,我们需要再遍历的过程中,碰到红色节点,就验证一下双亲的颜色,如果也为红,则违反性质,返回false
完整代码如下:
// 检测红黑树是否为有效的红黑树,注意:其内部主要依靠_IsValidRBTRee函数检测
bool IsValidRBTRee()
{
if (!_pRoot)
return true;
if (RED == _pRoot->_color)
return false;
Node* pCur = _pRoot;
size_t blackcount = 0;
while (pCur)
{
if (BLACK == pCur->_color)
++blackcount;
pCur = pCur->_pLeft;
}
return _IsValidRBTRee(_pRoot, blackcount, 0);
}
bool _IsValidRBTRee(Node* pRoot, size_t blackCount, size_t pathBlack) {
if (!pRoot) {
// 如果到达叶子节点,检查黑色节点数量
return pathBlack == blackCount;
}
// 检查当前节点的颜色
if (pRoot->_color == RED) {
// 如果是红色,检查父节点是否也是红色
if (pRoot->_pParent && pRoot->_pParent->_color == RED) {
return false; // 违反红黑树性质
}
}
// 更新路径上的黑色节点数量
if (pRoot->_color == BLACK) {
pathBlack++;
}
// 递归检查左子树和右子树
return _IsValidRBTRee(pRoot->_pLeft, blackCount, pathBlack) &&
_IsValidRBTRee(pRoot->_pRight, blackCount, pathBlack);
}
最后我们用一万组包含10个1~100随机数的数组验证红黑树:
#include<iostream>
using namespace std;
#include"My_RBTree.h"
#include<vector>
bool test1()
{
srand(time(0)); // 初始化随机种子
// 生成10000组随机向量
for (int i = 0; i < 10000; ++i) {
RBTree<int> BT;
vector<int> nums;
// 每组随机生成10个整数(范围为 1 到 100)
for (int j = 0; j < 10; ++j) {
nums.push_back(rand() % 100 + 1);
}
// 将每个整数插入 B 树中
for (const int& num : nums) {
BT.Insert(num);
}
// 检查 B 树
if (!BT.IsValidRBTRee()) {
cout << "树不平衡,测试失败!\n";
for (auto it : nums)
{
cout << it << ", ";
}
cout << endl;
return 1;
}
}
cout << "所有10000组随机树均平衡,测试通过!\n";
}
int main()
{
test1();
return 0;
}
运行结果:
验证通过!
附上完整代码:
//My_RBTree.h
#pragma once
// 节点的颜色
enum Color { RED, BLACK };
// 红黑树节点的定义
template<class T>
struct RBTreeNode
{
RBTreeNode(const T& data = T(), Color color = RED)
: _pLeft(nullptr)
, _pRight(nullptr)
, _pParent(nullptr)
, _data(data)
, _color(color)
{}
RBTreeNode<T>* _pLeft; // 节点的左孩子
RBTreeNode<T>* _pRight; // 节点的右孩子
RBTreeNode<T>* _pParent; // 节点的双亲
T _data; // 节点的值域
Color _color; // 节点的颜色
};
template<class T>
class RBTree
{
typedef RBTreeNode<T> Node;
public:
RBTree()
{
_pRoot = nullptr;
}
// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false
// 注意:为了简单起见,本次实现红黑树不存储重复性元素
bool Insert(const T& data)
{
if (!_pRoot) // 根节点为空直接插入
{
_pRoot = new Node(data);
_pRoot->_color = BLACK; // 设置根节点为黑色
return true;
}
Node* pParent = nullptr;
Node* pCur = _pRoot;
while (pCur)
{
pParent = pCur;
if (data == pCur->_data) // 元素重复,不进行插入
return false;
else if (data > pCur->_data) // 大于向右走
pCur = pCur->_pRight;
else // 小于向左走
pCur = pCur->_pLeft;
}
pCur = new Node(data);
// 更新父节点的子节点指针
if (data < pParent->_data)
pParent->_pLeft = pCur;
else
pParent->_pRight = pCur;
pCur->_pParent = pParent;
while (pParent && pParent->_color == RED)
{
Node* pGrandpa = pParent->_pParent;
if (!pGrandpa)
pParent->_color = BLACK;
else {
if (pParent == pGrandpa->_pRight)
{
Node* pUncle = pGrandpa->_pLeft;
// uncle存在且为红
if (pUncle && pUncle->_color == RED)
{
pUncle->_color = pParent->_color = BLACK;
pGrandpa->_color = RED;
// 继续向上调整
pCur = pGrandpa;
pParent = pCur->_pParent;
}
// uncle不存在或者uncle为黑
else
{
// 右右的情况
if (pCur == pParent->_pRight)
{
RotateL(pGrandpa);
pParent->_color = BLACK;
pGrandpa->_color = RED;
}
// 右左的情况
else
{
RotateR(pParent);
RotateL(pGrandpa);
pCur->_color = BLACK;
pGrandpa->_color = RED;
}
break;
}
}
else // pParent = pGrandpa->_pLeft
{
Node* pUncle = pGrandpa->_pRight;
// uncle存在且为红
if (pUncle && pUncle->_color == RED)
{
pUncle->_color = pParent->_color = BLACK;
pGrandpa->_color = RED;
// 继续向上调整
pCur = pGrandpa;
pParent = pCur->_pParent;
}
// uncle不存在或者uncle为黑
else
{
// 左左的情况
if (pCur == pParent->_pLeft)
{
RotateR(pGrandpa);
pParent->_color = BLACK;
pGrandpa->_color = RED;
}
// 左右的情况
else
{
RotateL(pParent);
RotateR(pGrandpa);
pCur->_color = BLACK;
pGrandpa->_color = RED;
}
break;
}
}
}
}
_pRoot->_color = BLACK;
return true;
}
// 检测红黑树中是否存在值为data的节点,存在返回该节点的地址,否则返回nullptr
Node* Find(const T& data)
{
Node* pCur = _pRoot;
while (pCur)
{
if (data == pCur->_data)
return pCur;
if (data > pCur->_data)
pCur = pCur->_pRight;
else
pCur = pCur->_pLeft;
}
return nullptr;
}
// 检测红黑树是否为有效的红黑树,注意:其内部主要依靠_IsValidRBTRee函数检测
bool IsValidRBTRee()
{
if (!_pRoot)
return true;
if (RED == _pRoot->_color)
return false;
Node* pCur = _pRoot;
size_t blackcount = 0;
while (pCur)
{
if (BLACK == pCur->_color)
++blackcount;
pCur = pCur->_pLeft;
}
return _IsValidRBTRee(_pRoot, blackcount, 0);
}
private:
bool _IsValidRBTRee(Node* pRoot, size_t blackCount, size_t pathBlack) {
if (!pRoot) {
// 如果到达叶子节点,检查黑色节点数量
return pathBlack == blackCount;
}
// 检查当前节点的颜色
if (pRoot->_color == RED) {
// 如果是红色,检查父节点是否也是红色
if (pRoot->_pParent && pRoot->_pParent->_color == RED) {
return false; // 违反红黑树性质
}
}
// 更新路径上的黑色节点数量
if (pRoot->_color == BLACK) {
pathBlack++;
}
// 递归检查左子树和右子树
return _IsValidRBTRee(pRoot->_pLeft, blackCount, pathBlack) &&
_IsValidRBTRee(pRoot->_pRight, blackCount, pathBlack);
}
// 左单旋
void RotateL(Node* pParent)
{
Node* pCur = pParent->_pRight;
pParent->_pRight = pCur->_pLeft; // cur的左孩子作为parent的右孩子
if (pCur->_pLeft)
pCur->_pLeft->_pParent = pParent;
if (pParent->_pParent) // parent不是根节点
{
pCur->_pParent = pParent->_pParent;
if (pParent->_pParent->_pLeft == pParent)
pParent->_pParent->_pLeft = pCur;
else
pParent->_pParent->_pRight = pCur;
}
else // parent是根节点
{
_pRoot = pCur;
pCur->_pParent = nullptr;
}
pParent->_pParent = pCur;
pCur->_pLeft = pParent;
}
// 右单旋
void RotateR(Node* pParent)
{
Node* pCur = pParent->_pLeft;
pParent->_pLeft = pCur->_pRight; // cur的右孩子作为parent的左孩子
if (pCur->_pRight)
pCur->_pRight->_pParent = pParent;
if (pParent->_pParent) // parent不是根节点
{
pCur->_pParent = pParent->_pParent;
if (pParent->_pParent->_pLeft == pParent)
pParent->_pParent->_pLeft = pCur;
else
pParent->_pParent->_pRight = pCur;
}
else // parent是根节点
{
_pRoot = pCur;
pCur->_pParent = nullptr;
}
pParent->_pParent = pCur;
pCur->_pRight = pParent;
}
private:
Node* _pRoot;
};
//test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
#include"My_RBTree.h"
#include<vector>
bool test1()
{
srand(time(0)); // 初始化随机种子
// 生成10000组随机向量
for (int i = 0; i < 10000; ++i) {
RBTree<int> BT;
vector<int> nums;
// 每组随机生成10个整数(范围为 1 到 100)
for (int j = 0; j < 10; ++j) {
nums.push_back(rand() % 100 + 1);
}
// 将每个整数插入 B 树中
for (const int& num : nums) {
BT.Insert(num);
}
// 检查 B 树
if (!BT.IsValidRBTRee()) {
cout << "树不平衡,测试失败!\n";
for (auto it : nums)
{
cout << it << ", ";
}
cout << endl;
return 1;
}
}
cout << "所有10000组随机树均平衡,测试通过!\n";
}
void test2()
{
RBTree<int> BT1;
vector<int> v1 = { 70,25,97,18,51,85 };
for (auto it : v1)
{
BT1.Insert(it);
}
cout << BT1.IsValidRBTRee() << endl;
}
int main()
{
test1();
return 0;
}
以上就是红黑树的详解与模拟实现过程,欢迎在评论区留言,码文不易,觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~
标签:color,红黑树,pRoot,C++,pCur,pParent,搞懂,data,节点 From: https://blog.csdn.net/dhgiuyawhiudwqha/article/details/143210484