前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【C++】红黑树的插入分析及验证

【C++】红黑树的插入分析及验证

作者头像
lovevivi
发布2023-10-16 15:41:14
1750
发布2023-10-16 15:41:14
举报
文章被收录于专栏:萌新的日常

1. 红黑树概念

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

2. 红黑树性质

1. 每个结点不是红色就是黑色 2. 根节点是黑色的\ 3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 (不能出现连续的红色节点) 4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点\ (每条路径上都有相同数量的黑色节点) 5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点) (走到NULL才算一条路径)

3. 结构定义

使用枚举来记录红色与黑色,用_col表示当前节点颜色


但是在构造函数中为什么默认是红色呢?为什么不能是黑色呢?

关于默认节点为红/黑色的讨论

若在红框中插入黑色节点则违反规则4 即每条路径上都有相同数量的黑色节点,还需要再次将不同路径上都添加黑色节点,影响太大


若在红框中插入红色节点,则有可能违反规则3(存在两个连续的红色节点) 当前情况违反规则3


若插入红色节点后,父节点为黑色,则不违反规则3


所以默认节点为红色更利于去解决问题

4. insert

grandfather节点省略为g ,uncle节点省略为u ,parent节点省略为p,cur节点省略为c

情况1—— uncle节点存在且为红色(g p c左斜形成一条直线)

当插入红色节点后,与父节点形成连续的红色节点 把parent节点变成黑色,uncle节点置为黑色,并将grandfather节点置为红色 若grandfather节点的父节点为黑色,则不需要继续处理 若grandfather节点的父节点为红色,把当前的grandfather节点作为新增节点cur,继续向上调整


情况2——uncle节点不存在/存在且为黑色(g p c 左斜形成直线 右单旋)

uncle节点不存在

当uncle节点不存在时,则cur作为新增节点, 因为红黑树也是一种二叉搜索树,因为左边高,所以进行右单旋

uncle节点存在并且为黑色

首先进行变色,将新增节点的上面两个节点置为黑色,再将cur节点置为红色 同时需要进行右旋转


将c作为g的左子树,将g作为p的右子树 将g置为红色 将p置为黑色

RotateR/RotateL的实现,与AVL树的类似,只需把原来的代码的平衡因子去掉即可 不懂查看:AVL树的实现

情况3——uncle节点不存在/存在且为黑色(g p c 形成左折线 双旋)

因为 grandfather(g) parent( p) cur( c) 节点为一条折线,所以为双旋

uncle节点不存在

作为这样的折线存在,所以要进行双旋,先对p进行右单旋,在对旋转后的根进行左单旋

uncle节点存在并且为黑色

首先进行变色,将新增节点上面的两个节点由红色置为黑色 再将cur节点由黑色置为红色


在进行左单旋,将cur的左子树节点 作为p的右子树,将p作为cur的左子树


进行右单旋,将cur的右子树节点作为g的左子树,将g作为cur的右子树 最终cur变为黑色,g变为红色


情况1—— uncle节点存在且为红色(g p c右斜形成一条直线)

当插入红色节点后,与父节点形成连续的红色节点 把parent节点变成黑色,uncle节点置为黑色,并将grandfather节点置为红色


若grandfather节点的父节点为红色,把当前的grandfather节点作为新增节点cur,继续处理


与上述左斜形成直线的写法相同

情况2——uncle节点不存在/存在且为黑色(g p c 右斜形成直线 左单旋)

这里以节点不存在举例


此时的uncle节点处于NULL 将parent节点置为黑色,将grandfather节点置为红色 并进行旋转,将1作为6的左子树,将6作为8的左子树 相当于进行左单旋

情况3——uncle节点不存在/存在且为黑色(g p c 形成右折线 双旋)

首先进行变色,将新增节点上面的两个节点由红色置为黑色 再将cur节点由黑色置为红色


在进行右单旋,将cur的右子树节点 作为p的左子树,将p作为cur的右子树


进行左单旋,将cur的左子树节点作为g的右子树,将g作为cur的左子树 最终cur变为黑色,g变为红色

5.判断是否为红黑树

规则中要求根节点为黑色,所以当根为红色时就返回false

连续的红色节点 若当前节点为红时,检查儿子是否为红,但是儿子节点有可能为空 所以采用当前节点为红时,若父节点也为红,则返回false

使用blacknum用于记录每条路径的黑色节点个数 blacknum作为一个形参传值调用,下一层的++不会影响上一层 如果当前节点的颜色为黑色,则blacknum++

6. 整体代码

