当前位置: 首页 > news >正文

手撕红黑树

引入:为何要有红黑树树,AVL树有什么不足?

至今,若有用到二叉搜索树的场景,绝大多数用的是红黑树,而不是AVL树,因为:

红黑树和 AVL树虽然都是高效的平衡二叉树,增删改查的时间复杂度都是 O(logn) ,但红黑树不追
求绝对平衡,其只需 保证最长路径不超过最短路径的2倍 ,相对而言,降低了插入和旋转的次数,
所以在经常进行增删的结构中性能比 AVL 树更优,而且红黑树实现比较简单,所以实际运用中红
黑树更多。
也就是说:红黑树在牺牲了微乎其微的速度,换回来的是大大降低了插入和旋转的次数!
有人说:若发明AVL树的是一个天才,那么发明红黑树的就是天才中的佼佼者!
要读懂此篇博客,得先会AVL树->手撕AVL树-CSDN博客

一:红黑树的性质

红黑树保证最长路径不超过最短路径的2倍,这是红黑树的规则 ,既然是规则,那必定是在其性质的限制下,才能达到这条规则。
红黑树的性质:
1. 每个结点不是红色就是黑色
2. 根节点是黑色的 
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 
总结一下,让其更好理解:
1:根为黑色
2:没有连续的红色
3:每条路径都有相同个数的黑色节点
路径: 根节点走到空节点所遍历的节点,就叫做路径(而不是走到叶子节点)
Q:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?
A:最短路径不就是全黑吗?那最长路径不就是一黑一红吗?所以不会超过最短的两倍。如图:

注意:
最短路径和最长路径都有可能不存在。因为最短和最长路径都是极端的情况,无法保证每颗红黑树都有全黑路径和严格的一黑一红路径,如下图随便一个红黑树都有可能不存在最短/长路径

如图:

路径黑色个数为2,所以最短应该是纯黑路径长度为2,但不存在,最长是4个节点的一黑一红,也不存在

二:红黑树的实现

a:框架

和AVL树类似 不再赘述

代码如下:

//枚举
enum Colour
{
	RED,
	BLACK
};

//节点类
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Colour _col;//颜色变量

	RBTreeNode(const pair<K, V>& kv)//节点类的构造函数
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)
	{}
};

 //红黑树类
emplate<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;


private:
	Node* _root = nullptr;
	
};

对枚举的解释:

enum Colour
{
    RED,
    BLACK
};

①:这是一个简单的枚举定义,它:

  1. 创建了一个名为Colour的新类型

  2. 定义了两种可能的枚举值:REDBLACK

  3. 默认情况下,RED对应整数值0,BLACK对应1

 ②:为什么使用枚举而不是其他方式?

  1. 可读性REDBLACK比数字0和1更直观

  2. 类型安全Colour类型只能取REDBLACK,不能赋其他值

  3. 维护性:如果需要修改颜色表示方式,只需修改枚举定义

b:插入函数(重难点)

既然是红黑树,那么我们插入的节点应该为红色还是黑色?

回顾一下性质:

1:根为黑色
2:没有连续的红色
3:每条路径都有相同个数的黑色节点

选择黑色:会违反性质3,则需要让其他路径的黑色节点也增加一个

选择红色:可能会性质2,违反需要处理

为什么说红色有可能违反2呢?因为若新增节点的父节点为黑,则没违反

正所谓:"当所有选择都暗藏代价,择其轻而承之"

所以这里肯定选择新增节点为红色,因为红色不仅有可能不违反性质,即使性质也比黑色违反性质所付出的代价更低!

这也是为什么我们a中的框架已经 _col(RED)了

颜色选定后,则插入就有以下几步了:

①:如何找到插入的位置并插入

②:检测新节点插入后,是否还是红黑树

③:处理让其恢复红黑树

①:如何找到插入的位置并插入

非常简单,和AVL树类似

代码如下:

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//不允许存在两个k值一样的节点
			{
				return false;
			}
		}
        
        //走到这里 代表找到了插入的位置
		cur = new Node(kv); //新的节点已经被设置为红色的
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;
        

        //插入完成 则返回真
		return true;
	}

②:检测新节点插入后,是否还是红黑树

Q:那插入红色节点,什么时候代表红黑树不再是红黑树了?

