红黑树(RBTree)的简单操作

Posted 遥远的歌s

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了红黑树(RBTree)的简单操作相关的知识,希望对你有一定的参考价值。

红黑树介绍

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过 对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
性质

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
    如下图为一颗红黑树

    比如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)的简单操作的主要内容,如果未能解决你的问题,请参考以下文章

java——红黑树 RBTree

红黑树底层实现原理分析及JAVA代码实现

红黑树(RBTREE)之上-------构造红黑树

rbtree

二叉树之红黑树(RBTree)

数据结构 红黑树(RBTree)的原理与实现