还在感觉为红黑树所困扰?相信我你就点进来看!!!★★★_红黑树好麻烦(2)

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

红黑树删除,点击这里!!!

红黑树 :红黑树是一种特殊的二插查找数,不过为了防止二插查找树在最差情况下蜕变成链表,因此红黑树利用了结点的颜色来调节树的平衡,这使得红黑树的任意一条路径长度都不可能超过其他路径的两倍,因此红黑树是一种自调节的高度平衡的二叉查找树。
性质

  • 性质1:结点要么是红要么是黑
  • 性质2:根结点必须是黑色
  • 性质3:每个叶子结点(NIL)是黑色
  • 性质4:每个红色结点的两个子结点一定都是黑色。,不能有两个红色结点相连
  • 性质5:从根结点开始,到这条路径上的空结点,各条路径上的黑色结点个数相同

红黑树的添加操作与删除操作相比,要简单的多,不过比其他数据结构的操作要稍微复杂那么一点,但是如果理解操作的具体过程,也并不是太复杂。我相信,大家跟着我一起,看完这篇文章,一定能掌握红黑树的插入操作!!!

插入情况分析:我们首先来分析一下红黑树的插入情况有哪几种,然后各个击破!!!

对于红黑树插入结点来讲,插入结点时,插入的位置要么是根,要么不是根;插入结点的父亲要么是红色,要么是黑色。总体来看就这几种情况,如下:

按照插入结点的位置情况分

1 插入位置是根
2 插入位置非根

按照插入时的颜色情况分

1 插入结点的父亲是红
2 插入结点的父亲是黑

总体分析插入结点颜色情况

在我们对结点进行插入时,插入的结点颜色应该是红色还是黑色呢?
首先,我们应该知道,在进行插入时,我想总想尽可能的用最少的操作来达到插入结点时红黑树的平衡,这样红黑树的插入效率才会高。因此,如果插入结点是黑色时,由于红黑树要求每条路径上的黑色结点个数相同,当插入结点是黑色时,这条路径上的黑色结点个数就比其他路径上的黑色结点数多一个,要想使得红黑树平衡,我们就需要对整颗红黑树进行调整,显然非常麻烦。而当插入结点是红色时,黑色结点个数不变,只是有可能其父亲结点是红色,这里发生冲突。即使发生冲突,但我们只需要对发生冲突的这里单独进行调整,并不会操作整颗树,因此插入结点是红色的效率要比插入结点是黑色的效率要高!!!

注:红黑树的插入操作,插入结点时,所插入的结点一定是红色结点!!!

如果插入位置处的父结点的颜色是黑色?
此时,我们直接将结点插入即可,因此红色结点并不影响黑色结点个数,也不与红色结点冲突。

如果插入位置处的父结点是红色?
此时,我们也是要直接插入,但是情况要稍微复杂一点,需要进行调整,详细调整过程我们在下面进行讨论,这里先不讨论。
我们想一下,如果插入位置处的父结点是红色,那么其父结点的兄弟(待插入结点的叔叔)可能是红色,也可能是黑色。

详细讨论红黑树结点插入

为了保证每次情况的讨论都是封闭的,即考虑所有的讨论情况,因此我们要互补的进行讨论,即讨论类别为:是/不是,或者为红/黑,这种讨论都是封闭的。
我们已经知道了,对红黑树的结点进行插入时,插入的结点可能是根,也可能不是根。因此,我们需要从插入节点是根和不是根两种情况进行讨论。如果插入的结点是根,那么我们直接将插入节点变黑,根指向插入的结点,结束,如下图(插入6):
在这里插入图片描述

1 插入的结点是根:根指向插入的结点,结点变黑,结束
2 插入的结点不是根

插入的结点是根的时候,情况很简单。那么如果插入的结点不是根呢?对于插入的结点不是根时,就说明插入的结点有父亲,由于父亲结点的颜色关系着我们对红黑树的调整情况。因此我们就需要对插入结点的父亲颜色进行讨论。由于其父亲就可能是红色或者黑色,我们的讨论就分为父亲颜色是黑色和父亲颜色是红色。如果父亲的颜色是黑色,很简单,我们直接将结点插入就行,不需要对红黑树进行调整,应为红色结点不会增加黑色结点的个数(不需要考虑父亲节点是不是根,这里只是针对父结点是黑这种情况,此时插入结点可能是父亲的左,也可能是父亲的右,图片只画出插入结点是父亲的左,插入结点是右时基本一样),如下图(插入6):
在这里插入图片描述