代码语言:javascript
复制
#pragma once
#include<iostream>
#include<cassert>	
using namespace std;
enum colour
{
	RED,//红色 默认为0
	BLACK,//黑色 默认为1
};
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;
		   }
		   Node* parent = nullptr;//用于记录cur的前一个节点
		   Node* cur = _root;
		   while (cur)
		   {
			   //若插入的值比当前树的值小 插入左边
			   if (cur->_kv.first > kv.first)
			   {
				   parent = cur;
				   cur = cur->_left;
			   }
			   //若插入的值比当前树的值大 插入右边
			   else if (cur->_kv.first < kv.first)
			   {
				   parent = cur;
				   cur = cur->_right;
			   }
			   else
			   {
				   //若插入的值,在树中有相同的值 ,则插入失败
				   return false;
			   }
		   }
		   cur = new Node(kv);
		   //再次判断parent当前节点值与插入值大小
		   if (parent->_kv.first > kv.first)
		   {
			   parent->_left = cur;
		   }
		   else
		   {
			   parent->_right = cur;
		   }
		   //cur的上一个节点即为 刚才的记录cur前一个节点的parent
		   cur->_parent = parent;

           //当父节点不为NULL并且父节点为红色
		   while (parent != nullptr && parent->_col == RED)
		   {
			   Node* grandfather = parent->_parent;//爷爷节点

			   //若父节点为爷爷节点的左子树,则unlce为爷爷节点的右子树
			   if (grandfather->_left == parent)
			   {
				   Node* uncle = grandfather->_right;
				   //       g
				   //    p     u
				   // c
				   //情况1:u存在并且为红,直接变色即可,并继续往上处理
				   if (uncle && uncle->_col == RED)
					   //若uncle节点为红色,将parent与uncle都置为黑色,爷爷节点置为红色
				   {
					   parent->_col = BLACK;
					   uncle->_col = BLACK;
					   grandfather->_col = RED;

					   //继续往上调整
					   cur=grandfather;
					   parent = cur->_parent;
				   }
				   //情况2+3:u不存在或者u存在且为黑,旋转+变色
				   else
				   {	 
					   //情况2
					   //g p c 作为一条直线 所以为单旋
					   //    g
					   //  p   u
	 				   //c 
					   if (cur == parent->_left)
					   {
						   //右旋转
						   RotateR(grandfather);
						    
						   //最终p作为最终的根 为黑  g为红 
						   parent->_col = BLACK;
						   grandfather->_col = RED;
					   }
					   //情况3
					   //g p c 作为一条折线 所以为双旋
					   //    g
					  //   p   u
					  //     c 
					   else
					   {
						   //左单旋
						   RotateL(parent);
						   //右单旋
						   RotateR(grandfather);
						   //最终cur作为最终的根 为黑  g为红 
						   cur->_col = BLACK;
						   grandfather->_col = RED;
						   //父节点继续保持红色
						   parent->_col = RED;
					   }
					   break; 
				   }
			   }

			   else//grandfather->_right == parent
				   ////若父节点为爷爷节点的右子树,则unlce为爷爷节点的左子树
			   {
				   //   g
				   // u   p
				   //       c
				   Node* uncle = grandfather->_left;

				   //情况1:u存在并且为红,直接变色即可,并继续往上处理
				   if (uncle && uncle->_col == RED)
					   //若uncle节点为红色,将parent与uncle都置为黑色,爷爷节点置为红色
				   {
					   parent->_col = BLACK;
					   uncle->_col = BLACK;
					   grandfather->_col = RED;

					   //继续往上调整
					   cur = grandfather;
					   parent = cur->_parent;
				   }

				   //情况2+3:u不存在或者u存在且为黑,旋转+变色
				   else
				   {
					   //情况2
					   //g p c 作为一条直线 所以为单旋
					   //    g
					   //  u   p
					   //        c  
					   if (cur == parent->_right)
					   {
						   //左旋转 
						   RotateL(grandfather);

						   //最终p作为最终的根 为黑  g为红 
						   parent->_col = BLACK;
						   grandfather->_col = RED;
					   }
					   //情况3
					   //g p c 作为一条折线 所以为双旋
					   //   g
					  //  u   p
					  //    c 
					   else
					   {
						   //右单旋
						   RotateR(parent);
						   //左单旋
						   RotateL(grandfather);
						   //最终cur作为最终的根 为黑  g为红 
						   cur->_col = BLACK;
						   grandfather->_col = RED;
					   }
					   break;
				   }
			   }
		   }
		   //为了避免grandfather节点正好为根时,会被更新成红色的情况
		   _root->_col = BLACK;
		   return true;
	   }

	   void inorder()//中序遍历
	   {
		   _inorder(_root);
		   cout << endl;
	   }
	   //判断一颗二叉树是否为红黑树
	   bool isbalance()
	   {
		   //检查根是否为黑
		   if (_root && _root->_col == RED)
		   {
			   cout << "根节点颜色是红色" << endl;
			   return false;
		   }
		   //连续的红色节点
		   return _check(_root,0);
	   }

	private:
		bool _check(Node* root,int blacknum)
		{
			if (root == nullptr)
			{
				//为空时,blacknum代表一条路径的黑色节点个数
				cout << blacknum << " ";
				return true;
		    }
			//blacknum代表黑色节点的个数
			if (root->_col == BLACK)
			{
				blacknum++;
			}
			//若当前节点为红 父节点也为红
			if (root->_col == RED
				&&root->_parent 
				&&root->_parent->_col==RED)
			{
				cout << "存在连续的红色节点" << endl;
				return false;
			}
			//遍历整棵树
			return	_check(root->_left,blacknum) && _check(root->_right,blacknum);
		}
		void _inorder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}
			_inorder(root->_left);
			cout << root->_kv.first << " ";
			_inorder(root->_right);
		}
		   void RotateL(Node* parent)//左单旋
		   {
			   Node* subR = parent->_right;
			   Node* subRL = subR->_left;

			   parent->_right = subRL;
			   if (subRL != nullptr)
			   {
				   subRL->_parent = parent;
			   }
			   Node* ppnode = parent->_parent;//记录parent的前一个节点

			   subR->_left = parent;
			   parent->_parent = subR;

			   if (ppnode == nullptr)//说明parent是根即代表整棵树
			   {
				   _root = subR;//subR作为新的根
				   _root->_parent = nullptr;//subR的父节点指向原来的parent,所以要置nullptr
			   }
			   else//说明旋转的是一部分,所以要跟ppnode相连接
			   {
				   if (ppnode->_left == parent)//若连接在左子树上
				   {
					   ppnode->_left = subR;
				   }
				   else//若连接在右子树上
				   {
					   ppnode->_right = subR;
				   }
				   subR->_parent = ppnode;//将subR父节点置为ppnode
			   }
		   }

		   void RotateR(Node* parent)//右单旋
		   {
			   Node* subL = parent->_left;
			   Node* subLR = subL->_right;
			   parent->_left = subLR;
			   if (subLR != nullptr)
			   {
				   subLR->_parent = parent;
			   }
			   Node* ppnode = parent->_parent;//记录parent的父节点
			   subL->_right = parent;
			   parent->_parent = subL;

			   if (ppnode == nullptr)//若旋转整棵树
			   {
				   _root = subL;
				   _root->_parent = nullptr;
			   }
			   else//若旋转整棵树的部分子树
			   {
				   if (ppnode->_left == parent)
				   {
					   ppnode->_left = subL;
				   }
				   else
				   {
					   ppnode->_right = subL;
				   }
				   subL->_parent = ppnode;//使subL的父节点为ppnode
			   }

		   }
	private:
		Node* _root = nullptr;
};

void test_RBTree1()
{
	int a[]={16, 3, 7, 11, 9, 26, 18, 14, 15};
	//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16,14 };
	RBTree<int, int>v1;
	for (auto e : a)
	{
		v1.insert(make_pair(e, e));
	}
	v1.inorder();
	cout << v1.isbalance() << endl;
	
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-10-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 红黑树概念
  • 2. 红黑树性质
  • 3. 结构定义
    • 关于默认节点为红/黑色的讨论
    • 4. insert
      • 情况1—— uncle节点存在且为红色(g p c左斜形成一条直线)
        • 情况2——uncle节点不存在/存在且为黑色(g p c 左斜形成直线 右单旋)
          • uncle节点不存在
          • uncle节点存在并且为黑色
        • 情况3——uncle节点不存在/存在且为黑色(g p c 形成左折线 双旋)
          • uncle节点不存在
          • uncle节点存在并且为黑色
        • 情况1—— uncle节点存在且为红色(g p c右斜形成一条直线)
          • 情况2——uncle节点不存在/存在且为黑色(g p c 右斜形成直线 左单旋)
            • 情况3——uncle节点不存在/存在且为黑色(g p c 形成右折线 双旋)
            • 5.判断是否为红黑树
            • 6. 整体代码
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档