自学内容网 自学内容网

【C++】红黑树

🔥个人主页🔥:孤寂大仙V
🌈收录专栏🌈:C++从小白到高手
🌹往期回顾🌹:【C++】AVL树
🔖流水不争,争的是滔滔不息

红黑树的概念

红⿊树是⼀棵⼆叉搜索树,他的每个结点增加⼀个存储位来表⽰结点的颜色,可以是红色或者黑色。通过对任何⼀条从根到叶⼦的路径上各个结点的颜色进行约束,红⿊树确保没有⼀条路径会⽐其他路径⻓出2倍,因而是接近平衡的。

红黑树的规则

  1. 每个结点不是红色就是黑色

  2. 根结点是黑色的

  3. 如果⼀个结点是红色的,则它的两个孩⼦结点必须是黑色的,也就是说任意⼀条路径不会有连续的红色结点。

  4. 对于任意⼀个结点,从该结点到其所有NULL结点的简单路径上,均包含相同数量的⿊⾊结点
    在这里插入图片描述
    红⿊树如何确保最⻓路径不超过最短路径的2倍的?

  5. 由规则4可知,从根到NULL结点的每条路径都有相同数量的黑色结点,所以极端场景下,最短路径
    就就是全是⿊⾊结点的路径,假设最短路径长度为bh(black height)。

  6. 由规则2和规则3可知,任意⼀条路径不会有连续的红⾊结点,所以极端场景下,最⻓的路径就是⼀
    ⿊⼀红间隔组成,那么最⻓路径的⻓度为2*bh。

  7. 综合红⿊树的4点规则而言,理论上的全⿊最短路径和⼀黑⼀红的最⻓路径并不是在每棵红⿊树都
    存在的。假设任意⼀条从根到NULL结点路径的⻓度为x,那么bh <= h <= 2*bh。

红黑树的效率

假设N是红⿊树树中结点数量,h最短路径的⻓度,那么2的h次方-1<=2的2*h次方-1 , 由此推出h≈logN,也就是意味着红⿊树增删查改最坏也就是⾛最⻓路径 2 ∗ logN,那么时间复杂度还是O(logN)。

红⿊树的表达相对AVL树要抽象⼀些,AVL树通过⾼度差直观的控制了平衡。红⿊树通过4条规则的颜色约束,间接的实现了近似平衡,他们效率都是同⼀档次,但是相对而言,插⼊相同数量的结点,红⿊树的旋转次数是更少的的,因为他对平衡的控制没那么严格。

在这里插入图片描述

红黑树的实现

红黑树的结构

// 枚举值表⽰颜⾊
enum Colour
{
RED,
BLACK
};
// 这⾥我们默认按key/value结构实现
template<class K, class V>
struct RBTreeNode
{
// 这⾥更新控制平衡也要加⼊parent指针
pair<K, V> _kv;
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
Colour _col;
RBTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
,_parent(nullptr)
{}
};
template<class K, class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
public:
private:
Node* _root = nullptr;
};

红黑树的插入

插入过程

  1. 插⼊⼀个值按⼆叉搜索树规则进⾏插⼊,插⼊后我们只需要观察是否符合红⿊树的4条规则。
  2. 如果是空树插⼊,新增结点是⿊⾊结点。如果是⾮空树插⼊,新增结点必须红⾊结点,因为⾮空树
    插⼊,新增⿊⾊结点就破坏了规则4,规则4是很难维护的。
  3. ⾮空树插⼊后,新增结点必须红⾊结点,如果⽗亲结点是⿊⾊的,则没有违反任何规则,插⼊结束
  4. ⾮空树插⼊后,新增结点必须红⾊结点,如果⽗亲结点是红⾊的,则违反规则3。进⼀步分析,c是
    红⾊,p为红,g必为⿊,这三个颜⾊都固定了,关键的变化看u的情况,需要根据u分为以下⼏种
    情况分别处理。

说明:下图中假设我们把新增结点标识为c (cur),c的⽗亲标识为p(parent),p的⽗亲标识为
g(grandfather),p的兄弟标识为u(uncle)。

情况1 变色

c为红,p为红,g为黑,u存在且为红,则将p和u变黑,g变红。在把g当做新的c,继续往上更新。分析:因为p和u都是红色,g是黑色,把p和u变黑,左边子树路径各增加⼀个⿊⾊结点,g再变红,相当于保持g所在⼦树的黑色结点的数量不变,同时解决了c和p连续红⾊结点的问题,需要继续往上更新是因为,g是红⾊,如果g的夫亲还是红色,那么就还需要继续处理;如果g的⽗亲是黑色,则处理结束了;如果g就是整棵树的根,再把g变回黑色。
在这里插入图片描述

在这里插入图片描述
情况1只变色不旋转。

情况2 单旋+变色

c为红,p为红,g为⿊,u不存在或者u存在且为⿊,u不存在,则c⼀定是新增结点,u存在且为⿊,则c⼀定不是新增,c之前是⿊⾊的,是在c的⼦树中插⼊,符合情况1,变⾊将c从⿊⾊变成红⾊,更新上来的。
分析:p必须变⿊,才能解决,连续红⾊结点的问题,u不存在或者是⿊⾊的,这⾥单纯的变⾊⽆法解决问题,需要旋转+变⾊。