1 插入的结点是根:根指向插入的结点,结点变黑,结束
2 插入的结点不是根

2.1 如果插入结点的父亲是黑结点:链接插入结点,结束
2.2 如果插入结点的父亲是红结点

对于插入结点的父亲结点颜色是黑色时,情况也并不复杂。下面看一下如果插入结点的父亲结点颜色是红色时情况如何。
如果插入结点的父亲结点颜色是红色,那么此时就需要对红黑树进行调整。由于调整可能涉及到旋转,因此我们需要对插入结点是父亲节点的左还是父亲节点的右进行讨论,再进行这个讨论前,我们同样需要判断父亲结点是爷爷结点的左还是右,即如下所示:

1 插入的结点是根:根指向插入的结点,结点变黑,结束
2 插入的结点不是根

2.1 如果插入结点的父亲是黑结点:链接插入结点,结束
2.2 如果插入结点的父亲是红结点

2.2.1如果父亲是爷爷的左

2.2.1.1如果插入结点是父亲的左

2.2.1.1.1如果叔叔的颜色是红:
2.2.1.1.2如果叔叔的颜色是黑:
2.2.1.2如果插入结点是父亲的右

2.2.2如果父亲是爷爷的右

2.2.1.1如果插入结点是父亲的左
2.2.1.2如果插入结点是父亲的右

对于父亲结点颜色为红色时,如果此时父亲是爷爷的左,插入结点是父亲的左,分析如下。如果父亲结点的颜色是红色,那么爷爷结点的颜色一定是黑色。由于插入的结点和父亲的结点颜色都是红色,因此需要进行变色。那么是对插入结点进行变色还是对父亲结点进行变色呢?很显然是需要对父亲结点进行变色。为什么呢?如果是对插入结点变色,那么和直接插入黑色结点有什么区别呢?所示是要对父亲结点进行变色,即将父亲结点颜色变黑。如果父亲结点颜色变黑,那么该条路径就会多出一个黑色结点,因此需要将爷爷结点颜色变红,如果爷爷结点颜色变红,那么叔叔结点那条路径就会少一个黑色结点。然而,由于爷爷结点颜色是黑色,所以叔叔结点颜色可能是黑色,也可能是红色。所以此时我们就需要对叔叔结点的颜色进行讨论,即如果叔叔结点的颜色是黑色,如果叔叔结点的颜色是红色。如果叔叔结点的颜色是红色时,这种情况我们只需要将叔叔结点的红色变为黑色,这条路径黑色结点颜色就相同。至此,叔叔这条路径和父亲这条路径的黑色结点个数都保持了平衡。然而,由于我们将爷爷结点的颜色变成了红色,那么爷爷的父亲之前如果也是红色,是不是又不平衡了?所以,这里我们需要将爷爷为新插入的结点,重新进行讨论!!!总结来看,如果父亲节点颜色是红,父亲是爷爷的左,插入结点是父亲的左,叔叔结点是红色,我们需要将父亲变黑,爷爷变红,叔叔变黑,然后爷爷为新插入的结点,重新讨论!!!不过,我们想一下,我们在讨论叔叔的颜色时好像并没有发生旋转,仅仅是变色,与父亲是不是爷爷的左,插入结点是不是父亲的左无关啊,所以,在这里我们可以先对叔叔的结点颜色进行讨论,而后再讨论左右;因此,当父亲结点和叔叔结点都是红色时,可这样讨论“如果父亲和叔叔是红色,则父亲变黑,爷爷变红,叔叔变黑,然后爷爷变为新“插入”的结点,重新判断,这种情况我们有个需要注意的就是,如果爷爷是根的话,那么我们直接将爷爷变黑,直接结束即可,如下所示(插入3)(注意:只要不发生旋转,就不需要单独的讨论左还是右!!!):
在这里插入图片描述