A:当新增节点的父节点为红,则违反了不能有连续的红这条性质(因为新增节点一定为红),此时不再是红黑树了,反之父为黑,则还是红黑树
代码如下:
        //父亲存在且父亲为红代表要继续循环处理
		while (parent && parent->_col == RED)
		{
		    //处理..
            //让其恢复为红黑树	
		}

        /最后的根一定为黑 不再在代码中进行特判 直接暴力解决
		_root->_col = BLACK;

		return true;

Q:不是新增节点的父节点为红才代表不是红黑树吗,为什么要判断parent是否存在呢?

A:因为父节点不存在,代表新增的就是第一个节点,此时也应该跳出while循环,到外面处理_root为黑即可

其实我们上面一直以新增节点为视角来看,这是局限的!其实真实情况是处理有可能不只是需要一次,每次单次处理完成后,都有可能还不是红黑树,我们则需要继续处理

所以

①:while判断中的parent不一定只是新增节点的父节点,有可能是往上继续处理中的某个节点的父节点

②:while外面的置根节点为黑,也不一定只是新增节点为第一个节点的情况,也有可能是处理完成后,影响到了根节点的颜色,我们要把根节点恢复黑色

③:处理让其恢复红黑树

和AVL树类似,违反性质的红黑树不止一种情况,所以对应的处理的方案也不止一种

约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点 双亲(p和u 也就是父亲和叔叔)

而违反规则的时候,则代表p一定为红,g一定为黑,只有u不确定(u存在为红/u存在为黑/u不存在)

所以当插入节点后,此时不再是红黑树的时候,该红黑树此时的状态就是三种:

p一定为红,g一定为黑,u存在为红 / u存在为黑/ u不存在

所以处理就是处理这个三种状态!

Q:为什么p一定为红?

A:p为黑,那还叫违反红黑树规则吗,那还需要处理吗?

Q:为什么g一定为黑?

A:因为p为红,所以g一定为黑,不然在我们新增之前就已经不是红黑树了

Q:咱们不是还没写吗,也有可能出现新增之前就不是红黑树的情况啊?

A:hehe,我们讨论的红黑树插入算法有一个重要前提:插入前树必须满足所有红黑树性质。这是算法正确性的基础假设,就像数学证明需要先明确公理一样。"

博主依旧是采取抽象图来讲解,因为例子多到数不过来,用单一的例子反而不能代表全部情况,用抽象图更好

第一种不是红黑树的情况及处理:

情况一: cur为红,p为红,g为黑,u存在且为红
解释:
原先状态:从g到任意空节点的路径,只有一个黑 ,但是出现了连续的红(p和cur) ×
处理后的状态:从g到任意空节点的路径,只有一个黑 ,且没有连续的红

单次处理后:此时的g就是新的cur,然后继续进行处理(处理可能不只是用情况1的处理,下面会讲),直到某个cur的父亲不再是红色 即可

情况一处理的代码如下:

                Node* uncle = grandfather->_right;
				// 情况一:叔叔存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}

第二种不是红黑树的情况及处理:

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

Q:为什么u不存在/u存在且为黑 在一个情况中?

A:因为处理方案都是旋转

解释:

1.叔叔存在且为黑:

如果u节点存在,且其为黑,那么cur节点原来的颜色一定是黑色的,现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色改成红色。所以现在a,b,c中一定至少有一个黑色(这样才能符合每条路径黑色个数一致的性质) 举个例子如下图:

由图可知,cur的确是由黑变红的,新增节点是在cur的子树中

u存在且为黑的情况的处理:

我们无法再像情况1一样直接做变色处理即可了;我们要先旋转再做特殊变色处理才可以 !

处理如下:

咱们得旋转函数的参数是g,才能正确旋转!

如图可知:情况2一定是从情况1 往上处理遇到的一种情况

2.叔叔不存在:

如果u节点不存在,则cur一定是新插入节点

Q:为什么cur一定是新插入节点?

A:那假设cur不是新增。cur不是新增则代表其的红色是由黑色转变的(因为之前肯定符合红黑树,所以cur和p形成两个连续的红),但是现在cur为红,代表cur的子树一定有一个黑节点的,那此时的每条路径的黑色不再一致,此时从 g 到 cur 的子树的叶子节点的路径会比 gu 的路径多一个黑色节点(因为 cur 的子节点有黑),违反性质了。

Q:为什么p的右孩子不存在?

A:同理啊!因为g到其右孩子为空这条路径黑色为1,若此时p的右孩子存在,那么该节点就应该是黑色,那此时g到p再到p的右孩子,就是两个黑色了,违反性质了。

