C++ Learning——Build a simple&little RB_Tree
2016-05-08 21:39
381 查看
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!)
插入的节点会破坏RB_Tree的规则,致使我们必须旋转树形并调整节点的颜色。书中给了我们四种考虑办法。
“根据x的插入位置即外围节点(s伯父节点和GG曾祖父节点)的颜色,有了四种考虑”
——《STL源码剖析》
因为是简单实现,我们只来剖析一下单旋转的情况。后面我将陆续为大家更新SGI–STL版红黑树的实现过程,敬请期待!
下面即为左旋、右旋,以及插入之后将红黑树平衡之的代码:
情况如下:
(详情见《STL源码剖析》210页,这里不再赘述)
我们要知道的是,一段代码用来解决一个问题,如果考虑到各种可能会出现的情况,那么这段代码便可以完美的解决这个问题中出现的所有情况。就像上述的左旋,右旋代码,我们只用它来解决左旋,或者右旋的问题,在编写代码的时候,我们已经考虑到了在这一过程中会出现的情况,那么它不仅仅只局限于简单的树,而是所有需要进行左旋或者右旋的树。
《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) { }