【C++学习】:红黑树(上)

前言:

一个学期过去了,稀里糊涂的忙忙碌碌总算有时间整理之前学过的东西了hhh,多余的话后记说吧,写博客顺带回忆一下红黑树。

一、概念

红黑树红黑树,当然是以红色和黑色的结点构成的树(这就是红黑树基本特点)。

确实,我们的红黑树的各个结点中存放着一个标志颜色的标识。

1.1红黑树本体代码

template <class T>
struct RBTreeNode//红黑树结点
{
	RBTreeNode<T>* _left;//红黑树是一个三叉链的树形结构。
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	Colour _col;//<-------颜色标识
	T _data;    //数据的存放
	RBTreeNode(const T&data= T())
		:_data(data)
		, _left(nullptr)
		, _right(nullptr)
		,_parent(nullptr)
		,_col(RED)
	{}
};

 二、红黑树规则

红黑树作为一种较为复杂的二叉树,必定有着其自己的规则。

常见规则如下

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的 
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 

然后是相对难以理解的规则

  1. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 
  2.  每个叶子结点都是黑色的(此处的叶子结点指的是空结点) 

第一点是的意思是从任意一个结点开始计算,到叶子结点的黑色结点都相同。以此来保证了我们的最长路径不超过最短路径的2倍。黑色结点可以连续。

第二点中叶子节点指代空节点我们称他为(NIL节点) 

三、红黑树与AVL树

不能说红黑树是AVL树就比avl高级,红黑树比avl相对宽松;(话说就像高考和大学考试一样(雾))

AVL树只要不是一就要立刻旋转该树的结构。

红黑树给我们扩大了他的高低容忍差。

像下面哪个情况,AVL树早就不知道旋转多少次了。

但是红黑树就是可以容忍。

AVL树和红黑树的区别如下

  •  AVL树是更加严格的平衡,因此可以提供更快的查找速度,一般读取查找密集型任务,适用AVL树。
  • 红黑树更适合于插入修改密集型任务。
  • 相对于要求严格平衡的AVL树来说,红黑树的旋转次数少,对于插入、删除操作较多的情况下,选择红黑树。
  • 红黑树的查询性能略微逊色于AVL树,但是红黑树在插入和删除上优于AVL树。

 

3.1红黑树的插入时的上色与旋转。

前面插入位置寻找的逻辑与搜索二叉树逻辑一样(高端的程序员往往会用简单的方式来处理代码(ctrl+c+v))