u不存在的处理:

u存在且为黑一样的:

一样的:旋转函数的参数是g,才能正确旋转!

但是根据我们对AVL树的了解可知,如情况2的这种p为g的左,c又为p的左所需的是右单旋,所以就单旋而言,还有右旋

如下所示:

pg的左孩子,curp的左孩子,则进行右单旋转;相反,
pg的右孩子,curp的右孩子,则进行左单旋转
单旋变色:p变黑,g变红
所以左旋和右旋的代码如下:
            // 情况二:叔叔不存在或者存在且为黑
			// 旋转+变色            


            //p是g的左
            if (parent == grandfather->_left)
			{
                //且c是p的左    
				if (cur == parent->_left)
				{
					//       g
					//    p    u(存在且为黑或不存在)
					// c
					RotateR(grandfather);//参数为g

                    //变色处理 p变黑 g变红
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
            }



            //p是g的右
            if (parent == grandfather->_right)
			{
                //且c是p的右    
				if (cur == parent->_right)
				{
					//       g
					//    u    p
					//           c
					RotateL(grandfather);//参数为g

                    //变色处理 p变黑 g变红
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
            }
            

都讲到这里了,肯定还有左右双旋和右左双旋

左右双旋:p是g的左,但是c是p的右

右左双旋:p是g的右,但是c是p的左

且变色也有不同:需要双旋之后  再c变黑 g变红

所以情况2还有其他的树状态,此时需要双旋来解决!

所以左右双旋代码如下:

                //叔叔不存在或者存在且为黑
                //p是g的左    
                if (parent == grandfather->_left)
			    {
                    if (cur == parent->_left)
					{
					    //右单旋处理...
					}
					else//代表c是p的右
					{
						//       g
						//    p     u((存在且为黑或不存在))
						//      c

                        //则需左右双旋 在变色(c变黑 g变红)
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					//旋转后代表红黑树已经合理 所以break 不再通过循环判断
					break;
                }

右左双旋代码如下:

                //叔叔不存在或者存在且为黑
                //p是g的右    
                if (parent == grandfather->_right)
			    {
                    //c是p的右
                    if (cur == parent->_right)
					{
					    //左单旋处理...
					}
					else//代表c是p的左
					{
						//       g
						//    u     p
						//        c

                        //则需右左双旋 在变色(c变黑 g变红)
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					//旋转后代表红黑树已经合理 所以break 不再通过循环判断
					break;
                }

到这里咱们得插入就算完成了,但是说了这么多,也挺杂的,梳理一下吧:

④:插入函数的整体思路:

1. 基础插入部分

  • 空树处理:若树为空,直接创建黑色根节点并返回。

  • BST查找插入位置:从根节点开始比较键值,找到合适的位置插入新节点(默认红色)。

  • 重复值检查:若键已存在,直接返回false。

2. 红黑树性质修复

当父节点为红色时(违反红黑树性质),进入修复循环:

情况分类

根据父节点(p)相对于祖父节点(g)的位置分为两大分支:

A. 父节点是祖父的左孩子

  1. 情况1:叔叔节点(u)存在且为红

    • 操作:将父节点和叔叔节点变黑,祖父节点变红

    • 后续:将祖父g节点设为当前cur节点,继续向上检查

  2. 情况2:叔叔节点不存在或为黑

    • 子情况2.1:当前节点是父节点的左孩子(直线型)

      • 对祖父节点右旋

      • 父节点变黑,祖父节点变红

    • 子情况2.2:当前节点是父节点的右孩子(折线型)

      • 先对父节点左旋,再对祖父节点右旋

      • 当前节点变黑,祖父节点变红

B. 父节点是祖父的右孩子(对称处理)

  1. 情况1:同A.1,方向相反

  2. 情况2

    • 子情况2.1:当前节点是父节点的右孩子(直线型)

      • 对祖父节点左旋

    • 子情况2.2:当前节点是父节点的左孩子(折线型)

      • 先对父节点右旋,再对祖父节点左旋

3. 终止条件

  • 旋转调整后直接退出循环(因为已满足性质,此时不能再通过循环判断跳出)

  • 向上检查到根节点或遇到黑父节点时终止

4. 最终处理

  • 强制将根节点设为黑色(保证性质根为黑)

⑤:插入函数代码如下:

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); //新的节点已经被设置为红色的
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = 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 = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					// 情况二:叔叔不存在或者存在且为黑
					// 旋转+变色
					if (cur == parent->_left)
					{
						//       g
						//    p    u(存在且为黑或不存在)
						// c
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//       g
						//    p     u((存在且为黑或不存在))
						//      c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					//旋转后代表红黑树已经合理 所以break 不再通过循环判断
					break;
				}
			}
			else//p是g的右
			{
				Node* uncle = grandfather->_left;
				// 情况一:叔叔存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else//情况2
				{
					// 情况二:叔叔不存在或者存在且为黑
					// 旋转+变色
					//         g
					//   u(...)     p
					//           c
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//c是p的左
					{
					//         g
					//   u(...)     p
					//           c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					//旋转后代表红黑树已经合理 所以break 不再通过循环判断
					break;
				}
			}
		}
		//最后的根一定为黑 不再在代码中进行特判 直接暴力解决
		_root->_col = BLACK;

		return true;
	}

c:旋转函数

不再赘述,AVL博客里面讲过了~

void RotateL(Node* parent)
	{
		++rotateSize;

		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		subR->_left = parent;
		Node* ppnode = parent->_parent;
		parent->_parent = subR;

		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subR;
			}
			else
			{
				ppnode->_right = subR;
			}
			subR->_parent = ppnode;
		}
	}

	void RotateR(Node* parent)
	{
		++rotateSize;

		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		subL->_right = parent;

		Node* ppnode = parent->_parent;
		parent->_parent = subL;

		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}
	}

