既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
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)
{
//叔叔红:父变黑,叔叔变黑,爷爷变红,爷爷为新Z,爷爷非根重新判断
if(pUncle && pUncle->color == RED)
{
pFather->color = BLACK;
pUncle->color = BLACK;
pGrandFather->color = RED;
pTemp = pGrandFather;
//如果爷爷是根
if(pTemp == \*pRoot)
{
pTemp->color = BLACK;
return;
}
//更新父子关系
pFather = pTemp->pfather;
pUncle = GetUncle(pFather);
pGrandFather = pFather->pfather;
continue;
}
//叔叔黑(叔叔黑才发生旋转,另外叔叔黑可能是空)
else
{
//爷爷为空:父亲变黑,结束
if(pGrandFather == NULL)
{
pFather->color = BLACK;
return;
}
//爷爷非空
else
{
//父亲是爷爷的左
if(pFather == pGrandFather->plchild)
{
//Z是父亲的左:爷爷变红,父亲变黑,爷爷为支点右旋,结束
if(pTemp == pFather->plchild)
{
pGrandFather->color = RED;
pFather->color = BLACK;
RightRotaryTree(pGrandFather,pRoot);
return;
}
//Z是父亲的右:父为新Z,父为支点左旋,重新判断
else
{
pTemp = pFather;
LeftRotaryTree(pTemp,pRoot);
//更新父子关系
pFather = pTemp->pfather;
pGrandFather = pFather->pfather;
pUncle = GetUncle(pFather);
continue;
}
}
//父亲是爷爷的右
else
{
//Z是父亲的左:父为新Z,父为支点右旋,重新判断
if(pTemp == pFather->plchild)
{
pTemp = pFather;
RightRotaryTree(pTemp,pRoot);
//更新父子关系
pFather = pTemp->pfather;
pGrandFather = pFather->pfather;
pUncle = GetUncle(pFather);
continue;
}
//Z是父亲的右:爷爷变红,父亲变黑,爷爷为支点左旋,结束
else
{
pGrandFather->color = RED;
pFather->color = BLACK;
LeftRotaryTree(pGrandFather,pRoot);
return;
}
}
}
}
}
}
//删除结点
void DeleteRBTNode(BinaryTree \*pTree,BinaryTree \*\*pRoot,int nValue)
{
if(\*pRoot == NULL) return;
//查找要删除的结点,并转换(真正要删除的只有一个孩子或无孩子)
BinaryTree \*pNode = SearchNode(pTree,nValue);
BinaryTree \*pMark = pNode;//标记要删除的结点
//如果没找到,则直接返回
if(pNode == NULL || pNode->nValue != nValue)
{
printf("There don't have the nValue int the tree.\n");
return;
}
//如果找到了则将结点转换:转换成真正要删除的结点
BinaryTree \*pFather = pNode->pfather;
//寻找左边最右替换删除结点
if(pNode->plchild && pNode->prchild)//有两个孩子才进行转换
{
pNode = pNode->plchild;
while(pNode->prchild)
pNode = pNode->prchild;
pMark->nValue = pNode->nValue;
}
//如果删除的是根
if((\*pRoot)->nValue == nValue)
{
//如果根无孩子:直接删除,结束
if((\*pRoot)->plchild == NULL && (\*pRoot)->prchild == NULL)
{
free(\*pRoot);
\*pRoot = NULL;
return;
}
//如果根有一个孩子:直接删除,红孩子成为新根,根变黑,结束
if(((\*pRoot)->plchild == NULL && (\*pRoot)->prchild != NULL) || ((\*pRoot)->plchild != NULL && (\*pRoot)->prchild == NULL))
{
BinaryTree \*pDel = \*pRoot;
\*pRoot = (\*pRoot)->plchild == NULL? (\*pRoot)->prchild:(\*pRoot)->plchild;
(\*pRoot)->color = BLACK;
(\*pRoot)->pfather = NULL;
free(pDel);
pDel = NULL;
return;
}
}
//-----------下面删除的是非根的情况-------------------(因为上面判断的就是删除的是根的情况,因此走下来就一定是非根)
//如果删除的是红结点:直接删除,父亲指向空,结束
pFather = pNode->pfather;
//printf("pFather[%d]\n",pFather->nValue);
if(pNode->color == RED)
{
if(pFather->plchild == pNode)
pFather->plchild = NULL;
else
pFather->prchild = NULL;
printf("pNode[%d]\n",pNode->nValue);
free(pNode);
pNode = NULL;


**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**
面判断的就是删除的是根的情况,因此走下来就一定是非根)
//如果删除的是红结点:直接删除,父亲指向空,结束
pFather = pNode->pfather;
//printf("pFather[%d]\n",pFather->nValue);
if(pNode->color == RED)
{
if(pFather->plchild == pNode)
pFather->plchild = NULL;
else
pFather->prchild = NULL;
printf("pNode[%d]\n",pNode->nValue);
free(pNode);
pNode = NULL;
[外链图片转存中...(img-AjGUFAUK-1715889888524)]
[外链图片转存中...(img-xF5shtBJ-1715889888524)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**