ret_type Insert(const T& data)
{
	if (_root == nullptr)
	{
		_root = new Node(data);
		_root->_col = BLACK;
		return make_pair(iterator(_root), true);
	}
	else
	{
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			parent = cur;
			if (kot(cur->_data) > kot(data))
			{
				cur = cur->_left;
			}
			else if (kot(cur->_data) < kot(data))
			{
				cur = cur->_right;
			}
			else
			{
				return make_pair(iterator(cur), false);
			}
		}
		cur = new Node(data);
		Node* ret = cur;
		if (kot(parent->_data) < kot(cur->_data))
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;
        //准备上色旋转
 

3.1.2上色

为了不会一插入就旋转,我们新结点初始状态就是红结点,为什么不是黑色呢?

如果我们插入黑色,在这棵树中有极大可能让这条路径比其他路径多一个黑色结点严重破坏结构,还得去检查前提路径黑结点情况,各种修改,各种调整,极其复杂。

所以我们都是默认新结点是红色结点,我们只需要检查脑袋上的父亲结点的颜色。

情况一:空树

那么我们的插入就是根结点,改变一下,_root链接,然后变个色就好了。

情况二:非空树,父节点为黑

这也很爽,由于父亲是黑色,我们插入一个红色结点不会破坏如何规则。

 情况三:非空树,父节点为红色只需染色操作

开始操作

cur为刚刚插入的结点,父节点为红,我们需要检查uncle结点为红色,我们将parent和uncle染成黑色保证父节点与uncle结点所在的子树黑数量相同 

我们还需要将gf染成红色,保证了17结点的左右子树路径黑色结点相同 

将gf指针赋给cur指针,其指针也一起变动。

如果cur->_parent为黑色就结束,p为红色就重复之前动作 

 

parent等于空的时候退出循环。

  无论是哪种情况,我们最好在循环后都加上一句_root=_col=BLACK,保证大树根为黑。

while (parent && parent->_col == RED)
{
 
}
_root->_col = BLACK;

3.1.3上色+旋转 

记住我们的uncle结点是很重要的,他是判别旋转的一个标志。

假设我们在上色的过程中uncle结点原本为空或者黑色的那可就麻烦了。

 第一次上色uncle为红允许改变。

 

 到了我们第二次操作的时候发现我们的parent为红色需要调整,但是uncle却是黑色的,不可以直接色,应为当前的gf开始每一条路径的黑结点数量都相同,如果我们一意孤行只改变p的颜色的话,会发现gf的左树路径比右数路径上的黑节点多一个,这是极其不正确的!破坏了红黑树的路径黑结点数量相同的原则。

我们需要开始旋转了,根据cur确定是双旋还是单旋。图里的是左单选情况。

情况四:单旋

 旋转完后为了防止我们所看见的树也是一颗子树,我们恢复到旋转前黑色结点差,并且遵守规则,我们将p的颜色改变为BLACK,将gf改变为RED,

这样哪怕这就是是整颗红黑树,那他的根也是黑的,如果他只是红黑树的一个子树,我们的在各个路径上黑色结点的数量在插入后也是不改变的:插入前2个黑色结点,插入后依旧是2个。 

 

看上图,这样我们30的左子树插入一个结点后旋转,但是插入后路径的黑结点都是2未该改变,这样我们就30结点左子树的插入不会影响与右子树路径黑结点数量的差。30插入前后左右子树路径的黑色结点都是2个黑

将grandfather指针赋值给cur后,重新标定cur与parent位置,但注意不能随意标定grandfather,为什么呢?我们的parent可能为空。

 所以只有在parent存在且为红的时候我们才可以对grandfather赋值。

情况五:双旋情况

(跟着我左手右手一个慢动作)

我们现在需要插入数据为13的结点,先链接到树中 

uncle存在且为红 

将grandfather指针赋值给cur,cur->parent._col==RED。继续循环,标定其他指针

 发现uncle这是为黑结点,我们需要开始旋转,发现gf->right==p p->left==cur所以我们这时候需要双旋存在,这棵树该先右旋在左旋 

换成流程图后便于理解

 

 先对20结点右旋 

再对grandfather左旋

 

 最后将cur颜色该为BLACK,grandfather改变为RED 

四、完整代码:

pair<iterator, bool> Insert(const T& data)
{
	KeyOfT kot;
	if (_root == nullptr)
	{
		_root = new Node(data);
		_root->_col = BLACK;
		return make_pair(iterator(_root), true);
	}
	else
	{
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			parent = cur;
			if (kot(cur->_data) > kot(data))
			{
				cur = cur->_left;
			}
			else if (kot(cur->_data) < kot(data))
			{
				cur = cur->_right;
			}
			else
			{
				return make_pair(iterator(cur), false);
			}
		}
		cur = new Node(data);
		Node* ret = cur;
		if (kot(parent->_data) < kot(cur->_data))
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			assert(grandfather->_col == BLACK);
			Node* uncle = nullptr;
			if (parent == grandfather->_left)
			{
				uncle = grandfather->_right;
 
				//情况一:uncle存在,且为红。
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况二+三:uncle不存在,或者存在为黑。
				else
				{
					//单旋
					if (parent->_left == cur)
					{
						RotateR(parent->_parent);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//双旋
					else
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
			else /*if (parent == grandfather->_right)*/
			{
				uncle = grandfather->_left;
 
				//情况一:uncle存在,且为红。
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况二+三:uncle存在,且为红。
				else
				{
					if (parent->_right == cur)
					{
						RotateL(parent->_parent);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//双旋
					else
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
						int n = 0;
					}
					break;
				}
			}
		}
		_root->_col = BLACK;
		return make_pair(iterator(ret), true);
	}
}
//......其他代码
public:
 
void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
 
	parent->_right = subRL;
	if (subRL)
	{
		subRL->_parent = parent;
	}
	Node* pparent = parent->_parent;
 
	parent->_parent = subR;
	subR->_left = parent;
	if (_root == parent)
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else
	{
		if (pparent->_left == parent)
		{
			pparent->_left = subR;
		}
		else
		{
			pparent->_right = subR;
		}
		subR->_parent = pparent;
	}
}
 
void RotateR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
 
	parent->_left = subLR;
	if (subLR)
	{
		subLR->_parent = parent;
	}
	Node* pparent = parent->_parent;
 
	parent->_parent = subL;
	subL->_right = parent;
	if (_root == parent)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		if (pparent->_left == parent)
		{
			pparent->_left = subL;
		}
		else
		{
			pparent->_right = subL;
		}
		subL->_parent = pparent;
	}
}

后记:

这个学期经历了打工、组织活动、参加比赛、裸考四级、然后就特么期末了、我都不清楚自己在忙什么啊哈哈(;д;)但愿能赶回来吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值