d:检查函数

Q:检查该树是否为红黑树,那怎么检查呢?

A:根据性质来检查即可:

性质:

1:根为黑色
2:没有连续的红色
3:每条路径都有相同个数的黑色节点
检查:
1:根不为红色?
很简单

2:有连续的红色?
遍历到每个节点的时候,让其与这个节点的父亲的颜色对比

3:存在两条路径的黑色个数不相等?
先算出一条路径的黑色个数(代码以最左路径为例),再用这个值和其余路径相比
代码如下:
				      //路径的黑色个数   计算好的标准值
	bool Check(Node* cur, int blackNum, int refBlackNum)
	{	
		//执行这里则代表:空树或算完一条路径了
		if (cur == nullptr)
		{
			//判断一下标准值和我们算的某条路径的值是否相等
			if (refBlackNum != blackNum)
			{
				cout << "黑色节点的数量不相等" << endl;
				return false;
			}

			return true;
		}
		
		//判断是否有连续的红
		if (cur->_col == RED && cur->_parent->_col == RED)
		{
			cout << cur->_kv.first << "存在连续的红色节点" << endl;
			return false;
		}

		//遇到黑色节点则++lackNum
		if (cur->_col == BLACK)
			++blackNum;

		//递归 本质是前序遍历
		return Check(cur->_left, blackNum, refBlackNum)
			&& Check(cur->_right, blackNum, refBlackNum);
	}

	bool IsBalance()
	{
		//根节点不为红
		if (_root && _root->_col == RED)
			return false;

		int refBlackNum = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				refBlackNum++;//计算一条路径的黑色个数

			cur = cur->_left;
		}
		//然后以这条路径的黑色个数为标准值 去看其余的每条路径是否都一样
		return Check(_root, 0, refBlackNum);
	}

解释:

  • 关键点
    每次递归调用都拷贝传递blackNum值,确保:

    • 向左/右分支探索时,各自独立累计黑色节点数

    • 不同路径的计数互不干扰

假设树结构如下(B:黑, R:红):
       B1
     /   \
   R2     B3
  / \    /  
B4  B5 B6 
  • 基准值计算:最左路径 B1→B4 → refBlackNum=2

  • 递归验证

    • 路径1: B1→R2→B4 → 黑色数=2(B1,B4)

    • 路径2: B1→R2→B5 → 黑色数=2(B1,B5)

    • 路径3: B1→B3→B6 → 黑色数=3(B1,B3,B6)→ 此处会报错

其余函数,中序,高度,个数,都不说了,AVL中都说过

其中的该函数使用来得到旋转次数的函数,方便和AVL树比较:所以参数也要放进成员变量中

    int GetRotateSize()
	{
		return rotateSize;
	}

三:红黑树代码

#pragma once
#include<vector>
//枚举
enum Colour
{
	RED,
	BLACK
};

//节点类
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Colour _col;//颜色变量

	RBTreeNode(const pair<K, V>& kv)//节点类的构造函数
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)
	{}
};

