该文主要解释红黑树的插入和插入后的再平衡
所有代码已经过编译,编译环境ubuntu12.04LTS GCC 4.6.3
完整的红黑树代码已上传GitHub:https://github.com/xusongqi/Data_Structures
————————————————————————————————————————————
插入算法(插入+再平衡)在4月30日前后完成的,因为删除算法还没完成,就没有发这篇博文。
上午总算把删除算法弄得差不多了......
————————————————————————————————————————————
前一阵子写完了AVL树之后就有点手痒,想写写红黑树~五一前后就抽了几天,看了几篇关于红黑树的文章和代码,了解差不多之后就动手写了这片文章和这棵树。写之前先看了July的关于红黑树的系列文章,感觉不错不过还是有几个小细节没太理解,又陆陆续续找了几篇其他人的博客看了一下,原理get。其实红黑树经典(同样也是难点)的地方在于插入(和平衡)和删除(和平衡)。这样算起来,应该是有4个point:插入函数,插入再平衡函数,删除函数,删除再平衡函数。下面分别按顺序写一下学习过程中我对这4个point的感受。
红黑树的性质
首先,我们列出红黑树的性质,一共五条:
1)每个结点要么是红的,要么是黑的。
2)根结点是黑的。
3)每个叶结点,即空结点(NIL)是黑的。
4)如果一个结点是红的,那么它的俩个儿子都是黑的。
5)对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。
插入函数
插入在这四个当中其实还是比较简单的,因为基本原理和二叉树的差不多(二叉树的插入原理在早先文章有描述,此处不再赘述),我觉得一张流程图+代码足以~具体的细节在代码的主时尚写的比较详细。
流程图
代码
/*插入函数*/
RB_Tree RB_Insert(int key, RB_Tree index)
{
RB_Tree parent = NULL;//插入节点的父节点
RB_Tree node = NULL;//插入节点
/*判定:若辅助查找函数返回非空,则树中已存在相同的key值。返回root节点。
* (赋值表达式返回的值等于表达式左值)*/
if( node = RB_Search_Assist(key, index, &parent) )
{
return index;
}
/*开辟节点(已从辅助查询函数中得到插入点的parent位置)*/
node = (RB_Tree)malloc(sizeof(RB_Node));
node->color = RED; //新节点初始化为红色
node->key = key; //写入key值
node->parent = parent; //之前带回了parent位置
node->leftChild = NULL;
node->rightChild = NULL;
/*插入新节点*/
if(parent)//若parent不为空(树非空)
{
if(key < parent->key)
{
parent->leftChild = node;
}
else
{
parent->rightChild = node;
}
}
else//!parent 树为空,当前节点成为root点
{
index = node;
}
/*树的再平衡*/
return RB_Insert_Rebalance(node, index);
}
插入再平衡函数
在插入节点后,因为我们默认了新节点是红色,所以可能会破坏第2条和第5条。所以我们需要对树进行再平衡,让它符合红黑树的性质。
若插入的是根结点,直接染黑,OK。
若插入后的父节点是黑的,不需要平衡,直接OK。
除了上面的两种简单情况,我们还会遇到下面三种比较一般的情况:
【注意:以下三种情况均为父节点为祖父节点的左孩子时候的情况,当父节点为祖父节点的右孩子时请镜像反转(left<->right)】
1)当前节点的父节点为红色,且叔叔节点(祖父节点的另一个节点)为红色。
解决办法:将祖父节点染红(因为父节点为红色,所以祖父节点一定为黑色,否则插入前就已经不是红黑树),父节点和叔叔节点染黑。将祖父节点成为新的当前节点,继续算法。(未改变性质五)
以下两张图片引自博客【结构之法,算法之道】
调整前:
调整后:
2)父节点为红,叔叔节点为黑色,当前节点为父节点的右孩子。
解决办法:将父节点作为新的当前节点,并将其左旋,继续算法(直接到情况3)。(未改变性质五)
以下两张图片引自博客【结构之法,算法之道】
调整前:
调整后:
3)父节点为红色,叔叔节点为黑色,且当前节点为父节点的左孩子。
解决办法:将父节点染黑,祖父节点染红,并右旋祖父节点。(改变了性质五,树平衡,结束算法。)
以下两张图片引自博客【结构之法,算法之道】
调整前:
调整后:
最后附上代码:
/*插入后再平衡函数*/
static RB_Tree RB_Insert_Rebalance(RB_Tree node, RB_Tree index)
{
RB_Tree parent, grandpa, uncle, temp;//父节点,祖父节点,叔叔节点,临时节点
/*循环体:校正从插入点开始到root结束*/
while((parent = node->parent) && (parent->color == RED))
{
grandpa = parent->parent;
/*当父节点为祖父节点的左孩子*/
if(parent == grandpa->leftChild)
{
uncle = grandpa->rightChild;
/*情况一:父节点与叔节点均为红色*/
if(uncle && uncle->color == RED)
{
uncle->color = BLACK;//叔节点染黑
parent->color = BLACK;//父节点染黑
grandpa->color = RED;//祖父节点染红
node = grandpa;//指向祖父节点,继续算法
}
else//叔节点为黑(为黑或为空)
{
/*情况二:叔节点为黑,当前节点为父节点右孩子*/
if(node == parent->rightChild)
{
index = RB_Rotate_Left(parent, index);//以父节点为轴,左旋
temp = parent;
parent = node;
node = temp;//并将指针指向旋转前的父节点,继续算法
}
//注意:情况二必然导致情况三;但情况三未必经过情况二而来
/*情况三:叔节点为黑,当前节点为父节点的左孩子*/
parent->color = BLACK;//父节点染黑
grandpa->color = RED;//祖父节点染红
index = RB_Rotate_Right(grandpa, index);//以祖父节点为轴,左旋;继续算法
//情况三的调整结束之后,node的父节点变成黑色,调整结束。
}
}
/*父节点为祖父节点右孩子,情形与上类似但镜像对称*/
else//parent == grandpa->rightChild
{
uncle = grandpa->leftChild;
/*情况一:父节点与叔节点均为红色*/
if(uncle && uncle->color == RED)
{
uncle->color = BLACK;//叔节点染黑
parent->color = BLACK;//父节点染黑
grandpa->color = RED;//祖父节点染红
node = grandpa;//指向祖父节点,继续算法
}
else//叔节点为黑(为黑或为空)
{
/*情况二:叔节点为黑,当前节点为父节点左孩子*/
if(node == parent->leftChild)
{
index = RB_Rotate_Right(parent, index);//以父节点为轴,右旋
temp = parent;
parent = node;
node = temp;//当前节点指向旋转前的父节点,继续算法
}
//注意:情况二必然导致情况三;但情况三未必经过情况二而来
/*情况三:叔节点为黑,当前节点为父节点右孩子*/
parent->color = BLACK;//父节点染黑
grandpa->color = RED;//祖父节点染红
index = RB_Rotate_Left(grandpa, index);//以祖父节点为轴,左旋;继续算法
}
}
}
index->color = BLACK;//root节点必然为黑色,没有为什么。
return index;//返回root节点
}
参考资料:
红黑树从头至尾插入和删除结点的全程演示图—结构之法,算法之道
(下面这个我就纳闷怎么就属于违禁url了)
数据结构之红黑树C源码实现与剖析—梦醒潇湘loveblog.chinaunix.net/uid-26548237-id-3479927.html