C++学习笔记---023
C++之set和map的底层红黑树封装操作的详解+图解
前言:
前面篇章学习了C++对于AVL树的知识认识和了解,接下来继续学习,C++的红黑树等知识。
/知识点汇总/
1、C++中红黑树的简单介绍
1.1、红黑树概念
红黑树(Red-Black Tree)是一种自平衡的二叉搜索树,其中每个节点包含一个额外的位来表示其颜色属性,通常是红色或黑色。红黑树的平衡并不是完全对称的,但它通过颜色和一系列的性质来保证搜索、插入和删除操作的时间复杂度为O(log^n)。
1.2、红黑树性质/规则
1.每个节点要么是红色,要么是黑色。
2.根节点是黑色的。
3.每个叶节点(NIL或空节点)是黑色的。
4.如果一个节点是红色的,则它的两个子节点都是黑色的(从每个叶子到根的所有路径上不能有两个连续的红色节点)。
5.从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点(这保证了树的平衡)。
为了方便理解如图解所示: 图解参考优秀博主:link
1.3、map和set底层逻辑之红黑树
在C++中,标准模板库(STL)中的std::map、std::set、std::multimap和std::multiset通常使用红黑树(或类似的自平衡搜索树)作为其内部实现,以提供高效的搜索、插入和删除操作。这些容器都保证了元素的有序性,并提供了相应的迭代器和访问函数。
2、红黑树的封装实现
为了红黑树同时适配map和set,作为底层实现所以需要更改之前的程序,添加迭代器、泛型编程和仿函数接口,使得程序更加健壮和规范,更加提升效率。所以就以封装的参数和仿函数,最后迭代器的思路编写文章。
2.1、insert的插入理解和图解
新插入节点,插入红色,因为插入黑色,必然影响规则4,而红色,可能影响规则3
情况1:
cur(current)为红,p(parent)为红,g(grandfather)为黑,u(uncle)存在且为红。-- - 将p, u变黑,g变红
g是否可以不变?不行, 因为g所在的树,可能是整个树的子树,不变红就可能影响整个规则4
1.如果g是根,再次变红
2.如果g不是根,cur指向g,继续向上变色调整直到,变到g的p为黑色或根结束
情况2:
cur(current)为红,p(parent)为红,g(grandfather)为黑,u(uncle)不存在。-- - 将p, u变黑,g变红
1.p为g的左孩子,cur为p的左孩子,则右单旋
2.相反p为g的右孩子,cur为p的右孩子,则左单旋
情况3:
cur(current)为红,p(parent)为红,g(grandfather)为黑,u(uncle)存在且为黑。-- - 将p变黑,g变红,再右单旋
相反,左单旋
其次,注意双旋的情况
2.1.1、情况1:叔叔存在且为红色,图解
2.1.2、情况2:叔叔不存在,图解
2.1.3、情况3:叔叔存在且为黑色,图解
2.2、IsBalance红黑树判断是否平衡
判断是否平衡,遵循以下三点实现,建议学完AVL树再来理解会更轻松。
1.连续的红色节点false
2.每条路径黑色节点数量相等。 – 深度(前序)遍历dfs
每个节点记录一个值(形参):根到当前节点路径中黑色节点的数量值。
3.根节点是黑色
bool IsBalance()
{
if (_root->_col == RED)
{
return false;
}
//基准值/参考值 -- 求得最左路径黑色节点值
int refNum = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
{
++refNum;
}
cur = cur->_left;
}
return _Check(_root, 0, refNum);
}
private:
bool _Check(Node* root,int BlackNum,const int refNum)
{
if (root == nullptr)
{
if (refNum != BlackNum)
{
cout << "存在黑色节点的数量不相等的路径" << endl;
return false;
}
//cout << "BlackNum: " << BlackNum << endl;
return true;
}
if (root->_col == RED && root->_parent->_col == RED)
{
cout << root->_kv.first << "->出现连续红色,不平衡" << endl;
return false;
}
if (root->_col == BLACK)
{
BlackNum++;
}
return _Check(root->_left, BlackNum, refNum) && _Check(root->_right, BlackNum, refNum);
}
2.3、红黑树封装前的完整代码
#define _CRT_SECURE_NO_WARNINGS 1
//红黑树的构造
/**/
#include <iostream>
#include <vector>
using namespace std;
enum Colour
{
BLACK,
RED
};
template<class K, class V>
struct RBTreeNode
{
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
pair<K, V> _kv;
Colour _col; // balance factor平衡因子
RBTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _col(RED)
{
}
};
template<class K, class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
public:
//新插入节点,插入红色,因为插入黑色,必然影响规则4,而红色,可能影响规则3
//情况1:cur(current)为红,p(parent)为红,g(grandfather)为黑,u(uncle)存在且为红。 --- 将p,u变黑,g变红
// g是否可以不变?不行,因为g所在的树,可能是整个树的子树,不变红就可能影响整个规则4
// 1.如果g是根,再次变红
// 2.如果g不是根,cur指向g,继续向上变色调整直到,变到g的p为黑色或根结束
//情况2:cur(current)为红,p(parent)为红,g(grandfather)为黑,u(uncle)不存在。--- 将p,u变黑,g变红
// 1.p为g的左孩子,cur为p的左孩子,则右单旋
// 2.相反p为g的右孩子,cur为p的右孩子,则左单旋
//情况3:cur(current)为红,p(parent)为红,g(grandfather)为黑,u(uncle)存在且为黑。--- 将p变黑,g变红,再右单旋
//相反,左单旋
//其次,注意双旋的情况
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
//插入根的颜色参数 -- 黑色
_root->_col = BLACK;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(kv);
//新增节点颜色参数给红色
cur->_col = RED;
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
//确定p,g,u颜色
//如果p颜色是黑色就结束,是红色就继续调整
while (parent && parent->_col == RED)
{
//关键看u的情况
Node* grandfather = parent->_parent;
//判断g和u的位置 -- 左/右单旋
if (parent == grandfather->_left)//右单旋
{
Node* uncle = grandfather->_right;
//判断u的情况
//1.u存在且为红色 -- 变色即可(p,u变黑,g变红)
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//继续调整
cur = grandfather;
parent = cur->_parent;
}
else//2.u不存在或存在且为黑色 --- p变黑,g变红 + 右单旋/双旋
{
if (cur == parent->_left)
{
// g
// p (u)
// c
//右单旋
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// g
// p (u)
// c
//左右双旋
RotateL(parent);//左单旋p为旋转点
RotateR(grandfather);//右单旋g为旋转点
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
else//左单旋
{
Node* uncle = grandfather->_left;
//判断u的情况
//1.u存在,且u为红色 -- 直接变色即可,p和u变黑色,g变红色
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//继续调整
cur = grandfather;
parent = cur->_parent;
}
else//2.u不存在或存在且为黑色 --- p变黑,g变红 + 左单旋/双旋
{
if (cur == parent->_right)
{
// g
// (u) p
// c
//左单旋
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// g
// (u) p
// c
//右左双旋
RotateR(parent);//左单旋p为旋转点
RotateL(grandfather);//右单旋g为旋转点
cur->_col = BLACK;//注意,旋转后,cur已经与p交换了
grandfather->_col = RED;
}
break;
}
}
}
//始终保持根为黑
_root->_col = BLACK;
return true;
}
//右单旋
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
subL->_right = parent;
Node* ppNode = parent->_parent;
parent->_parent = subL;
if (parent == _root)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
}
//左单旋
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
subR->_left = parent;
Node* ppNode = parent->_parent;
parent->_parent = subR;
if (parent == _root)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (ppNode->_right == parent)
{
ppNode->_right = subR;
}
else
{
ppNode->_left = subR;
}
subR->_parent = ppNode;
}
}
//中序打印
void InOrder()
{
_InOrder(_root);
cout << endl;
}
//判断是否平衡
//1.连续的红色节点false
//2.每条路径黑色节点数量相等。 -- 深度(前序)遍历dfs
//每个节点记录一个值(形参):根到当前节点路径中黑色节点的数量值。
//3.根节点是黑色
bool IsBalance()
{
if (_root->_col == RED)
{
return false;
}
//基准值/参考值 -- 求得最左路径黑色节点值
int refNum = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
{
++refNum;
}
cur = cur->_left;