g
PU
C

如果p是g的左,c是p的左,那么以g为旋转点进⾏右单旋,再把p变⿊,g变红即可。p变成课这颗树新的根,这样⼦树⿊⾊结点的数量不变,没有连续的红⾊结点了,且不需要往上更新,因为p的⽗亲是⿊⾊还是红⾊或者空都不违反规则。

g
up
c

如果p是g的右,c是p的右,那么以g为旋转点进⾏左单旋,再把p变⿊,g变红即可。p变成课这颗树新的根,这样⼦树⿊⾊结点的数量不变,没有连续的红⾊结点了,且不需要往上更新,因为p的⽗亲是⿊⾊还是红⾊或者空都不违反规则。

在这里插入图片描述

情况3 双旋+变色

c为红,p为红,g为⿊,u不存在或者u存在且为⿊,u不存在,则c⼀定是新增结点,u存在且为⿊,则c⼀定不是新增,c之前是⿊⾊的,是在c的⼦树中插⼊,符合情况1,变⾊将c从⿊⾊变成红⾊,更新上来的。
分析:p必须变⿊,才能解决,连续红⾊结点的问题,u不存在或者是⿊⾊的,这⾥单纯的变⾊⽆法解决问题,需要旋转+变⾊。

g
p u
c

如果p是g的左,c是p的右,那么先以p为旋转点进⾏左单旋,再以g为旋转点进⾏右单旋,再把c变⿊,g变红即可。c变成课这颗树新的根,这样⼦树⿊⾊结点的数量不变,没有连续的红⾊结点了,且不需要往上更新,因为c的⽗亲是⿊⾊还是红⾊或者空都不违反规则。

g
u p
c

如果p是g的右,c是p的左,那么先以p为旋转点进⾏右单旋,再以g为旋转点进⾏左单旋,再把c变⿊,g变红即可。c变成课这颗树新的根,这样⼦树⿊⾊结点的数量不变,没有连续的红⾊结点了,且不需要往上更新,因为c的⽗亲是⿊⾊还是红⾊或者空都不违反规则。
在这里插入图片描述

红黑树的代码实现

#include<iostream>
using namespace std;

enum Colour
{
RED,
BlACK
};

template<class K,class V>
struct RBTreeNode
{
pair<K, V> _kv;
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
Colour _col;

RBtreeNode(const pair<K, V>& kv)
:_kv(kv)
,_left(nullptr)
,_righr(nullptr)
,_parent(nullptr)
{}
};

template<class K,class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
public:
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->_left;
}
else if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}

cur = new Node(kv);
cur->_col = RED;
if (parent->_kv.first > kv.first)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}

cur->_parent = parent;

while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
//  g
//p   u
if (parent == grandfather->_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 (cur==parent->_left)
{
//   g
// p   u
//c          插入节点在父左边 单旋
RtateR(grandparent);
parent->_col = BlACK;
grandfather->_col = RED;
}
else
{
//   g
// p   u
//  c       插入节点在父右  双旋
RtateLR(grandfather);
parent->_col = BlACK;
grandfather->_col = RED;
}

break;
}
}
else
{
//  parent==grandfather->_right
//  g
//u   p

Node* uncle = grandfather->_left;
if (uncle && uncle->_col == RED)
{
uncle->_col = parent->_col = BlACK;
grandfather->_col = RED;

cur = grandfather;
parent = cur->_parent;
}
else
{
if (cur == parent->_right)
{
RtateL(grandfather);

parent->_col = BlACK;
grandfather->_col = RED;
}
else
{
RtateRL(grandfather);

parent->_col = BlACK;
grandfather->_col = RED;
}

break;
}
}
}

_root->_col = BlACK;
return true;
}
//右旋
void RtateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;

parent->_left = subLR;
if (subLR)
{
subLR->_parent = parent;
}

Node* Pparent = parent->_parent;

subL->_right = parent;
parent->_parent = subL;

if (parent == _root)
{
subL=_root;
subL->_parent = nullptr;
}
else
{
if (Pparent->_left = parent)
{
Pparent->_left = subL;
}
else
{
Pparent->_left = subL;
}

subL->_parent = Pparent;
}
}
//左旋
void RtateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;

parent->_right = subRL;
if (subRL)
{
subRL->_parent = parent;
}

Node* Pparent = parent->_parent;

subR->_left = parent;
parent->_parent = subR;

if (parent == _root)
{
subR = _root;
subR->_parent = nullptr;
}
else
{
if (Pparent->_left = parent)
{
Pparent->_left = subR;
}
else
{
Pparent->_right = subR;
}

subR->_parent = Pparent;
}
}

//左右旋
void RtateLR(Node* parent)
{
RtateL(parent->_left);
RtateR(parent);
}
//右左旋
void RtateRL(Node* parent)
{
RtateR(parent->_right);
RtateL(parent);
}
private:
Node* _root = nullptr;
};


原文地址:https://blog.csdn.net/GGDxianv/article/details/143576040

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!