红黑树详解

一、本博客主要内容:
1、二叉查找树简要介绍
2、2-3树简要介绍
3、2-3树到红黑树的变化过程
4、红黑的性质,特点
5、红黑树构建方法1(不可出现红色右子节点)
6、红黑树构建方法2(可出现红色右子节点)
7、红黑树代码实现链接

二、红黑树背景
讲解红黑树之前,先了解一下二叉查找树和2-3树的概念,这有助于我们理解红黑树的作用及实现过程。
1、二叉查找树性质特点:
二叉查找树是一个操作效率很高的树,平局时间复杂度为O(logN)
二叉查找树的节点键值关系:左节点 < 根 < 右节点
节点的操根据节点键值之间的关系进行操作。
缺点:二叉查找树对树的平衡未进行处理,当数据接近有序时,树退化成一个单链表,操作效率变低,时间复杂度为O(N)。
解决方法:对树的平衡进行调整。
方法:2-3树,红黑树,AVL树。
二叉查找树操作链接:

三、2-3树简要介绍:
1、2-3树是一个完美平衡的查找树,通过增加节点的键值和分支,保证树的平衡。
2、2-3树不是一个二叉树,一个节点由两个子节点/三个子节点组成
3、2-3树的节点由一个或两个键值组成
4、2-3树是自下向上生长的树。二叉查找树是一个自上向下生长的树。
5、2-表示节点有一个键值,两个子树,key < 节点左键值key,则key存在左子树
6、3-表示节点有两个键值,两个/三个子树,key < 节点左键值key,则key存在左子树,key > 节点右键值key,则key存在右子树,key在节点左右键值key之间,则key存在中子树中。
7、2-3树的插入:先通过4找到插入节点
插入节点只有一个key:则将将该key插入到该节点中,然后对键值进行顺序调整(左 < 右)
插入节点有两个键值:将该键值插入到该节点中,按顺序调整后,取出中间key插入到父节点中,同时将该节点拆分成两个节点,作为父节点的左/中子树或中/右子树。若父节点的键值也为2,则同上继续向上处理,直到根节也为3个键值时,取出中间key作为新的根节,将原有节点拆为左右子树。
8、图解及2-3树的插入过程:
《红黑树详解》

9、缺点:2-3树不是一个二叉树,且树是向上生长的,每个节点都不相同,且存在相互变换过程,状态比较多,不容易维护,节点的插入过程比较复杂,需大量的算法进行处理。
解决办法:只要将2-3树进行稍加修改,使用一个标记标记3-节点,将3-节点拆成2-节点,即使用二叉树来表示,再增加一些规定即可保证树的平衡同时,简化节点的操作。即所谓的红黑二叉查找树(红黑树)。

四、2-3树转化为红黑树的过程及红黑树的性质
1、通过2-3树的介绍我们对红黑树的实现有了一定的了解。现在来介绍2-3树到红黑树的变化过程。
《红黑树详解》
通过以上图片,我们对红黑树有了清楚的理解,知道红黑树为何要如此定义了。
现在就是我们如何利用红黑树的性质,构建红黑树了。
《红黑树详解》

五、构建红黑树方法1的主要调整方式:
《红黑树详解》
现在给出红黑树两种实现的主要代码:
方法1:
1、节点数据结构:

enum RB {RED,BLACK};
template<class T,class V>
struct RBTNode {
	T _key;
	V _value;
	enum RB _color;
	int _count;
	RBTNode<T, V> *_left;
	RBTNode<T,V> *_right;

	RBTNode(T key = (T), V value = (V)) 
		:_key(key)
		,_value(value)
		,_color(RED)
		,_count(1)
		,_left(NULL)
		,_right(NULL)
	{}
};

2、节点左旋代码:

RBTNode* rotateLeft(RBTNode *root) {
	//节点位置交换
	RBTNode* root_right = root->_right;
	root->_right = root_right->_left;
	root_right->_left = root;
	//节点的颜色与原来该位置节点颜色保持一致
	root_right->_color = root->_color; 
	root->_color = RED;
	//节点计数器也同样与原来抱持一致
	root_right->_count = root->_count;
	//添加节点位置节点重新计算
	root->_count = size(root->_left) + size(root->_right) + 1;
	return root_right;  //返回新的root
}

3、节点右旋代码:

RBTNode* rotateRight(RBTNode* root) {
	//节点位置交换
	RBTNode* root_left = root->_left;
	root->_left = root_left->_right;
	root_left->_right = root;
	//保持原位置节点的颜色
	root->_color = RED;
	root_left->_color = root->_color;
	//保持原来位置的计数器值
	root_left->_count = root->_count;
	//插入节点位置的节点重新计算
	root->_count = size(root->_left) + size(root->_right) + 1;
	return root_left;
	}