1 插入的结点是根:根指向插入的结点,结点变黑,结束
2 插入的结点不是根

2.1 如果插入结点的父亲是黑结点:链接插入结点,结束
2.2 如果插入结点的父亲是红结点

2.2.1如果叔叔的颜色是红:父亲变黑,爷爷变红,叔叔变黑,然后爷爷为新插入的结点,重新讨论

2.2.2如果叔叔的颜色是黑

2.2.2.1如果父亲是爷爷的左

2.2.2.1.1如果插入结点是父亲的左
2.2.2.1.2如果插入结点是父亲的右

2.2.2.2如果父亲是爷爷的右

2.2.2.2.1如果插入结点是父亲的左
2.2.2.2.2如果插入结点是父亲的右

如上所示,这样讨论是不是显得更简单点呢?关于叔叔结点是红色的时候就讨论完了,下面我们看一下叔叔结点是黑色的情况。如果叔叔结点是黑色,那么显然我们就不能将叔叔结点变黑,因为叔叔本来就是黑色。所以,如果此时想要让叔叔这条路径多出一个黑结点,那么我们必须要发生旋转。因此,我们可以有这样一个结论:只有当父亲是红,叔叔是黑时才会发生旋转!!!只有发生旋转时我们才需要具体的讨论左右情况!!!。所以,现在我们就讨论一下左右情况;由于父亲是爷爷的左和父亲是爷爷的右这两种情况基本一样,只是旋转情况相反,因此我们只对父亲是爷爷的左这种情况进行详细讨论,另一种情况就自己研究研究吧,很简单。

对于父亲是红,叔叔是黑时,我们首先看一下如果父亲是爷爷的左,插入结点是父亲的左这种情况,如下图(插入3):
在这里插入图片描述

此时6是3的父亲,NULL是3的叔叔,这里我们可能会有一个疑问,为什么叔叔是NULL?在此种情况,叔叔只可能是NULL,如果叔叔是黑且不是NULL,在3插入之前6一定是叶子结点,然而6的兄弟如果不是NULL,这颗树肯定不会平衡,因此此时6的兄弟一定是NULL。如果此时我们将父亲变黑,父亲这条路径会多出一个黑结点,那么就将爷爷变红,父亲这条路径就少一个黑结点;然而,由于爷爷是父亲和叔叔的公共节点,因此叔叔这条路径同样少了一个黑色结点。如果我们想让叔叔这条路径不少黑色结点,我们应该很自然的就能想到将父亲节点6变成公共黑结点,即右旋,这样这两条路径都不少黑结点,由于黑色结点成为了公共节点,我们此时也不必担心黑结点的父亲是不是红了,因此这样调整后这课红黑树就平衡了,总的调节过程如下:父亲变黑,爷爷变红,以爷爷为支点右旋,如下图所示:
在这里插入图片描述对于父亲是红,叔叔是黑时,我们首先看一下如果父亲是爷爷的左,插入结点是父亲的右这种情况,如下图(插入9):
在这里插入图片描述此种情况,我们观察一下,应该就不难看出。如果我们以父亲为支点进行左旋,这时这个情况就和上种情况一样了,即插入结点是父亲的左,父亲是爷爷的左?所以,对于此种情况,我们只需要进行一下左旋,然后即可将此种情况变换为上一种情况,然后重新判断即可。不过,我们需要注意的是,在进行左旋时,是以父亲为支点左旋,并且要将父亲结点作为新插入的结点重新判断,总结就是(父为新插入的结点,以父亲为支点左旋,重新判断)如下图所示:
在这里插入图片描述以上所有情况都以列出,并进行了详细的讨论,下面综合一下各种情况如下:

1 插入的结点是根:根指向插入的结点,结点变黑,结束
2 插入的结点不是根

2.1 如果插入结点的父亲是黑结点:链接插入结点,结束
2.2 如果插入结点的父亲是红结点

2.2.1如果叔叔的颜色是红:父亲变黑,爷爷变红,叔叔变黑,然后爷爷为新插入的结点,重新讨论

2.2.2如果叔叔的颜色是黑

