标签:
Why would we go to such ludicrous lebgths to explain the RB_TREE?
《STL源码剖析》上给了我们一个很好的解释:(见202页)
所谓树形平衡与否,并没有一个绝对的测量标准。“平衡”的大致意义是:没有一个节点过深(深度即就是“根节点至任一节点的路径长度,即所谓该节点的深度”,在数值上与路径长度相等)。不同的平衡条件,造就出不同的效率表现,以及不同的实现复杂度。有数种特殊结构如:AVL-Tree,RB-tree,AA-tree,均可实现出平衡二叉搜索树,他们都比一般的(无法绝对维持平衡的)二叉搜索树更加复杂,因此,插入节点和删除节点的平均时间也比较长,但是他们可以避免极难应付的(高度不平衡)最坏情况,而且由于他们总是保持某种程度的平衡,所以元素的访问(搜寻)时间平均而言也就比较少,一般而言其搜寻时间可以节省25%左右
一个平衡二叉搜索树之RB_Tree.它不仅是一个二叉搜索树,还应该满足以下规则:
概括起来即是“一头一脚黑,黑同红不连”:
我们约定四个规则:
(1)每个节点不是红色就是黑色
(2)根节点必须为黑色(即一头一脚黑,其中一脚指的是空节点)
(3)如果节点为红,其子节点必须为黑(即红不连)
(4)任一节点到NULL(树尾端)的任何路径,所含之黑节点数必须相同。 (即黑同)
Here are the RB_TREE code && explanation:(You also can actualize it.Believe yourself!)
/*头文件*/
#include<iostream>
#include<assert.h> //for assert
#include<string.h> //for memset
/*memset用法:
* void *memset(void *s, int c, size_t n);
* The memset() function fills the first n bytes of the memory area
* pointed to by s with the constant byte c.
*/
using namespace std;
/*树节点颜色用枚举类型来定义
*typedef的使用方法见我的另一篇文章[typedef用法汇总](http://blog.csdn.net/derkampf/article/details/51339953)
*这里用来定义节点中所存实值的类型。
*/
typedef int Type;
typdef enum{RED=0, BLACK}Color;
typedef struct Node
{
Color color;
Type key;
struct Node *left, *right, *parent;
}*PNode; //节点的类型为指针类型
typedef struct
{
Node *root;
Node *Nil;
}RB_TREE; //RB_TREE节点内容,Nil为一个工具,为后续树之节点的的赋空、判空起作用
Node* Buynode()
{
Node *p = new Node;
assert(p != NULL);
memset(p,0,sizeof(Node)); //为新增节点节点初始化,{color = RED, key = 0, left = 0x0, right = 0x0, parent = 0x0}
return p;
}
void InitTree(RB_TREE &t) //创建节点并对其初始化
{
t.Nil = Buynode();
t.root = t.Nil;
t.Nil->color = BLACK;
t.Nil->key = -1;
}
/*左旋、右旋,以及插入之后将红黑树平衡的代码置于后面进行分析*/
bool Insert(RB_TREE &t, Type x) //插入节点
{
Node *p = t.Nil;
Node *s = t.root;
/*从根结点开始遍历,找到属于新增节点q的位置,将其插入*/
while(s != t.Nil)
{
p = s;
if(x == s->key)
{
return false;
}
else if(x < s->key)
{
s = s->left; //说明x的位置应该在此时s的左树位置查找
}
else
{
s = s->right; //那么此时x应插入的位置应该继续在s的右树上寻找
}
}
/*到达此步,程序已经找到新增节点的位置,接下来要做的是:新建一个空节点q,将x的值给与该结点(这就构成我们要插入的新节点)*/
Node *q = Buynode();
q->key = x;
q->parent = p; //仔细想想,这里不能写q->parent = s;因为程序执行到这里的时候s = t.Nil,即s为NULL
/*后续操作*/
if(p == t.Nil) //这里我感觉可以没有这个条件,因为此时p是存在的
{
t.root = q;
}
else if(x < p->key)
{
p->left = q;
}
else
{
p->right = q;
}
q->left = q->right = t.Nil;
q->color = RED; //新插入节点颜色须为红色
Insert_Fixup(t,q); //需要进一步修正一下,是的满足RB_TREE的规则
return true;
}
int main()
{
int ar[] = {5,7,10};
RB_TREE rb;
InitTree(rb);
for(int i = 0; i<sizeof(ar) / sizeof(int); ++i)
{
Insert(rb, ar[i]);
}
return 0;
}
插入的节点会破坏RB_Tree的规则,致使我们必须旋转树形并调整节点的颜色。书中给了我们四种考虑办法。
“根据x的插入位置即外围节点(s伯父节点和GG曾祖父节点)的颜色,有了四种考虑”
——《STL源码剖析》
因为是简单实现,我们只来剖析一下单旋转的情况。后面我将陆续为大家更新SGI–STL版红黑树的实现过程,敬请期待!
下面即为左旋、右旋,以及插入之后将红黑树平衡之的代码:
情况如下:
(详情见《STL源码剖析》210页,这里不再赘述)
//有了上面的树,得到下面的代码是显而易见的
void Rotateleft(RB_TREE &t, Node *p)
{
Node *s = p->right;
P->right = s->left;
if(s->left != t.Nil)
{
s->left->parent = p;
}
s->parent = p->parent;
if(p->parent = t.Nil)
{
t.root = s;
}
else if(p = p->parent->left)
{
p->parent->left = s;
}
else
{
p->parent->right = s;
}
s->left = p;
p->parent = s;
}
//上面是右旋的示意图,只要考虑到所会出现的各种情况,那么就可以写出通用的右旋代码。
void Rotateright(RB_TREE &t, Node *p)
{
Node *s = p->left;
p->left = s->right;
if(s->right != t.Nil)
{
s->right->parent = p;
}
s->parent = p->parent;
if(p->parent == t>Nil)
{
t.root = s;
}
else if(p == p->parent->left)
{
p->parent->left = s;
}
else
{
p->parent->right = s;
}
s->right = p;
p->parent = s;
}
我们要知道的是,一段代码用来解决一个问题,如果考虑到各种可能会出现的情况,那么这段代码便可以完美的解决这个问题中出现的所有情况。就像上述的左旋,右旋代码,我们只用它来解决左旋,或者右旋的问题,在编写代码的时候,我们已经考虑到了在这一过程中会出现的情况,那么它不仅仅只局限于简单的树,而是所有需要进行左旋或者右旋的树。
void Insert_Fixup(RB_TREE &t, Node *z)
{
}
C++ Learning——Build a simple&little RB_Tree
标签:
原文地址:http://blog.csdn.net/derkampf/article/details/51340128