//红黑树类
template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
    //插入函数
	bool Insert(const pair<K, V>& kv)
	{
        //空树情况
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;//直接return了
		}

        //开始找插入的位置
		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进行插入        
		cur = new Node(kv); //新的节点已经被设置为红色的
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		//父亲存在且父亲为红代表要继续循环
		while (parent && parent->_col == RED)
		{    
            //记录g
			Node* grandfather = parent->_parent;
            //p为g的左
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				// 情况一:叔叔存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else//情况二
				{
					// 情况二:叔叔不存在或者存在且为黑
					// 旋转+变色
                    
                    //p为g的左且c为p的左 则右单旋
					if (cur == parent->_left)
					{
						//       g
						//    p    u(存在且为黑或不存在)
						// c
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//p为g的左且c为p的右 则左右双旋
					{
						//       g
						//    p     u((存在且为黑或不存在))
						//      c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					//旋转后代表红黑树已经合理 所以break 不再通过循环判断
					break;
				}
			}
			else//p是g的右
			{
				Node* uncle = grandfather->_left;
				// 情况一:叔叔存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else//情况2
				{
					// 情况二:叔叔不存在或者存在且为黑
					// 旋转+变色
					//         g
					//   u(...)     p
					//           c
    
                    //p是g的右且c是p的右  左单旋
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//p是g的右且c是p的左  右左单旋
					{
					//         g
					//   u(...)     p
					//           c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					//旋转后代表红黑树已经合理 所以break 不再通过循环判断
					break;
				}
			}
		}
		//最后的根一定为黑 不再在代码中进行特判 直接暴力解决
		_root->_col = BLACK;

		return true;
	}
    
    //左单旋
	void RotateL(Node* parent)
	{
		++rotateSize;

		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		subR->_left = parent;
		Node* ppnode = parent->_parent;
		parent->_parent = subR;

		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subR;
			}
			else
			{
				ppnode->_right = subR;
			}
			subR->_parent = ppnode;
		}
	}

    //右单旋
	void RotateR(Node* parent)
	{
		++rotateSize;

		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		subL->_right = parent;

		Node* ppnode = parent->_parent;
		parent->_parent = subL;

		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}
	}

    //中序
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_kv.first << endl;
		_InOrder(root->_right);
	}

	void InOrder()
	{
		_InOrder(_root);
	}
	//检查函数			路径的黑色个数   计算好的标准值
	bool Check(Node* cur, int blackNum, int refBlackNum)
	{	
		//执行这里则代表:空树或算完一条路径了
		if (cur == nullptr)
		{
			//某条路径的黑色值和标准值不相等?
			if (refBlackNum != blackNum)
			{
				cout << "黑色节点的数量不相等" << endl;
				return false;
			}

			return true;
		}
		
		//有连续的红?
		if (cur->_col == RED && cur->_parent->_col == RED)
		{
			cout << cur->_kv.first << "存在连续的红色节点" << endl;
			return false;
		}

		//遇到黑色节点则++lackNum
		if (cur->_col == BLACK)
			++blackNum;

		//递归 本质是前序遍历
		return Check(cur->_left, blackNum, refBlackNum)
			&& Check(cur->_right, blackNum, refBlackNum);
	}

	bool IsBalance()
	{
		//根节点为红?
		if (_root && _root->_col == RED)
			return false;

		int refBlackNum = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				refBlackNum++;//计算一条路径的黑色个数

			cur = cur->_left;
		}
		//然后以这条路径的黑色个数为标准值 去看其余的每条路径是否都一样
		return Check(_root, 0, refBlackNum);
	}

    //节点个数函数
	size_t Size()
	{
		return _Size(_root);
	}

	size_t _Size(Node* root)
	{
		if (root == NULL)
			return 0;

		return _Size(root->_left)
			+ _Size(root->_right) + 1;
	}

    //查找函数
	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}

		return NULL;
	}

    //高度函数
	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);

		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

	int Height()
	{
		return _Height(_root);
	}

    //获取旋转次数函数
	int GetRotateSize()
	{
		return rotateSize;
	}

private:
	Node* _root = nullptr;
	int rotateSize = 0;
};
//----------------------------------------------------------------
//以下为测试
void TestRBTree1()
{

	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14,16, 3, 7, 11, 9, 26, 18, 14, 15 };
	RBTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(make_pair(e, e));
	}

	t.InOrder();
	cout << t.IsBalance() << endl;
}