2.2.2.1如果父亲是爷爷的左

2.2.2.1.1如果插入结点是父亲的左:爷爷变红,父亲变黑,爷爷为支点右旋,结束
2.2.2.1.2如果插入结点是父亲的右:父为新Z,父为支点左旋,重新判断

2.2.2.2如果父亲是爷爷的右

2.2.2.2.1如果插入结点是父亲的左:父为新Z,父为支点右旋,重新判断
2.2.2.2.2如果插入结点是父亲的右:爷爷变红,父亲变黑,爷爷为支点左旋,结束

注意

1 只有当父亲是红,叔叔是黑时才会发生旋转!!!
2 只有发生旋转时我们才需要具体的讨论左右情况!!!
3 如果父亲是红,叔叔是红时,在我们变色后重新判断之前,需要先判断爷爷是不是根,如果爷爷是根,那么将爷爷变黑结束即可。
4 在进行结点插入时,由于一个结点包含三个指针,即左右孩子指针和父指针,所千万不要忘记链接父亲指针。

代码如下
#include <stdio.h>
#include <stdlib.h>
enum COLOR{RED,BLACK};
typedef struct tree
{
	int nValue;
	enum COLOR color; 
	struct tree \*pfather;
	struct tree \*plchild;
	struct tree \*prchild;
}BinaryTree;