4、节点颜色及调整判断:

bool isRED(RBTNode* node) {
	if (node == NULL) return false;
	if (node->_color == RED) return true;
	else return false;
}

RBTNode* flipColor(RBTNode* root) {
	root->_color = RED;
	root->_left->_color = BLACK;
	root->_right->_color = BLACK;
	return root;
}

5、插入节点:

void put(T key, V value) {
	_root = _put(_root, key, value);  //递归添加
	_root->_color = BLACK; //将根变为黑色
}
RBTNode* _put(RBTNode* root, T key, V value) {
	//创建新节点
	if (root == NULL)  return new RBTNode(key, value);
	//递归查找插入位置
	if (root->_key < key)      root->_right = _put(root->_right, key, value);
	else if (root->_key > key) root->_left = _put(root->_left, key, value);
	else                       root->_value = value;
	//找到了插入节点位置root,并且已将新节点插入
	//递归进行红黑调整(插入节点都为红色),从下向上调整树的颜色,直到根节结束
	//1、插入位置节点为红色,新节点插入在右侧,进行一次左旋转
	//2、插入位置节点为红色,新节点插入在左侧,进行一次右旋转,
	//3、出现左右子节点都为红色节点,将左右子节点变为黑色,根点变为红色
	if (isRED(root->_right) && !isRED(root->_left))       root = rotateLeft(root);  //右旋节点为插入位置的节点
	if (isRED(root->_left) && isRED(root->_left->_left))  root = rotateRight(root);  //注:左旋的节点为插入位置节点的上一个节点
	if (isRED(root->_left) && isRED(root->_right))        root = flipColor(root);  //出现左右子节点都为红色,则进行颜色变换
	//节点数目统计
	root->_count = size(root->_left) + size(root->_right) + 1;
	return root;
}

6、删除节点
节点的删除比较复杂,可以从2-3树了解到,删除一个节点,可以直接删除一个3-节点,不可直接删除2-节点,否则导致树的不平衡。
同样红黑树中可直接删除红色节点,不可直接删除黑色节点,删除黑色节点时需进行调整,调整该节点为红色,然后再删除。
其它的操作与二叉查找树操作相同,可以参考二叉查找树相关的操作。

7、红黑树方法1完整代码链接(含删除操作):
https://github.com/weienjun/GetHub/tree/master/RBTree1

六、构建红黑树方法2
根据以上的方法可以实现一种无红色右子节点的红黑树,但可以从中发现,不许出现红色右子节似乎太过苛刻,不能使树更加趋于平衡,而且多了许多不必要的旋转调整,所以我们可以不考虑该条件,形成第二个种构建红黑树的方法:

《红黑树详解》
根据上面的方法2我们不考虑不许红色右子节点这个条件,也可构建红黑树,只是多了几种调整树的可能,实现的代码同样也会多一些。

构建红黑树方法总结:
1、每次插入红色节点,因为若插入黑色节点,则会改变路径上黑色节点个数,插入红色节点,再看其它条件是否满足,不满足进行调整

颜色调整:
1、插入的节点(cur)的父节点§为黑色,则直接插入

2、插入的节点(cur)的父节点§为红色,则看叔叔节点
2.1、叔叔节点存在且为红色—->将父节点和叔叔节点变为黑色,祖父节点变为红色,
之后若祖父节点为根节点,则再变为黑色
否则,cur,p,g向上移动,即cur = p,再判断cur的父节点,和叔叔节点(循环2步的以上步骤,直到p为黑色节点/根节点为止)。

2.2、叔叔节点不存在或为黑色节点,直线形式—->单旋转
p为g的左孩子,cur为p的左孩子—->右单旋
p为g的右孩子,cur为p的右孩子—->左单旋
再将p变为黑,g变为红。

2.3、叔叔节点为黑色或不存在,折现形式——–>双旋
p为g的左孩子,cur为p的右孩子—->则对p左单旋—>右单旋
p为g的右孩子,cur为p的左孩子—->则对p右单旋—>左单旋
再将p变为黑,g变为红。

节点删除:
节点的删除与AVL树的删除类似,分为5中情况
增删查改的时间复杂度为0(lgN)

构建红黑树方法2代码实现:
1、节点数据结构:

enum Color{RED,BLACK};
template<class T,class V>
struct RBTNode
{
	T _key;
	T _value;
	RBTNode<T,V>* _left;
	RBTNode<T,V>* _right;
	RBTNode<T,V>* _parent;
	Color _col;

	RBTNode(const T& data,const V& value)
	:_key(data)
	,_value(value)
	,_left(NULL)
	,_right(NULL)
	,_parent(NULL)
	,_col(RED)
	{}
};

