C++之红黑树
Posted 卷毛小学僧
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++之红黑树相关的知识,希望对你有一定的参考价值。
文章目录
前言
前面一节我们介绍了平衡搜索二叉树AVL树,我们知道,AVL树虽然查找效率很高,但是不能过多的修改,因为它为了保持平衡要不断的进行旋转。我们今天介绍的红黑树也是一种平衡搜索树,不过它所要求的平衡没有AVL树那么严格,因此对它进行修改操作时所要进行的旋转比AVL树要进行的旋转少。
一、概念
红黑树,一种二叉搜索树,它额外在每一个结点上增加一个表示结点颜色的存储位,有Red和Black两种可能。
通过对任意一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长处二倍,因此它是接近平衡的。
二、性质
- 每个结点不是黑色就是红色;
- 根节点是黑色;
- 如果一个结点是红色,那么它的两个孩子结点是黑色;
- 对于每一个结点,从该节点到其所有后代叶子结点的简单路径上,均包含相同数目的黑色结点;
- 每个叶子节点都是黑色(此处值空结点,即NIL结点)。
三、结点的定义
//结点的颜色
enum color
RED,
BLACK
;
//结点的定义
template<typename T>
struct RBnode//红黑树的结点(三叉链)
RBnode(T data)
:_data(data)
, _parent(nullptr)
, _left(nullptr)
, _right(nullptr)
, col(RED)
T _data;//数据
RBnode* _parent;//父节点(为了方便实现旋转)
RBnode* _left;//左孩子
RBnode* _right;//右孩子
color _col;//颜色
;
看到这里,相信大家会有一个疑问:为什么默认新插入的结点颜色是红色?
答:如果我们将新结点的颜色设置为黑色,那么它一定会违背性质4(即,对于每一个结点,从该节点到其所有后代叶子结点的简单路径上,均包含相同数目的黑色结点),这样我们就需要大幅度的在这棵树上进行调整(几乎需要所有路径进行调整),才能使它再次符合性质4;
如果我们将新结点颜色设置为红色,它有可能违背性质3(即,如果一个结点是红色,那么它的两个孩子结点是黑色),也有一定的可能不违背,即使违背性质3它所带来的后果比违背性质4严重性小很多,我们可以通过几次旋转(只对它所在的路径进行调整)使它重新符合性质3。
因为我们新插入一个结点,它不是黑色就是红色,因此一定会违反一条性质,我们选择违反后果较小的性质3。
四、红黑树的结构
五、插入操作
1.插入代码
bool insert(const pair<K, V>& kv)
Node* newnode = new RBnode<K,V>(kv);
if (!_root)//如果根节点为空,则新插入的结直接就是根节点
_root = newnode;
else
Node* cur = _root;
Node* parent = cur;
while (cur)
parent = cur;
if (cur->_kv.first > kv.first)
cur = cur->_left;
else if (cur->_kv.first < kv.first)
cur = cur->_right;
else//树中已经有这个结点,插入失败
return false;
cur = newnode;
if (parent->_kv.first > cur->_kv.first)
parent->_left = cur;
else
parent->_right = cur;
cur->_parent = parent;
Node* Grandpa = parent->_parent;
Node* uncle = nullptr;
while(Grandpa && parent->_col == RED)//如果父节点不是黑色,且父节点不是根结点
if (parent == Grandpa->_left)//定义叔叔结点
uncle = Grandpa->_right;
else
uncle = Grandpa->_left;
if (uncle->_col == RED)//如果叔叔存在且为红
Grandpa->_col = RED;
parent->_col = uncle->_col = BLACK;
else//如果叔叔不存在,或者存在且为黑
//p是g的右孩子,c是p的右孩子(左单旋)
if (parent == Grandpa->_right && cur == parent->_right)
Rotetal(Grandpa);
Grandpa->_col = RED;
parent->_col = BLACK;
//p是g的左孩子,c是p的左孩子(右单旋)
else if (parent == Grandpa->_left && cur == parent->_left)
Rotetal(Grandpa);
Grandpa->_col = RED;
parent->_col = BLACK;
//p是g的左孩子,c是p的右孩子(左右双旋)
else if (parent == Grandpa->_left && cur == parent->_right)
//先以parent为轴进行左单旋
Rotetal(parent);
//再以Grandpa为轴进行右单旋
Rotetar(Grandpa);
//更新颜色
cur->_col = BLACK;
Grandpa->_col = parent->_col = RED;
//p是g的右孩子,c是p的左孩子(右左双旋)
else if (parent == Grandpa->_right && cur == parent->_left)
//先以parent为轴进行右单旋
Rotetar(parent);
//再以Grandpa为轴进行左单旋
Rotetal(Grandpa);
//更新颜色
cur->_col = BLACK;
Grandpa->_col = parent->_col = RED;
//旋转之后就符合性质4了,因此不用再继续更新
break;
cur = parent;
parent = Grandpa;
Grandpa = Grandpa->_parent;
if (_root->_col == RED)//如果到最后更新到根节点,导致根节点为红色,为了满足性质2(根节点是黑色),就要将根结点置为黑色
_root->_col = BLACK;
return true;
2.左单旋
//左单旋
void Rotetal(Node* parent)
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
Node* Grandpa = parent->_parent;
parent->_parent = SubR;
parent->_right = SubRL;
if (SubRL)
SubRL->_parent = parent;
SubR->_parent = Grandpa;
if (!Grandpa)
_root = SubR;
_root->_parent = nullptr;
else
if (parent == Grandpa->_left)
Grandpa->_left = SubR;
else
Grandpa->_right = SubR;
SubR->_left = parent;
3.右单旋
//右单旋
void Rotetar(Node* parent)
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
Node* Grandpa = parent->_parent;
parent->_parent = SubL;
parent->_left = SubLR;
if (SubLR)
SubLR->_parent = parent;
SubL->_parent = Grandpa;
if (!Grandpa)
_root = SubL;
_root->_parent = nullptr;
else
if (parent == Grandpa->_left)
Grandpa->_left = SubL;
else
Grandpa->_right = SubL;
SubL->_right = parent;
4.插入新结点的情况分析与总结
第一步、按照搜索二叉树的规则插入新结点
如果该树是空树,就让新结点作为它的根节点。
先找到要插入的位置,比当前结点小就向左子树寻找,比当前结点大就向右子树寻找。
第二步、分析插入结点后红黑树的性质是否被破坏
新结点默认为红色,
1.如果双亲节点的颜色是黑色,则没有违反红黑树性质,不需要调整;
2.如果双亲节点的颜色是红色,则违反性质4需要进行调整。
为了方便分析,我们约定当前结点为cur©,当前节点的父节点为parent§,当前节点的祖父结点为Grandpa(g),当前结点的叔叔结点为uncle(c).。
- 情况一:c为红色,p为红,g为黑,u存在且为红
只需要将p和u的颜色置为黑色,g的颜色置为红色。
如果g是根节点,调整之后只需要将g改为黑色即可;
如果g是子树,那么g一定有双亲结点,如果g的双亲结点为红色,就需要继续向上调整。 - 情况二:cur为红,p为红,g为黑,u不存在/存在且为黑
-
- 如果u不存在,则说明cur是新增节点。因为如果cur不是新增节点,那么cur和p一定有一个是黑色,那么就不满足性质4(每条路径上的黑色结点的个数相同);
-
- 如果u存在且为黑,则说明cur原本就存在且为黑。现在是红色是因为cur所在子树新增节点导致向上调整颜色的过程中将cur置为红色。
这种情况有四种可能:
- 如果u存在且为黑,则说明cur原本就存在且为黑。现在是红色是因为cur所在子树新增节点导致向上调整颜色的过程中将cur置为红色。
- p是g的左孩子,c是p的左孩子;(要进行右单旋)
以g为轴进行右单旋:
更新结点p为黑色,cur和g为红色。 - p是g的右孩子,c是p的右孩子;(要进行左单旋)
以g为轴进行左单旋:
更新结点p为黑色,cur和g为红色。 - p是g的左孩子,c是p的右孩子;(要进行左右双旋)
先以p为轴进行左单旋,再以g为轴进行右单旋:
更新结点cur为黑色,p和g为红色。 - p是g的右孩子,c是p的左孩子。(要进行右左双旋)
先以p为轴进行左单旋,再以g为轴进行右单旋:
更新结点cur为黑色,p和g为红色。
动态演示:
升序:
降序:
随机插入构建红黑树:
右旋转:
左旋转:
六、验证红黑树
验证红黑树分为两步:
1.检测是否满足二叉搜索树
中序遍历是否为有序序列
2.检测是否满足红黑树性质
代码如下:
bool IsValidRBTree()//验证是否为红黑树
Node* pRoot = GetRoot();
// 空树也是红黑树
if (nullptr == pRoot)
return true;
// 检测根节点是否满足情况
if (BLACK != pRoot->_col)
cout << "违反红黑树性质二:根节点必须为黑色" << endl;
return false;
// 获取任意一条路径中黑色节点的个数
size_t blackCount = 0;
Node* pCur = pRoot;
while (pCur)
if (BLACK == pCur->_col)
blackCount++;
pCur = pCur->_left;
// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
size_t k = 0;
return _IsValidRBTree(pRoot, k, blackCount);
bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
//走到null之后,判断k和black是否相等
if (nullptr == pRoot)
if (k != blackCount)
cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
return false;
return true;
// 统计黑色节点的个数
if (BLACK == pRoot->_col)
k++;
// 检测当前节点与其双亲是否都为红色
Node* pParent = pRoot->_parent;
if (pParent && RED == pParent->_col && RED == pRoot->_col)
cout << "违反性质三:没有连在一起的红色节点" << endl;
return false;
return _IsValidRBTree(pRoot->_left, k, blackCount) &&
_IsValidRBTree(pRoot->_right, k, blackCount);
七、红黑树与AVL树的比较
红黑树和AVL树都是高效的平衡二叉树,增删查改的时间复杂度都是O( l o g 2 N log_2 N log2N),红黑树不追求绝对平衡,只要保证最长路径不超过最短路径的两倍。相对而言,插入和旋转的次数更少,在经常进行增删的结构中性能比AVL树更优,而且红黑树的实现比AVL树简单,因此更加常用。
总结
以上就是今天要讲的内容,本文介绍了C++中红黑树的相关概念。本文作者目前也是正在学习C++相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!
数据结构之红黑树
红黑树
文章目录
本篇博文只讲解了红黑树的插入操作,下面我们来了解一下什么是红黑树。
红黑树的概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
NIL是空结点,这里显示出来是为了表示路径
红黑树和AVLTree不同点:
AVLTree:左右高度差不超过1,严格平衡
红黑树:最长路径不超过最短路径的2倍,近似平衡
严格来说红黑树的效率没有AVLTree高,当AVLTree的旋转多时,此时红黑树的效率就高于AVLTree
红黑树的性质
- 每个结点不是红色就是黑色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子结点必须是黑色的
树里面没有连续的红色节点
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
每条路径都有相同数量的黑色节点(路径不是走到叶子节点,而是走到空节点)
- 每个叶子结点都是黑色的(此处的叶子结点指的是空结点),就像图中的NIL节点
那么是红黑树是如何做到平衡的呢?
通过性质3和4:
由性质四可知,每条路径都有相同的黑色节点,假设有一条路径上都是黑色节点,那么一棵红黑树的最短路径就是这条路径
由性质三可知,红黑树中没有连续的红色节点,那么一棵红黑树的最长路径就是一黑一红,交替出现,因为每条路径的黑色节点是相同的,最长路径不会超过最短路径的两倍
红黑树节点的定义
红黑树节点的颜色我们可以用枚举来定义,我们将红黑树实现成三叉链的结构:
#pragma once
enum Colour
BLACK,
RED
;
template<class K,class V>
struct RBTreeNode
RBTreeNode<K,V>* _left;
RBTreeNode<K,V>* _right;
RBTreeNode<K,V>* _parent;
Colour _col;//颜色
pair<K,V> _kv;//键值对
//构造函数
RBTreeNode(const pair<K,V>& kv)
:_left(nullptr),
:_right(nullptr),
:_parent(nullptr),
:col(RED),
:_kv(kv)
;
有一个问题:为什么颜色初始化,初始化成红色呢?
插入红色可能破坏规则3,但是插入黑色的话一定破坏规则4,因为如果插入黑色节点,该路径黑色节点的数目肯定发生了变化,为了保证所有路径黑色节点数目相同,所有路径都需要做调整,而插入红色,如果父亲节点是黑色节点,那么就不需要做调整,即使出现了连续的红色节点,破坏了规则3,只是破坏了这一条路径,只需要调整该路径,所以选择红色节点
红黑树的插入情况操作
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
- 插入情况一: cur为红,p为红,g为黑,u存在且为红
解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。
插入cur时,已经出现了连续的红节点,所以需要将p变黑,为了保证不同路径的黑色节点数目不变,u也得变黑,那么为什么最后将g改为红呢?因为这棵树可能只是局部的子树,那么要保持他的每条路径黑色节点的数量不变,p和u变黑以后,路径上的黑色节点多了,所以g得变红,g变红一会就得继续往上处理
具象图1:
处理结果:
具象图二(需要继续往上处理的场景):
如果g是根节点,调整完成后,需要将g改为黑色
如果g是子树,g一定有双亲,且g的双亲如果是红色,需要继续向上调整
- 插入情况二:cur为红,p为红,g为黑,u不存在/u存在且为黑
关键看叔叔,情况二相比情况一,只变了叔叔
u存在且为黑:
u存在且为黑的情况时,cur一定不是新增,cur原来是黑的,保证了规则4,cur之所以是红色,是因为cur是作为子树的祖父,是由第一种情况变化过来的
该情况是由情况一转变过来的:
u不存在:
u不存在,那么cur就是新增,因为cur不是新增的话,那么p和cur当中一定有一个黑色节点,此时黑色节点就多了,不满足规则4:每条路径黑色节点数目相同
处理方法:
叔叔不存在时:
p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,p为g的右孩子,cur为p的右孩子,则进行左单旋转,p、g变色–p变黑,g变红
叔叔存在且为黑:
- 插入情况三:cur为红,p为红,g为黑,u不存在/u存在且为黑
插入情况三相比于插入情况二不一样的是,p如果是p的左边,那么cur就是p的右边,p如果是p的右边,那么cur就是p的左边
这种情况做一次旋转是解决不了问题的,需要做两次旋转:
p为g的左孩子,cur为p的右孩子时,则针对p做左单旋转,然后再针对g做右单旋转;相反,p为g的右孩子,cur为p的左孩子,则针对p做右单旋转,再针对g做左单旋转
u不存在:
u存在且为黑:
红黑树的插入代码
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 < cur->_kv.first)
parent->_right = cur;
cur->_parent = parent;
else
parent->_left = cur;
cur->_parent = parent;
//控制平衡,控制颜色
while (parent && parent->_col == RED)
Node* grandfather = parent->_parent;
if (parent == grandfather->_right)
//找到叔叔
Node* uncle = grandfather->_left;
//情况一:uncle存在且为红,进行变色处理即可,并且继续往上更新处理
if (uncle && uncle->_col == RED)
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
//情况二+三:uncle不存在,uncle存在且为黑,进行旋转+变色处理
else
//情况二:单旋+变色
if (cur == parent->_left)
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
else
//情况三:双旋+变色
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
break;
else//parent == greandfather->_left
Node* uncle = grandfather->_right;
if (uncle && uncle->col == RED)
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
else
if (parent->_right == cur)
RotateL(grandfather);
parent->_col = BLACK;
grandfather->COL = RED;
else//parent->_left == cur
//双旋+变色
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
//不用再继续了,不会有连续红色和黑色个数不一样
break;
_root->_col = BLACK;
void RotateR(Node* parent)
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
Node* ppNode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (parent == _root)
_root = subL;
subL->_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;
Node* ppNode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (parent == _root)
_root = subR;
subR->_parent = nullptr;
else
if (ppNode->_left == parent)
ppNode->_left = subR;
else
ppNode->_right = subR;
subR->_parent = ppNode;
红黑树测试代码
void TestRBTree1()
//int a[] = 16, 3, 7, 11, 9, 26, 18, 14, 15 ;
//int a[] = 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 ;
const int n = 1000000;
vector<int> a;
a.reserve(n);
srand(time(0));
for (size_t i = 0; i < n; ++i)
a.push_back(rand());
RBTree<int, int> t1;
for (auto e : a)
t1.Insert(make_pair(e, e));
cout << t1.IsBalance() << endl;
//t1.InOrder();
void _InOrder(Node* root)
if(root == nullptr)
return;
_InOrder(root->_left);
cout<<root->_kv.first<<endl;
_InOrder(root->_right);
void InOrder(Node* root)
_InOrder(_root);
bool CheckRR(Node* cur)
if(cur == nullptr)
return true;
if(cur->_col == RED && cur->_parent == RED)
cout<<"违反规则三,存在连续的红色节点"<<endl;
return false;
return CheckRR(cur->_left)
&& CheckRR(cur->_right);
bool CheckBlackNum(Node* cur,int blackNum,int benchmark)
if(cur == nullptr)
if(blackNum != benchmark)
cout<<"违反规则四:黑色节点的数量不相等"<<endl;
return false;
return true;
if(cur->_col == BLACK)
++blackNum;
return CheckBlackNum(cur->_left,balckNum,benchmark);
&& CheckBlackNum(cur->_right,balckNum,benchmark);
bool IsBalance()
if(_root == nullptr)
return true;
if(_root->_col == RED)
cout<<"根节点是红色,违反规则二"<<endl;
return false;
//算出最左路径的黑色节点的个数作为基准值
int benchmark = 0;
Node* cur = _root;
while(cur)
if(cur->_col == BLACK)
++benchmark;
cur = cur->_left;
int blackNum = 0;
//检查规则三
return CheckRR(_root) && CheckBlackNum(root,blackNum,benchmark);
//每条路径的黑色节点都求出来,提前算出一个基准值进行比较
红黑树整体代码
template<class K,class V>
class RBTree
typedef RBTreeNode<K,V> Node;
public:
RBTree()
:_root(nullptr)
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;
//插入红色可能破坏规则3,插入黑色一定破坏规则4,破坏规则4比较难处理
//新增节点就新增一个红色节点
//1、如果新增节点的父亲是黑色,没有影响
//2、如果新增节点的父亲是红色,则产生了连续的红色节点,破坏了规则3,需要分情况处理:
cur = new Node(kv);
cur->_col = RED;
//插入链接
if(parent->_kv.first < kv.first)
parent->_right = cur;
cur->_parent = parent;
else
parent->_left = cur;
cur->_parent = parent;
//控制平衡,控制颜色
while(parent && parent->_col == RED)
Node* grandfather = parent->_parent;
if(parent == grandfather->_right)
//找到叔叔
Node* uncle = grandfather->_left;
//情况一:uncle存在且为红,进行变色处理即可,并且继续往上更新处理
if(uncle && uncle->_col == RED)
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
//情况二+三:uncle不存在,uncle存在且为黑,进行旋转+变色处理
else
//情况二:单旋+变色
if(cur == parent->_left)
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
else
//情况三:双旋+变色
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
break;
else//parent == greandfather->_left
Node* uncle = grandfather->_right;
if(uncle && uncle ->col == RED)
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
else
if(parent->_right == cur)
RotateL(grandfather);
parent->_col = BLACK;
grandfather->COL = RED;
else//parent->_left == cur
//以上是关于C++之红黑树的主要内容,如果未能解决你的问题,请参考以下文章