【C++】红黑树

  • 阿里云国际版折扣https://www.yundadi.com

  • 阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

    ​🌠 作者@阿亮joy.
    🎆专栏《吃透西嘎嘎》
    🎇 座右铭每个优秀的人都有一段沉默的时光那段时光是付出了很多努力却得不到结果的日子我们把它叫做扎根
    在这里插入图片描述

    目录

    👉红黑树👈

    红黑树的概念

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

    在这里插入图片描述
    红黑树与 AVL 树的简单对比

    在这里插入图片描述

    红黑树的性质

    1. 每个结点不是红色就是黑色
    2. 根节点是黑色的
    3. 如果一个节点是红色的则它的两个孩子结点是黑色的即红黑树中没有连续的红色节点
    4. 对于每个结点从该结点到其所有后代叶结点的简单路径上均包含相同数目的黑色结点即每条路径上的黑色节点数量相等注路径是从根节点到空节点
    5. 每个叶子结点都是黑色的此处的叶子结点指的是空结点NIL 表示空节点

    满足以上性质红黑树就能够保证最长路径中节点个数不会超过最短路径节点个数的两倍。最短的路径是全由黑色节点组成的最长路径是由一黑一红节点组成的注不一定每颗红黑树都存在这样的最短路径和最长路径此时的最长路径刚好是最短路径的两倍。因此别的最长路径更加不会超过最多路径的两倍了。

    在这里插入图片描述

    红黑树节点的定义

    // 用枚举常量来代表颜色
    enum Colour
    {
    	RED,
    	BLACK
    };
    
    template<class K, class V>
    struct RBTreeNode
    {
    	RBTreeNode<K, V>* _parent;
    	RBTreeNode<K, V>* _left;
    	RBTreeNode<K, V>* _right;
    
    	pair<K, V> _kv;
    	Colour _col;
    
    	RBTreeNode(const pair<K, V>& kv)
    		:_left(nullptr)
    		, _right(nullptr)
    		, _parent(nullptr)
    		, _kv(kv)
    	{}
    };
    

    总体来看红黑树节点的定义是和 AVL 树节点的定义是差不多的红黑树没有平衡因子而有了节点的颜色。节点的颜色有很多种方式来表示在这里我采用了枚举常量的形式。

    红黑树的插入操作

    红黑树是在二叉搜索树的基础上加上其平衡限制条件因此红黑树的插入可分为两步1. 按照二叉搜索的树规则插入新节点。2.检测新节点插入后红黑树的性质是否造到破坏。插入节点时我们需要考虑新插入节点的颜色。如果新插入节点的颜色是红色可能会违反规则三而如果新插入节点的颜色是黑色一定会违反规则四将会影响整棵树需要对整棵树进行调整所以我们默认新插入节点的颜色是红色如果违反了规则三可以通过变色加旋转的方式来进行调整。

    如果其双亲节点的颜色是黑色没有违反红黑树任何性质则不需要调整但当新插入节点的双亲节点颜色为红色时就违反了性质三不能有连在一起的红色节点此时需要对红黑树分情况来讨论

    约定cur 为当前节点parent 为父节点 pgrandfather 为祖父节点 guncle 为叔叔节点 u。

    注以下所看到的树可能是一颗完整的数也可能是一颗子树。

    情况一cur 为红p 为红g 为黑u 存在且为红

    在这里插入图片描述

    情况二cur 为红p 为红g 为黑u 不存在或 u 存在且为黑

    在这里插入图片描述
    注情况二是情况一转变而来的。

    情况三: cur为红p为红g为黑u不存在/u存在且为黑

    在这里插入图片描述

    注情况三是情况一和情况转换而来的。除了上面提及的左单旋和左右双旋还有右单旋和右左双旋。

    总结红黑树的关键是叔叔 u。u 存在且为红变色继续往上处理u 不存在或 u 存在且为黑需要进行旋转加变色。

    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->_right;
    			}
    			else if (cur->_kv.first > kv.first)
    			{
    				parent = cur;
    				cur = cur->_left;
    			}
    			else
    			{
    				return false;
    			}
    		}
    
    		// 插入节点
    		cur = new Node(kv);
    		cur->_col = RED;	// 新插入节点默认为红色
    
    		// 判断在哪一边插入
    		if (parent->_kv.first < kv.first)
    		{
    			parent->_right = cur;
    		}
    		else
    		{
    			parent->_left = cur;
    		}
    
    		cur->_parent = parent;
    		// 当parent存在且为红色,则需要继续往上处理
    		// 当parent为空或为黑色时,则不需要往上处理
    		while (parent && parent->_col == RED)
    		{
    			Node* grandfather = parent->_parent;
    			assert(grandfather);
    			assert(grandfather->_col == BLACK);
    			// 关键看叔叔
    			if (parent == grandfather->_left)
    			{
    				Node* uncle = grandfather->_right;
    				// 情况一uncle存在且为红
    				if (uncle && uncle->_col == RED)
    				{
    					parent->_col = uncle->_col = BLACK;
    					grandfather->_col = RED;
    					// 继续往上处理
    					cur = grandfather;
    					parent = cur->_parent;
    				}
    				else    //情况二和三uncle不存在或uncle存在且为黑
    				{
    					// 情况二右单旋+变色
    					//     g              p
    					//   p   u   ==>    c   g
    					// c                      u
    					if (cur == parent->_left)
    					{
    						RotateR(grandfather);
    						parent->_col = BLACK;
    						grandfather->_col = RED;
    					}
    					else
    					{
    						// 情况三parent先做左单旋,grandfather再做右单旋,最后变色
    						//     g          g            c
    						//   p   u ==>  c   u  ==>   p   g
    						//     c      p                    u
    						RotateL(parent);
    						RotateR(grandfather);
    						cur->_col = BLACK;
    						grandfather->_col = RED;
    					}
    
    					break;
    				}
    			}
    			else
    			{
    				Node* uncle = grandfather->_left;
    				// 情况一uncle存在且为红
    				if (uncle && uncle->_col == RED)
    				{
    					parent->_col = uncle->_col = BLACK;
    					grandfather->_col = RED;
    					// 继续往上处理
    					cur = grandfather;
    					parent = cur->_parent;
    				}
    				else    //情况二和三uncle不存在或uncle存在且为黑
    				{
    					// 情况二左单旋+变色
    					//     g                  p
    					//   u   p       ==>    g   c
    					//         c          u 
    					if (cur == parent->_right)
    					{
    						RotateL(grandfather);
    						parent->_col = BLACK;
    						grandfather->_col = RED;
    					}
    					else
    					{
    						// 情况三parent先做右单旋,grandfather再做左单旋,最后变色
    						//     g          g            c
    						//   u   p ==>  u   c  ==>   g   p
    						//     c             p     u
    						RotateR(parent);
    						RotateL(grandfather);
    						cur->_col = BLACK;
    						grandfather->_col = RED;
    					}
    
    					break;
    				}
    			}
    		}
    
    		// 根节点永远是黑色
    		_root->_col = BLACK;
    		return true;
    	}
    
    private:
    	// 左单旋
    	void RotateL(Node* parent)
    	{
    		Node* subR = parent->_right;
    		Node* subRL = subR->_left;
    
    		parent->_right = subRL;
    		if (subRL)
    			subRL->_parent = parent;
    
    		Node* ppNode = parent->_parent;
    
    		subR->_left = parent;
    		parent->_parent = subR;
    
    		if (_root == parent)
    		{
    			_root = subR;
    			subR->_parent = nullptr;
    		}
    		else
    		{
    			if (ppNode->_left == parent)
    			{
    				ppNode->_left = subR;
    			}
    			else
    			{
    				ppNode->_right = subR;
    			}
    
    			subR->_parent = ppNode;
    		}
    
    	}
    	// 右单旋
    	void RotateR(Node* parent)
    	{
    		Node* subL = parent->_left;
    		Node* subLR = subL->_right;
    
    		parent->_left = subLR;
    		if (subLR)
    		{
    			subLR->_parent = parent;
    		}
    
    		Node* ppNode = parent->_parent;
    
    		subL->_right = parent;
    		parent->_parent = subL;
    
    		if (_root == parent)
    		{
    			_root = subL;
    			subL->_parent = nullptr;
    		}
    		else
    		{
    			if (ppNode->_left == parent)
    			{
    				ppNode->_left = subL;
    			}
    			else
    			{
    				ppNode->_right = subL;
    			}
    
    			subL->_parent = ppNode;
    		}
    
    	}
    

    现在红黑树插入操作的代码就先完了接下来我们就来验证一下写得正不正确。

    红黑树的验证

    红黑树的验证分为两步1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)。2. 检测其是否满足红黑树的性质每条路径的黑色节点数量是否相等。

    在这里插入图片描述

    public:
    	bool IsBalance()
    	{
    		if (_root == nullptr)
    			return true;
    
    		if (_root->_col != BLACK)
    		{
    			cout << "根节点不是黑色" << endl;
    			return false;
    		}
    
    		// 黑色节点数量的基准值以最左或最右路径上的黑色节点的数量作为基准值
    		int mark = 0;
    		Node* cur = _root;
    		while (cur)
    		{
    			if (cur->_col == BLACK)
    				++mark;
    
    			cur = cur->_left;
    		}
    
    		return PrevCheck(_root, 0, mark);
    	}
    private:
    	void _InOrder(Node* root)
    	{
    		if (root == nullptr)
    		{
    			return;
    		}
    
    		_InOrder(root->_left);
    		cout << root->_kv.first << ":" << root->_kv.second << endl;
    		_InOrder(root->_right);
    	}
    
    	bool PrevCheck(Node* root, int blackNum, int mark)
    	{
    		if (root == nullptr)
    		{
    			if (blackNum != mark)
    			{
    				cout << "某条路径上的黑色节点的数量不相等" << endl;
    				return false;
    			}
    			else
    				return true;
    		}
    
    		if (root->_col == BLACK)
    			++blackNum;
    		// 检查父亲
    		if (root->_col == RED && root->_parent->_col == RED)
    		{
    			cout << "存在连续的红色节点" << endl;
    		}
    
    		return PrevCheck(root->_left, blackNum, benchmark)
    			&& PrevCheck(root->_right, blackNum, benchmark);
    	}
    

    测试样例

    void RBTreeTest1()
    {
    	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 0,5,30,25,20,4,13,30,28,27 };  
    	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
    	RBTree<int, int> t1;
    	for (auto e : a)
    	{
    		t1.Insert(make_pair(e, e));
    	}
    
    	t1.InOrder();
    	cout << "IsBalance:" << t1.IsBalance() << endl;
    }
    
    void RBTreeTest2()
    {
    	size_t N = 1000;
    	srand(time(0));
    	RBTree<int, int> t1;
    	for (size_t i = 0; i < N; ++i)
    	{
    		int x = rand();
    		cout << "Insert:" << x << ":" << i << endl;
    		t1.Insert(make_pair(x, i));
    	}
    	cout << "IsBalance:" << t1.IsBalance() << endl;
    }
    

    在这里插入图片描述

    在这里插入图片描述

    红黑树的删除

    红黑树的删除不做讲解有兴趣的大佬可参考《算法导论》或者《STL源码剖析》。

    红黑树与AVL树的比较

    红黑树和 AVL 树都是高效的平衡二叉树增删改查的时间复杂度都是 O( l o g 2 N log_2 N log2N)红黑树不追求绝对平衡其只需保证最长路径不超过最短路径的 2 倍。相对而言红黑树降低了插入和删除时旋转的次数所以在经常进行增删的结构中性能比 AVL 树更优而且红黑树实现比较简单所以实际运用中红黑树更多。

    👉总结👈

    本篇博客主要讲解了什么是红黑树、红黑树的性质、红黑树的插入操作以及验证等等。那么以上就是本篇博客的全部内容了如果大家觉得有收获的话可以点个三连支持一下谢谢大家💖💝❣️

  • 阿里云国际版折扣https://www.yundadi.com

  • 阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
    标签: c++