void TestRBTree2()//测试红黑树一千万个数据的速度
{
	const int N = 1000000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));

	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand() + i);

	}

	size_t begin1 = clock();
	RBTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));

	}
	size_t end1 = clock();
	//打印插入一千万数据的时间
	cout << "Insert:" << end1 - begin1 << endl;

	//判断是否平衡
	cout << t.IsBalance() << endl;

	//打印红黑树的高度和个数
	cout << "Height:" << t.Height() << endl;
	cout << "Size:" << t.Size() << endl;

	size_t begin2 = clock();
	//查找树中的一千万个值的时间
	for (auto e : v)
	{
		t.Find(e);
	}


	size_t end2 = clock();
	//打印查找的时间
	cout << "Find:" << end2 - begin2 << endl;
}

四:红黑树的测试

测试是和红黑树在同一个.h中的,这也是为什么红黑树代码上面包了vector,因为测试会用!

测试Test1结果如下:最后的1代表检查函数通过了,是一个红黑树

测试Test2结果如下:

五:红黑树和AVL的比较

该代码放在.c中 且.c包含AVL和红黑树的.h,才能同时用两个头文件的内容~

//红黑树和AVL的对比测试
//突出红黑树旋转次数的减少的同时 还在速度上逼近AVL树
void TestRBTree_AVLTree()
{
	const int N = 10000000;//一千万数据
	vector<int> v;
	v.reserve(N);
	srand(time(0));

	//先把一千万数据放进vector中
	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand() + i);
	}

	RBTree<int, int> t1;//红黑树对象
	AVLTree<int, int> t2;//AVL树对象

	// 红黑树插入一千万个数据的时间
	size_t begin1 = clock();
	for (auto e : v)
	{
		t1.Insert(make_pair(e, e));
	}
	size_t end1 = clock();


	// AVL树插入一千万个数据的时间
	size_t begin2 = clock();
	for (auto e : v)
	{
		t2.Insert(make_pair(e, e));
	}
	size_t end2 = clock();

	//打印红黑树的旋转次数
	cout << "RBTree RoateSize:" << t1.GetRotateSize() << endl;
	//打印AVL的旋转次数
	cout << "AVLTree RoateSize:" << t2.GetRotateSize() << endl;

	//打印红黑树插入一千万个数据的时间
	cout << "RBTree Insert:" << end1 - begin1 << endl;
	//打印AVL树插入一千万个数据的时间
	cout << "AVLTree Insert:" << end2 - begin2 << endl;

	//判断红黑树是否平衡
	cout << "RBTree IsBalance:" << t1.IsBalance() << endl;
	//判断AVL树是否平衡
	cout << "AVLTree IsBalance:" << t2.IsBalance() << endl;

	//打印红黑树高度
	cout << "RBTree Height:" << t1.Height() << endl;
	//打印红黑树节点个数
	cout << "RBTree Size:" << t1.Size() << endl;
	//打印AVL树高度
	cout << "AVLTree Height:" << t2.Height() << endl;
	//打印AVL树节点个数
	cout << "AVLTree Size:" << t2.Size() << endl;


	// 红黑树查找所有值的时间
	size_t begin3 = clock();
	for (auto e : v)
	{
		t1.Find(e);
	}
	size_t end3 = clock();


	//AVL树查找所有值的时间
	size_t begin4 = clock();
	for (auto e : v)
	{
		t2.Find(e);
	}
	size_t end4 = clock();

	//红黑树和AVL树查找所有值分别花的时间
	cout << "RBTree Find:" << end3 - begin3 << endl;
	cout << "AVLTree Find:" << end4 - begin4 << endl;
}

测试结果如下:

由图可知:

一千万个数据下

红黑树在速度上仅仅慢了AVL树5毫秒,但是红黑树所用的旋转次数降低了接近100w次,插入也稍快,由此可见红黑树是一个优秀的结构!

六:AVL树代码

需要的直接cv

相对与AVL树博客有少许改动,是为了和红黑树比较

#pragma once
#include<assert.h>
#include<vector>