2、左旋转

//左旋
void RotateL(Node* parent){
	Node* SubR = parent->_right;
	Node* SubRL = SubR->_left;

	parent->_right = SubRL;
	if (SubRL) {//更新SubRL的父亲
		SubRL->_parent = parent;
	}
	SubR->_left = parent;
	Node* pparent = parent->_parent;
	parent->_parent = SubR;
	//处理新的父亲的父亲节点
	SubR->_parent = pparent;
	if (pparent == NULL) {
		_root = SubR;
	}
	else{
		if(parent == pparent->_left) pparent->_left = SubR;
		else pparent->_right = SubR;
		SubR->_parent = pparent;
	}
	_root->_parent = NULL;
}

3、右旋转

void RotateR(Node* parent){
	Node* pparent = NULL;
	Node* SubL = parent->_left;
	Node* SubLR = SubL->_right;

	parent->_left = SubLR;
	if (SubLR) {
		SubLR->_parent = parent;
	}
	SubL->_right = parent;
	pparent = parent->_parent;
	parent->_parent = SubL;
	//处理新的父亲的父亲节点
	SubL->_parent = pparent;
	if(pparent == NULL){
	_root = SubL;
	}
	else{
		if(parent == pparent->_left) pparent->_left = SubL;
		else pparent->_right = SubL;
	SubL->_parent = pparent;
	}
	_root->_parent = NULL;
}

4、插入节点(非递归)

//采用非递归的方式插入节点
bool Insert(const T& key,const V& value){
	if(_root == NULL){
		_root = new Node(key, value);
		_root->_col = BLACK;
		return true;
	}
	Node* parent = _root;
	Node* cur = _root;
	//寻找插入节点
	while(cur){
		parent = cur;
		if(cur->_key < key)      cur = cur->_right;
		else if(cur->_key > key) cur = cur->_left;
		else return false;//数据相同,已存在,不插入
	}
	//插入节点
	cur = new Node(key,value);
	if(parent->_key > key) parent->_left = cur;
	else  parent->_right = cur;
	cur->_parent = parent;
	//调整
	while(parent && parent->_col == RED){
		Node* grandfather = parent->_parent;
		if(parent == grandfather->_left){//父节点在祖父的左
			Node* uncle = grandfather->_right;
			if(uncle && uncle->_col == RED){//叔叔存在为红色--->调整颜色
				parent->_col = BLACK;
				uncle->_col = BLACK;
				grandfather->_col = RED;
				//向上层处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else{ // 叔叔不存在或为黑色---->调整平衡
				if (cur == parent->_right) {//cur在父的左,双旋
					RotateL(parent);//先左旋
					parent = parent->_parent;
				}
				RotateR(grandfather);//右旋
				parent->_col = BLACK;
				grandfather->_col = RED;
			}
		}
		else{ //父在祖父的右	
			Node* uncle = grandfather->_left;
			if(uncle && uncle->_col == RED){//叔叔存在且为红色
				parent->_col = BLACK;
				uncle->_col = BLACK;
				grandfather->_col = RED;
				//向上层处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else{ //叔叔不存在或为黑色
				if (cur == parent->_left) {//折线型,双旋
					RotateR(parent);//右旋 parent;
					parent = parent->_parent;
				}
				RotateL(grandfather);//左旋 parent->_parent;
				parent->_col = BLACK;
				grandfather->_col = RED;
			}
		}
		_root->_col = BLACK;  //将根节点变为黑色
	}  //循环结束
	return true;
}

5、判断是否为红黑树
递归遍历
1、无连续的红色节点否则返回false(j节点为红色,父节点为红色)
2、每条路径黑色节点数量相同
方法:先计算一条路径(最左侧)黑色节点数量,依此为基准,再计算其它路径黑色节点数量,不相同返回false

bool Banlance(){
	if(_root && _root->_col == RED){
		printf("根为红色!\n");
		return false;
	}
	int num = 0;
	Node* cur = _root;
	while(cur){
		if(cur->_col == BLACK) ++num;
		cur = cur->_left;
	}
	int count = 0;
	return _Balance(_root,num,count);
}

bool _Balance(Node* cur,int num,int count){
	if(cur == NULL){
		if(num != count)
			return false;
		return true;
	}
	if(cur->_col == RED && cur->_parent->_col == RED){
		printf("连续的红色节点!\n");
		return false;
	}
	if (cur->_col == BLACK) {
		count++;
	}
	return _Balance(cur->_left,num,count) && _Balance(cur->_right,num,count);
}

构建红黑树方法2代码链接:
https://github.com/weienjun/GetHub/tree/master/RBTree2

    原文作者:算法小白
    原文地址: https://blog.csdn.net/wejboke626/article/details/84971437
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