void RightRotaryTree(BinaryTree \*pTree,BinaryTree \*\*pRoot)
{
	if(pTree == NULL || pTree->plchild == NULL) return;

	BinaryTree \*pRotaryChild = pTree->plchild;
	BinaryTree \*pRotaryGS = pTree->plchild->prchild;
	BinaryTree \*pRotaryFather = pTree->pfather;
	//旋转的点的左孩子重新连接
	pTree->plchild = pRotaryGS;
	if(pRotaryGS != NULL)
	{
		pRotaryGS->pfather = pTree;
	}
	//旋转点的左孩子
	pRotaryChild->prchild = pTree;
	pTree->pfather = pRotaryChild;
	//如果有父亲,连接旋转点的父亲
	pRotaryChild->pfather = pRotaryFather;
	if(pRotaryFather != NULL)
	{
		if(pRotaryFather->plchild == pTree)
		{
			pRotaryFather->plchild = pRotaryChild;
		}
		else
		{
			pRotaryFather->prchild = pRotaryChild;
		}
	}
	else//旋转的是根结点时换根
	{
		\*pRoot = pRotaryChild;
	}
}
void LeftRotaryTree(BinaryTree \*pTree,BinaryTree \*\*pRoot)
{
	if(pTree == NULL || pTree->prchild == NULL) return;

	BinaryTree \*pRotaryChild = pTree->prchild;
	BinaryTree \*pRotaryGS = pTree->prchild->plchild;
	BinaryTree \*pRotaryFather = pTree->pfather;
	//旋转的点的左孩子重新连接
	pTree->prchild = pRotaryGS;
	if(pRotaryGS != NULL)
	{
		pRotaryGS->pfather = pTree;
	}
	//旋转点的左孩子
	pRotaryChild->plchild = pTree;
	pTree->pfather = pRotaryChild;
	//如果有父亲,连接旋转点的父亲
	pRotaryChild->pfather = pRotaryFather;
	if(pRotaryFather != NULL)
	{
		if(pRotaryFather->plchild == pTree)
		{
			pRotaryFather->plchild = pRotaryChild;
		}
		else
		{
			pRotaryFather->prchild = pRotaryChild;
		}
	}
	else//旋转的是根结点时换根
	{
		\*pRoot = pRotaryChild;
	}
}
BinaryTree\* SearchNode(BinaryTree \*pRoot,int nValue)
{
	if(pRoot == NULL) return NULL;

	BinaryTree \*pFather = pRoot;
	BinaryTree \*pNode = pRoot;
	while(pFather)
	{
		pNode = pFather;
		if(pFather->nValue < nValue && pFather->prchild)
			pFather = pFather->prchild;
		else if(pFather->nValue > nValue)
			pFather = pFather->plchild;
		else
			return pFather;
	}
	return pNode;
}
//获取叔叔结点
BinaryTree \*GetUncle(BinaryTree \*pFather)
{
	BinaryTree \*pGrandFather = pFather->pfather;
	if(pGrandFather == NULL)
		return NULL;
	else if(pGrandFather->plchild == pFather)
		return pGrandFather->prchild;
	else
		return pGrandFather->plchild;
}
//获取兄弟结点
BinaryTree \*GetBrother(BinaryTree \*pFather,BinaryTree \*pNode)
{
	return pFather->plchild != pNode ? pFather->plchild : pFather->prchild;
}
//插入红黑树的结点
void InsertRBTNode(BinaryTree \*pTree,BinaryTree \*\*pRoot,int nValue)//Z为新插入的结点
{
	//为新插入的结点申请空间
	BinaryTree \*pTemp = (BinaryTree\*)malloc(sizeof(BinaryTree));
	pTemp->color = RED;
	pTemp->nValue = nValue;
	pTemp->plchild = NULL;
	pTemp->prchild = NULL;
	pTemp->pfather = NULL;

	//树为空:直接插入,并且根变黑
	if(\*pRoot == NULL)
	{
		\*pRoot = pTemp;
		(\*pRoot)->color = BLACK;
		return;
	}
	//查找插入结点位置
	BinaryTree \*pFather = SearchNode(\*pRoot,nValue);
	//如果插入的结点相同
	if(pFather->nValue == nValue)
		return;
	//直接插入
	if(nValue < pFather->nValue)
		pFather->plchild = pTemp;
	else
		pFather->prchild = pTemp;
	//链接父亲
	pTemp->pfather = pFather;

	//父亲结点是黑色:结束
	if(pFather->color == BLACK)
		return;
	//父亲结点是红色
	BinaryTree \*pGrandFather = pFather->pfather;
	BinaryTree \*pUncle = GetUncle(pFather);
	while(pFather->color == RED)


![img](https://img-blog.csdnimg.cn/img_convert/2a82013dcf3654b5d4796d7e0b0ca460.png)
![img](https://img-blog.csdnimg.cn/img_convert/2fdbc79a8f09881ca1c6bc68b47fda2d.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**


	//直接插入
	if(nValue < pFather->nValue)
		pFather->plchild = pTemp;
	else
		pFather->prchild = pTemp;
	//链接父亲
	pTemp->pfather = pFather;

	//父亲结点是黑色:结束
	if(pFather->color == BLACK)
		return;
	//父亲结点是红色
	BinaryTree \*pGrandFather = pFather->pfather;
	BinaryTree \*pUncle = GetUncle(pFather);
	while(pFather->color == RED)


[外链图片转存中...(img-vyHGgMeY-1715889921730)]
[外链图片转存中...(img-3NGTEF42-1715889921731)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
红黑树是一种在2-3树基础上发展而来的自平衡二叉搜索树。它的特是每个节都有一个颜色属性,可以是红色或黑色。红黑树满足以下性质: 1. 每个节要么是红色,要么是黑色。 2. 根节是黑色。 3. 每个叶子节(NIL节,空节)是黑色。 4. 如果一个节是红色的,则它的两个子节都是黑色的。 5. 对于每个节,从该节到其所有后代叶子节的简单路径上,均包含相同数目的黑色节。 这些性质保证了红黑树的平衡性和高效性。红黑树的平衡性是通过对节进行颜色调整和旋转操作来实现的。这些操作可以保持树的高度在O(log n)范围内,从而保证了树的查找、插入和删除操作的时间复杂度都是O(log n)。 红黑树的应用非常广泛,特别是在高级语言的编译器和标准库中。它常被用作实现有序集合、映射和优先队列等数据结构。红黑树的特性使得它在插入和删除操作频繁的情况下仍能保持较好的性能。 如果你对红黑树还不太了解,建议先学习2-3树的基础知识,因为红黑树是在2-3树的基础上发展而来的。了解2-3树的结构和操作可以帮助你更好地理解红黑树的原理和实现。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *2* *3* [面试让我手写红黑树?!](https://blog.csdn.net/Yao__Shun__Yu/article/details/127206776)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v4^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值