//节点类
template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf; // balance factor
	pair<K, V> _kv;

	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr) //指向父节点的之怎
		, _bf(0) //平衡因子
		, _kv(kv) //pair类型的对象  存储k值和v值
	{}
};
//AVL类
template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:

	//插入函数
	bool Insert(const pair<K, V>& kv)
	{
		//空树情况
		if (_root == nullptr)
		{
			_root = new Node(kv);
			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//AVL树不会存在相同的节点
			{
				return false;
			}
		}
		//走到这代表 找到了插入的位置 
		cur = new Node(kv);//先把该节点准备好
		//parent节点在之前是cur的父节点 也就是空节点的父亲
		//所以现在能将cur正确的链接到parent的正确方向
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		//插入完成一定会影响parent节点的bf(只是看cur是p的哪一边 bf再++或--)
		//p的bf三种情况:bf 0 1/-1 2/-2
		//0:不会影响parent的祖先的bf 直接break
		//1/-1:树高度增加 会影响祖先的bf 所以更新完p的bf 再次循环继续更新上面的bf
		//2/-2:则需要旋转
		while (parent)
		{
			//第一次更新p的bf
			if (cur == parent->_left)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}

			//对更新完的p的bf判断
			if (parent->_bf == 0)
			{
				break;
			}
			//祖先节点依旧需要更新bf  再次循环
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			//需要旋转
			//进入旋转,咋代表cur和parent 之前已经循环找了 需要旋转的p节点
			//(该p节点的bf 2/-2)
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				// 左旋转
				// 触发条件:p->bf为2 cur->_bf == 1
				if (parent->_bf == 2 && cur->_bf == 1)
				{
					RotateL(parent);
				}
				// 右旋转
				// 触发条件:parent->_bf == -2 && cur->_bf == -1
				else if (parent->_bf == -2 && cur->_bf == -1)
				{
					RotateR(parent);
				}
				// 左右旋转
				// 触发条件:parent->_bf == -2 && cur->_bf == 1
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent);
				}
				//除此之外右左旋转
				else
				{
					RotateRL(parent);
				}

				break;
			}
			else
			{
				// 插入之前AVL树就有问题
				assert(false);
				//标记“不应执行到此处”
			}
		}
		//插入完成 则返回真
		return true;
	}

	//左旋转
	//新节点插入较高右子树的右侧---简称右右
	//代表从次树的_root节点看右子树高1 且插入的节点在右子树的右侧
	//步骤: 
	//①:给到p的右指针指向原先p的右孩子的左子树
	//②:p的右子树的的左指针指向p节点
	void RotateL(Node* parent)//参数的p节点 就是bf为2的节点
	{
		++rotateSize;
		Node* subR = parent->_right; //p的右
		Node* subRL = subR->_left;   //p的右左

		parent->_right = subRL;//①
		if (subRL) //避免p的右左为空
			subRL->_parent = parent;

		subR->_left = parent;
		Node* ppnode = parent->_parent; //先记录p的父节点 以防p节点不是根节点
		parent->_parent = subR; //②

		if (parent == _root)//查看p是否为根节点
		{
			//p是根节点 则subR成为新的根接节点
			//再置一下subR父指针为空
			_root = subR;
			subR->_parent = nullptr;
		}
		else//p不是根 则p的父亲也需要正确的指向subR(判断原先的p是pp的左右孩子的哪一个)
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subR;
			}
			else
			{
				ppnode->_right = subR;
			}
			subR->_parent = ppnode;
		}

		parent->_bf = 0;
		subR->_bf = 0;
	}

	//右旋转
	//新节点插入较高左子树的左侧 简称左左
	//代表从次树的_root节点看左子树高1 且插入的节点在左子树的左侧
	//步骤: 
	//①:p的左指针指向p原先的左孩子的右孩子
	//②:原先的p的左孩子的右指针指向p节点
	//代码逻辑和左旋转同理 不再赘述
	void RotateR(Node* parent)
	{
		++rotateSize;
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		subL->_right = parent;

		Node* ppnode = parent->_parent;
		parent->_parent = subL;

		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}

		subL->_bf = 0;
		parent->_bf = 0;
	}

	//左右旋转->先左单旋再右单旋
	//新节点插入较高左子树的右侧
	//步骤 :
	//①:对p的左节点进行左单旋
	//②:再对p节点进行右单旋
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		int bf = subLR->_bf;
		RotateL(parent->_left);
		RotateR(parent);

		//由于在b树插入 在c树插入 或60本身就是插入的节点入 这三种情况双旋之后的树的bf不同
		//规律:根据插入节点的bf判断即可
		if (bf == -1)//代表在b树插入
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1)//代表在c树插入
		{
			subLR->_bf = 0;
			subL->_bf = -1;
			parent->_bf = 0;
		}
		//
		else if (bf == 0)//代表subLR就是插入节点
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	//右左旋转
	//新节点插入较高右子树的左侧---右左:先右单旋再左单旋
	//步骤 :
	//①:对p的右节点进行右单旋
	//②:再对p节点进行左单旋
	//代码逻辑类似 不再赘述
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateR(subR);
		RotateL(parent);

		subRL->_bf = 0;
		if (bf == 1)
		{
			subR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
		}
		else
		{
			parent->_bf = 0;
			subR->_bf = 0;
		}
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_kv.first << "[" << root->_bf << "]" << endl;
		_InOrder(root->_right);
	}
	//中序遍历
	void InOrder()
	{
		_InOrder(_root);
	}

	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);

		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}
	//高度函数
	int Height()
	{
		return _Height(_root);
	}

	//bool _IsBalance(Node* root) {
	//	if (root == nullptr) {
	//		return true;
	//	}

	//	int leftHeight = Height(root->_left);
	//	int rightHeight = Height(root->_right);

	//	// 检查当前节点是否平衡(左右子树高度差不超过1)
	//	if (abs(rightHeight - leftHeight) >= 2) {
	//		cout << root->_kv.first << " 不平衡" << endl;
	//		return false;
	//	}

	//	// 检查平衡因子是否正确(bf应等于右子树高度减左子树高度)
	//	if (rightHeight - leftHeight != root->_bf) {
	//		cout << root->_kv.first << " 平衡因子异常" << endl;
	//		return false;
	//	}

	//	// 递归检查左右子树
	//	return _IsBalance(root->_left) && _IsBalance(root->_right);
	//}

	//更优秀的平衡函数
	bool _IsBalance(Node* root, int& height)
	{
		if (root == nullptr)
		{
			height = 0;
			return true;
		}

		int leftHeight = 0, rightHeight = 0;
		if (!_IsBalance(root->_left, leftHeight)
			|| !_IsBalance(root->_right, rightHeight))
		{
			return false;
		}

		if (abs(rightHeight - leftHeight) >= 2)
		{
			cout << root->_kv.first << "不平衡" << endl;
			return false;
		}

		if (rightHeight - leftHeight != root->_bf)
		{
			cout << root->_kv.first << "平衡因子异常" << endl;
			return false;
		}

		height = leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;

		return true;
	}
	//是否平衡判断函数
	bool IsBalance()
	{
		int height = 0;
		return _IsBalance(_root, height);
	}
	//计算树的节点个数
	size_t Size()
	{
		return _Size(_root);
	}

	size_t _Size(Node* root)
	{
		if (root == NULL)
			return 0;

		return _Size(root->_left)
			+ _Size(root->_right) + 1;
	}

	//查找函数
	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}

		return NULL;
	}

	int GetRotateSize()
	{
		return rotateSize;
	}
