tips:这篇需要用到AVLtree的部分知识!
本人之前写的AVLtree, 不妨点击看下或阅读其他大佬的文章!谢谢~~
https://blog.csdn.net/weixin_44024891/article/details/104910691
红黑树(RBtree)是一个相对平衡的二叉搜索树。
为什么是相对呢?
相对于二叉搜索树, 他很平衡, 但对于高度平衡的AVLtree而言,他就显得不那么平衡了。
这块的概念很重要!我们知道二叉搜索树由于它的不平衡, 不规范的插入会导致树的性能下降,在极端情况下还不如链表。 而AVLtree由于过度平衡, 导致插入节点时为了调整平衡因子会花很大的代价,使得性能下降。
而我们RBtree介于两者中间, 拥有双方的优点,有着很好的性能。
那红黑树(RBtree)该如何实现呢?
在设计的时候,我们抛去AVLtree平衡因子的概念, 添加红色、黑色标识, 每个结点要么是黑色、要么是红色。
并且还要满足以下几点要求:
1)、一个结点要么为黑色,要么为红色。
2)、根结点的颜色为黑色
3)、如果一个结点为红色, 则它的两个孩子要为黑色。
4)、对于每一个结点(任意一个),该结点到其所有后代叶子结点路径上, 均包含相同的黑色结点个数。 (重点)
5)、对于每一个结点(任意一个),它的所有路径中最长的路径长度不超过最短路径的2倍。(重点)
6)、每个叶子结点都是黑色的,(此处的叶子结点指的是空结点)(这样定义叶子结点, 是为了区分路径!)
只要满足以上的要求, 就可以实现一个红黑树。并且查找效率很高。
每个条件之间互相约束, 在不满足条件时,需要调整。最终会得到一个高度平衡的树(小于AVLtree)。由于允许最长路径的长度不超过最短路径的2倍, 也给了很大的空间不需要做调整(AVLtree不超过1)。两者的结合得到高效率的RBtree。
那么如何做调整呢?
我这块以插入接口为例。
在找到合适的插入位置之后, 对其判断是否影响到RBtree的满足条件。
插入后大体分为4类情况:
1)、插入结点的父结点为黑色
答:如果父结点为黑色, 我们不需要做调整, 属于正常情况。
2)、插入结点父结点为红色、且叔叔结点存在且为红色(叔叔结点是父亲结点的兄弟结点)
答:将父亲结点和叔叔结点置为黑色。 祖父结点置为红色(祖父结点为父亲结点的父亲), 再以祖父结点为新的起点, 接着往上更新。 (如果祖父结点为根节点, 则停止更新, 并将祖父结点置为黑色)。
3)、插入结点父结点为红色, 叔叔结点存在且为黑色 或 叔叔结点不存在
a、插入的结点和父结点在一边
答:单旋; 以父结点为轴单旋。单旋完毕之后,parent变为黑,gfather变为红,停止调整!(看图)
右旋情况
左旋情况就不列举了, cur和parent结点在gfather的右边,则左旋!其他处理和右旋一样。
b、插入的结点和父结点不在一边
答:双旋; 以父结点为轴单旋一次, 再以祖父结点单旋一次。 cur结点变为黑色, gfather变为红色。(看图)
左右双旋
右左双旋就不介绍了, parent在gfather的右边,cur在parent的左边!其他处理和左右双旋一样!
以上就是插入的全部情况了,代码如下:
为了模仿关联式容器底层实现, 这块存储的元素是键值对(也就是pair类), 代码中提供insert(插入)接口,但未实现erase(删除)接口!
/*
tips: _header是头结点, 不是根结点
_header->_parent = 根结点; 根结点->_parent = _header;
_header->_left = 树的最左结点
_header->_right = 树的最右结点
这样设计是为了方便模拟关联式容器中迭代器的实现!
*/
#include<iostream>
#include<utility> //提供pair类
using namespace std;
//定义枚举常量 - 标志BLACK(黑色)、 RED(红色)
enum COLOR {
BLACK,
RED
};
//封装RBNtree树结点
template<class K, class V>
struct RBNode {
//定义RB树的节点信息
RBNode<K, V>* _left;
RBNode<K, V>* _right;
RBNode<K, V>* _parent;
pair<K, V> _value;
COLOR _color; //定义一个枚举变量
RBNode(const pair<K, V>& value = pair<K, V>()) //相当传递一个匿名的对象
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _value(value) //pair类中自动赋值
, _color(RED)
{
}
};
//RBtree类实现
template<class K, class V>
class RBTree {
public:
typedef RBNode<K, V> Node;
typedef Node* pNode;
RBTree(){
//无参构造 - 空树
_header = new Node;
_header->_left = _header;
_header->_right = _header;
_header->_parent = nullptr;
}
//左旋接口
void RotateL(pNode& parent) {
pNode SubR = parent->_right;
pNode SubRL = SubR->_left;
SubR->_left = parent;
parent->_right = SubRL;
pNode pp = parent->_parent;
if (pp != _header) {
if (pp->_left == parent) {
pp->_left = SubR;
}
else {
pp->_right = SubR;
}
}
else {
_header->_parent = SubR;
}
SubR->_parent = pp;
if (SubRL) {
SubRL->_parent = parent;
}
parent->_parent = SubR;
}
//右旋接口
void RotateR(pNode& parent) {
pNode SubL = parent->_left;
pNode SubLR = SubL->_right;
SubL->_right = parent;
parent->_left = SubLR;
pNode pp = parent->_parent;
if (pp != _header) {
if (pp->_left == parent) {
pp->_left = SubL;
}
else {
pp->_right = SubL;
}
}
else {
_header->_parent = SubL;
}
SubL->_parent = pp;
if (SubLR) {
SubLR->_parent = parent;
}
parent->_parent = SubL;
}
//插入接口
bool insert(const pair<K, V>& value) {
//判断是否为空树
if (_header->_parent == nullptr) {
//创建根节点
pNode root = new Node(value);
root->_color = BLACK; //更改根节点颜色为黑色
//根的父节点 == 头, 头节点的父节点 == 根
root->_parent = _header;
_header->_parent = root;
//头节点的左右节点==根
_header->_left = root;
_header->_right = root;
return true;
}
//从根节点开始查找合适位置
pNode cur = _header->_parent;
pNode parent = nullptr;
while (cur) {
parent = cur;
if (cur->_value.first == value.first)
return false;
else if (cur->_value.first > value.first)
cur = cur->_left;
else
cur = cur->_right;
}
cur = new Node(value);
if (parent->_value.first > cur->_value.first)
parent->_left = cur;
else
parent->_right = cur;
cur->_parent = parent;
//调整阶段
while (cur != _header->_parent && cur->_parent->_color == RED) {
//父亲的节点为红色(红红需要调整,) && 可以判断parent不为根 - 祖父就会存在
parent = cur->_parent;
pNode gfather = parent->_parent;
if (gfather->_left == parent) {
//叔叔节点在右边
pNode uncle = gfather->_right;
if (uncle && uncle->_color == RED) {
//叔叔节点存在, 并且为红
//调整方式1
parent->_color = uncle->_color = BLACK;
gfather->_color = RED;
//继续向上更新
cur = gfather;
}
else {
//叔叔节点存在, 但为黑 or 叔叔不存在
//调整完毕之后对gfather往上的节点无影响, 停止调整
//双旋情况 cur和parent不在同一遍
if (cur == parent->_right) {
RotateL(parent);
swap(parent, cur);
}
//右旋
RotateR(gfather);
parent->_color = BLACK;
gfather->_color = RED;
//退出
break;
}
}
else if (gfather->_right == parent) {
//叔叔节点在左边
pNode uncle = gfather->_left;
if (uncle && uncle->_color == RED) {
//叔叔节点存在, 并且为红
//调整方式1
parent->_color = uncle->_color = BLACK;
gfather->_color = RED;
//持续往上更新
cur = gfather;
}
else {
//叔叔节点存在, 但为黑 or 叔叔不存在
//调整完毕之后对gfather往上的节点无影响, 停止调整
//双旋 - parent和cur不在一边
if (cur == parent->_left) {
RotateR(parent);
swap(cur, parent);
}
//左旋
RotateL(gfather);
parent->_color = BLACK;
gfather->_color = RED;
//退出
break;
}
}
}
//根节点强转为黑色
_header->_parent->_color = BLACK;
//更新, 更新最左节点和最右节点 - 方便迭代器的设计
_header->_left = LeftMost();
_header->_right = RightMost();
return true;
}
//寻找最左节点
pNode LeftMost() {
pNode cur = _header->_parent;
if (!cur) {
//无根节点
return cur;
}
while (cur->_left != nullptr) {
cur = cur->_left;
}
return cur;
}
//寻找最右节点
pNode RightMost() {
pNode cur = _header->_parent;
if (!cur) {
return cur;
}
while (cur->_right != nullptr) {
cur = cur->_right;
}
return cur;
}
//中序遍历
void InOrder() {
_InOrder(_header->_parent);
cout << endl;
}
private:
void _InOrder(const pNode& root) {
if (root) {
_InOrder(root->_left);
cout << '<' << root->_value.first << "-->" << root->_value.second << '>' << endl;
_InOrder(root->_right);
}
}
private:
pNode _header; //定义头部结点, 不是根节点。
};
void test_RBtree() {
RBTree<int, int> rb;
rb.insert(make_pair(4, 7));
rb.insert(make_pair(2, 7));
rb.insert(make_pair(1, 7));
rb.insert(make_pair(10, 7));
rb.insert(make_pair(9, 7));
rb.insert(make_pair(7, 7));
rb.InOrder();
}
int main() {
test_RBtree();
return 0;
}
测试结果:
分析:
1、RBtree相对于AVLtree, 插入比较简单,且效率比较高,调整次数少。
2、在STL关联式容器中map、set、multimap、multiset容器都是采用RBtree作为底层的数据结构。学习RBtree也是对容器学习提供帮助!
3、学习RBtree个人感觉是可以替换AVLtree的。
转载:https://blog.csdn.net/weixin_44024891/article/details/105442405