封装map和set所需第二步:红黑树

1.红黑树的规则

1.每个结点只有黑或红色

2.根结点只为黑色

3.红节点的两孩子只为黑色

4.所有节点的绝对路径的黑色节点数都相同(绝对路径:从根节点到每一个子节点的过程)

2.红黑树的一些特点

1.设每一条路径有x个黑色结点,有最短路径为x,最长路径为x*2。

下图为最长:

下图为最短:

设N为该树的所有节点数,x为最短路径,有2^x-1 <=N<=2^(x+1)-1.即n=logN.可得一次查找的效率还是为O(logN).

下面开始代码实现:

1.结点和大框

cpp 复制代码
//枚举封装颜色
enum
{
	RED,
	BLACK
};
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode* _left;
	RBTreeNode* _right;
	RBTreeNode* _parent;
	pair<K, V> _val;
	int _col;
	RBTree(const pair<K, V>& val)
		:_val(val)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		//结点颜色优先为红色
		//因为违反条件4的代价很大
		,_col(RED)
	{ }
};
template<class K, class V>
class RBTree
{
public:
	typedef RBTreeNode<K, V> Node;
private:
	Node* _root = nullptr;
};

2.出现矛盾时的修改

如上图出现矛盾时,我们将新插的节点为cur,其父亲为parent,父亲的父亲为grandparent,grandparent的另一个孩子为uncle。

当cur与parent的颜色出现矛盾时,grandparent的颜色一定为黑色,此时uncle的颜色就极为重要。

1.uncle为红色

解法:

(cur可能是新节点也可能是从下面更新出来的)

这些情况下只需将parent和uncle更新为black,grandparent更新为red。然后将cur去到grandparent处向上更新即可。(可能grandparent为红后与其的祖父又发生了矛盾)

代码:(说白了,红黑树与AVL树的差别只有insert是不同的)