private:
	//成员变量_root根节点
	Node* _root = nullptr;
	int rotateSize = 0;
};

 

相关文章:

  • 什么网站做ppt互联网推广员是做什么的
  • 学校网站推广策划书网络推广员工作好做吗
  • 微信公司网站百度指数趋势
  • 网站建设设计思想游戏代理推广渠道
  • dedecms本地打开网站定制网站制作公司
  • 青岛微网站开发加强服务保障满足群众急需m
  • Python Lambda表达式详解
  • Vue 3 响应式更新问题解析
  • chrome extension开发框架WXT之WXT Storage api解析
  • 数列分块入门4
  • 信奥赛之c++课后练习题及解析(关系运算符+选择结构)
  • JAVA中正则表达式的入门与使用
  • Matlab 分数阶PID控制永磁同步电机
  • Codeforces Round 1016 (Div. 3) C ~ G 题解
  • Golang|协程
  • python-1. 找单独的数
  • 关于nacos注册的服务的ip异常导致网关路由失败的问题
  • 科技项目验收测试怎么做?验收测试报告如何获取?
  • 网安小白筑基篇六:数据库(增删改语法、表约束、查询语句、多表查询、附phppython小练习)
  • Kubernetes集群环境搭建与初始化
  • 【实战手册】8000w数据迁移实践:MySQL到MongoDB的完整解决方案
  • 蓝桥杯备赛知识点总结
  • 小白学习java第12天(下):IO流之字符输入输出
  • 联影医疗智能体 重构医疗新范式
  • 【物联网】PWM控制蜂鸣器
  • aosp13增加摄像头控制功能实现