红黑树(RBTree)的简单操作
Posted 遥远的歌s
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了红黑树(RBTree)的简单操作相关的知识,希望对你有一定的参考价值。
红黑树介绍
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过 对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
性质
- 每个结点不是红色就是黑色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子结点是黑色的
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
- 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
如下图为一颗红黑树
比如17这个结点开始,它的左子树以及右子树中黑色结点的个数都是1个。**值得注意的是,叶子节点是黑色,上图中的1,6,11,15,22,27结点中其实还是有两个孩子,只不过该孩子为NULL,**所以是这里所说的值得是空结点。在红黑树中,也需要用到左旋或者右旋,将在下面介绍到。
注意
下列代码中各类解释都会在代码中注释
RBTree的结点定义
enum Color
BLACK,//表示黑色结点
RED//表示红色结点
;
//模板建立,K代表key值,V代表value值
tenmplate<class K,class V>
struct RBTreeNode
pair<K,V> _value;//结点值
Color _color;//结点颜色
RBTreeNode<K,V>* _left;//左孩子指针
RBTreeNode<K,V>* _right;//右孩子指针
RBTreeNode<K,V>* _parent;//父指针
RBTreeNode(const pair<K, V>& value = pair<K,V>())
::_value(value)
, _color(RED)//每个默认为红色
, _parent(nullptr)
, _left(nullptr)
, _right(nullptr)
;
RBTree类
template<class K,class V>
class RBTree
public:
typedef RBTreeNode<K,V> Node;
RBTree()//初始化
:_header(new Node)
//刚开始左右指针都指向_header
_header->_left = _header->_right = _header;
bool insert(const pair<K, V>& val);//红黑树的插入
Node* leftMost()//寻找红黑树最左结点
Node* rightMost()//寻找红黑树的最右结点
void RotateR(Node* parent)//右旋
void RotateL(Node* parent)//左旋
void inorder()//红黑树的中序遍历,输出有序
private:
Node* _header;//指向根结点的指针
;
红黑树的调整及左旋和右旋
因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不 需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:
这里做一个定义:
cur表示当前结点,p表示父结点,g为祖父结点,u为叔叔结点
情况1:
cur为红色,p为红色,u存在且为红色,g为黑色
如下图
解决方法:
1.cur颜色不变,p,u结点变为黑色,g结点变为红色。
2.将g赋给cur,向上继续检查各节点颜色是否合法
3.对于g结点,如果g为根结点,则在调整完成后,将其结点改为黑色即可,如果g是某个结点的子树,则它比存在双亲,这时只需要继续向上检查即可,即重复2步骤。
上图改变后如下图
1.g为根结点
2.g不为根结点
g的颜色变为红色后,继续看g的父结点m结点的颜色开始向上继续检查即可。
情况2:
cur为红,p为红,g为黑,u不存在/u为黑。p为g的左孩子且cur为p的左孩子或p为g的右孩子且cur为p的右孩子
注意:u存在的话则一定为黑色,因为要满足性质4,如果u不存在,则cur插入的新节点的父结点必为红色,g为黑色,则此时不满足性质4,需要调整。
如下图u存在
解决步骤
1.p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反, p为g的右孩子,cur为p的右孩子,则进行左单旋转 (上图展示的是p为g的左孩子,cur为p的左孩子,则需要右旋)
2.p、g变色----->p变黑,g变红
上图调整后如下图
情况3:
cur为红,p为红,g为黑,u不存在/u为黑,p为g的左孩子且cur为p的右孩子或p为g的右孩子且cur为p的左孩子
如下图情况
解决步骤
1.若p为g的左孩子,cur为p的右孩子,则对p结点进行左旋,交换cur和p结点,此时变为情况2中的p为g的左孩子且为红,cur为p的左孩子且为红。
若p为g的右孩子,cur为p的左孩子,则对p结点进行右旋,交换cur和p结点,此时变为了情况2中的p为g的右孩子且为红,cur为p的右孩子且为红。
(上图展示的是p为g的左孩子,cur为p的右孩子的情况)
2.针对旋转后对应的情况2的某种情况继续旋转即可
右旋
void RotateR(Node* parent)
Node* subl = parent->_left;//取parent的左孩子subl
Node* sublr = subl->_right;//取subl的右孩子
parent->_left = sublr;//parent的左孩子变为sublr
subl->_right = parent;//subl的右孩子变为parent
//如果sublr存在不为空,则更新sublr的父亲
if(sublr) sublr->_parent = parent;
//如果parent为根结点
if(_header->_parent == parent)
_header->_parent = subl;//subl更新为根结点
subl->_parent = _header;//s更新ubl的父结点为_header
//如果parent不为根结点
else
Node* p = parent->_parent;//获取parent的父亲结点
subl->_parent = p;//subl的父亲结点更新为p
//如果p的左孩子为parent,则更新p的左孩子为subl
if(p->_left == parent) p->_left = subl;
else p->_right = subl;//否则p的右孩子为subl
parent->_parent = subl;//最后更新parent的父结点
左旋
void RotateL(Node* parent)
Node* subr = parent->_right;//获取parent的右孩子subr
Node* subrl = subr->_left;//获取subr的左孩子subrl
subr->_left = parent;//更新subr的左孩子为parent
parent->_right = subrl;//更新parent的右孩子为subrl
//如果subrl存在则subrl的父亲为parent
if(subrl) subrl->_parent = parent;
//如果parent为根结点
if(_header->_parent == parent)
_header->_parent = subr;//更新新的根结点
subr->_parent = _header;//subr的父亲指向_header
//如果不为根结点
else
Node* p = parent->_parent;//获取parent的父亲结点p
subr->_parent = p;//更新subr的父结点为p
//如果p的左孩子为parent更新p的左孩子为subr
if(p->_left == parent) p->_left = subr;
//否则更新p的右孩子为subr
else p->_right = subr;
//最后更新parent的父亲
parent->_parent = subr;
RBTree的插入
这里和之前说的AVL树的插入一样,只不过调整不一样。
bool insert(const pair<K, V>& val)
//如果插入的为根结点
if(_header->_parent == nullptr)
Node* root = new Node(val);
root->_color = BLACK;//根结点置为黑色
_header->_parent = root;//_header的父亲为root
root->_parent = _header;//root的父亲为_header
//_header的左右指针分别指向root
_header->_left = root;
_header->_right = root;
return true;//擦汗如成功返回true
else//插入的不为根结点
Node* cur = _header->_parent;//获取根结点方便遍历
Node* parent = nullptr;//parent表示cur的父亲
//cur不为空
while(cur)
parent = cur;//parent为cur的父亲结点
//如果key值相同,则返回false
if(cur->_value.first == val.first) return false;
//如果需要插入的结点key值小于当前结点key值,则继续向cur的左孩子遍历
if(cur->_value.first > val.first) cur = cur->_left;
//否则向cur的右孩子遍历
else cur = cur->_right;
//当跳出上述循环后,表示找到一个为空的地方插入val
cur = new Node(val);
//判断cur为parent的左孩子还是右孩子
if(parent->_value.first<val.first) parent->_right = cur;
else parent->_left = cur;
cur->_parent = parent;//更新cur的父亲
//调整判断
//表示cur不为根结点,且有两个连续的红色结点
while(cur != _header->_parent && cur->_parent->_color == RED)
Node* p = cur->_parent;//获取cur的父亲结点
Node* g = p->_parent;//获取cur的祖父结点
//如果p为g的左孩子
if(g->_left == p)
Node* u = g->_right//获取cur的叔叔结点
if(u&&u->_color == RED)//如果u存在且u的颜色为RED
//跟新p,u的颜色为BLACK
p->_color = u->_color = BLACK;
g->_color = RED;//更新g的颜色为RED
cur = g;//继续向上更新检查
else//表示u不存在或者u的颜色为BLACK
//这里可以先判断是否存在情况3,如果存在则变为情况2后继续旋转,否则直接按照情况2的对应情况进行旋转即可
if(cur == p->_right)
RotateL(p);//以p进行左旋
swap(cur, p);//交换cur和p结点
//不论上述是否旋转,都表示此时为情况2
RotateR(g);//以g结点进行右旋
//修改颜色
p->_color = BLACK;
g->_color = RED;
break;
else//否则p为g的右孩子
Node* u = g->_left;//获取cur的叔叔结点u
if(u&&u->_color == RED)
//更新颜色
p->_color = u->_color = BLACK;
g->_color = RED;
cur = g;//继续向上更新
else//表示u不存在或者u的颜色为黑色
if(p->_left == cur)
RotateR(p);//以p进行右旋
swap(cur, p);//交换cur和p结点
//此时为情况2
RotateL(g);
//修改颜色
p->_color = BLACK;
g->_color = RED;
break;
//根颜色置为黑色
_header->_parent->_color = BLACK;
//更新_header的左,右
_header->_left = leftMost();
_header->_right = rightMost();
return true;
获取RBTree的最左和最右结点
该函数会在自我实现红黑树的迭代器时用的,因为红黑树的遍历为中序,迭代器则需要该树的最左和最右结点,将来最右结点相当于begin()位置,最右结点相当于end()。
Node* leftMost()//寻找红黑树最左结点
Node* cur = _header->_parent;
while(cur&&cur->_left)
cur = cur->_left;
return cur;
Node* rightMost()//寻找红黑树的最右结点
Node* cur = _header->_right;
while(cur&&cur->_right)\\
cur = cur->_right;
return cur;
RBTree的遍历
这里就是中序遍历即可,但是注意一下需要封装一下遍历
void inoder()
Node* root = _header->_parent;
_inoder(root);
void _inoder(Node* root)
if(root)
_inoder(root->_left);
cout<<root->_value.first<<" "<<root->_value.second<<endl;
_inoder(root->_right);
以上就是红黑树的简单操作
以上是关于红黑树(RBTree)的简单操作的主要内容,如果未能解决你的问题,请参考以下文章