cpp 复制代码
	bool insert(const pair<K, V>& val)
	{
		if (_root == nullptr)
		{
			_root = new Node(val);
			_root->_col = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_val.first < val.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_val.first > val.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		//此处走到空结点
		cur = new Node(val);

		//连接parent和cur
		if (parent->_val.first < val.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;
		//parent==nulptr代表走到_root了
		while (parent && parent->_col == RED)
		{
			Node* grandparent = parent->_parent;
			//这里核心是用于限定uncle在grandparent的左还是右的			
if (grandparent->_left == parent)
			{
				Node* uncle = grandparent->_right;
				//uncle==nullptr为其它情况
				if (uncle && uncle->_col == RED)
				{
					uncle->_col = parent->_col = BLACK;
					grandparent->_col = RED;
					cur = grandparent;
					parent = grandparent->_parent;
				}
				else
				{
					if (parent->_left == cur)
					{						
rotateR(grandparent);
						parent->_col = BLACK;
						grandparent->_col = RED;
					}
					else
					{
						rotateL(parent);
						rotateR(grandparent);
						cur->_col = BLACK;
						grandparent->_col = RED;
					}				
	    //修改完后就要break了,不要再往上走了
					break;
				}
				
			}
			else
			{
				Node* uncle = grandparent->_left;
				//uncle==nullptr为其它情况
				if (uncle && uncle->_col == RED)
				{
					uncle->_col = parent->_col = BLACK;
					grandparent->_col = RED;
					cur = grandparent;
					parent = grandparent->_parent;
				}
				else
				{
					if (parent->_left == cur)
					{
						rotateR(parent);
						rotateL(grandparent);
						cur->_col = BLACK;
						grandparent->_col = RED;
					}
					else
					{
						rotateL(grandparent);
						parent->_col = BLACK;
						grandparent->_col = RED;
					}
					break;
				}
			}
		}
		_root->_col = BLACK;
		return true;
	}

2.uncle为黑或uncle为空

1.uncle为空

此时cur一定为新插入结点,不然左边一定有别的黑节点使黑节点数两边不相等。

2.uncle为黑

此时cur一定不为新插入结点,不然左子树一定比右子树少至少一个黑节点。

二者本质是一样的,只有旋转才能解决,此时又分两种情况。

1.cur在parent的左子树,右单旋

以grandparent为旋转点单右旋,然后将grandparent改为红色,parent改为黑色。

2.cur在parent的右子树,左右双旋

先以parent为旋转点单左旋,再以grandparent为旋转点单右旋,grandparent改为红色,cur改为黑色.

代码:

cpp 复制代码
while (parent && parent->_col == RED)
{
	Node* grandparent = parent->_parent;
	//这里核心是用于限定uncle在grandparent的左还是右的
//此处uncle为右
	if (grandparent->_left == father)
	{
		Node* uncle = grandparent->_right;
		//uncle==nullptr为其它情况
		if (uncle&&uncle->_col == RED)
		{
			uncle->_col = parent->_col = BLACK;
			grandparent->_col = RED;
			cur = grandparent;
			parent = grandparent->_parent;
		}
		else
			if (parent->_left == cur)
			{
				rotateR(grandparent);
				parent->_col = BLACK;
				grandparent->_col = RED;
			}
			else
			{
				rotateL(parent);
				rotateR(grandparent);
				cur->_col = BLACK;
				grandparent->_col = RED;
			}
	}
//此处uncle为左
	else
	{
		Node* uncle = grandparent->_laft;
		//uncle==nullptr为其它情况
		if (uncle && uncle->_col == RED)
		{
			uncle->_col = parent->_col = BLACK;
			grandparent->_col = RED;
			cur = grandparent;
			parent = grandparent->_parent;
		}
		else
			if (parent->_left == cur)
			{
				rotateR(parent);
				rotateL(grandparent);
				cur->_col = BLACK;
				grandparent->_col = RED;
			}
			else
			{
				rotateL(parent);
				parent->_col = BLACK;
				grandparent->_col = RED;
			}
	}

3.检查是否为红黑树

1.用孩子找是否与父亲都为红色

2.计算每一条路径的黑色结点是否都相同

cpp 复制代码
	bool isRBtree()
	{
		if (_root == nullptr)
			return true;
		if (_root->_col == RED)
			return false;
		Node* cur = _root;
		int realheight = 0;
		while (cur)
		{
			if (cur->_col == BLACK)realheight++;
			//随便找一条路径的黑色节点数即可
			cur = cur->_left;
		}
		return _isRBtree(_root, 0, realheight);
	}
private:
	//num是形参,找每一条的黑色结点个数,realnum是外部传的一个一条路径的黑色节点数
	bool _isRBtree(Node* _root, int num, const int realnum)
	{
		//说明遍历到头了
		if (_root == nullptr)
		{
			if (num != realnum)
			{
				cout << "not real number" << endl;
				return false;
			}
			return true;
		}
		if (_root->_col == BLACK)
			num++;
		if (_root->_col == RED && _root->_parent->_col == RED)
		{
			cout << "contrast colour " << endl;
			return false;
		}
		return _isRBtree(_root->_left, num, realnum) && _isRBtree(_root->_right, num, realnum);
	}

总结两次出的问题都在于在更新完后没有及时的break,因此要注意在调整更新后要及时地break,部分情况除外。

相关推荐
郝学胜-神的一滴2 小时前
图形学基础:OpenGL、图形引擎与IG的核心认知及核心模式解析
开发语言·c++·qt·程序人生·图形渲染
BigDark的笔记2 小时前
[温习C/C++]0x09 C++构造函数中调用虚函数会发生什么?
c++
kyle~2 小时前
C++---yaml-cpp YAML标准解析/生成库
c++·参数
96772 小时前
多线程编程:整个互斥的流程以及scoped_lock的用法,以及作用,以及 硬件上的原子操作和逻辑上的原子操作
开发语言·c++·算法
liuyao_xianhui2 小时前
优选算法_topk问题_快速排序算法_堆_C++
java·开发语言·数据结构·c++·算法·链表·排序算法
yunn_2 小时前
Qt智能指针
c++·qt
liuyao_xianhui2 小时前
优选算法_堆_最后一块石头的重量_C++
java·开发语言·c++·算法·链表
上天_去_做颗惺星 EVE_BLUE2 小时前
Linux Core Dump 测试操作手册
linux·c++·测试工具
羊小猪~~2 小时前
算法/力扣--栈与队列经典题目
开发语言·c++·后端·考研·算法·leetcode·职场和发展