红黑树
来源:互联网 发布:淘宝客ev是什么 编辑:程序博客网 时间:2024/06/06 03:51
红黑树是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。它是在1972年由鲁道夫·贝尔发明的,他称之为"对称二叉B树",它现代的名字是在 Leo J. Guibas 和 Robert Sedgewick 于1978年写的一篇论文中获得的。它是复杂的,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n是树中元素的数目。
用途和好处
红黑树和AVL树一样都对插入时间、删除时间和查找时间提供了最好可能的最坏情况担保。这不只是使它们在时间敏感的应用如实时应用(real time application)中有价值,而且使它们有在提供最坏情况担保的其他数据结构中作为建造板块的价值;例如,在计算几何中使用的很多数据结构都可以基于红黑树。
红黑树在函数式编程中也特别有用,在这里它们是最常用的持久数据结构之一,它们用来构造关联数组和集合,在突变之后它们能保持为以前的版本。除了O(log n)的时间之外,红黑树的持久版本对每次插入或删除需要O(log n)的空间。
红黑树是 2-3-4树的一种等同。换句话说,对于每个 2-3-4 树,都存在至少一个数据元素是同样次序的红黑树。在 2-3-4 树上的插入和删除操作也等同于在红黑树中颜色翻转和旋转。这使得 2-3-4 树成为理解红黑树背后的逻辑的重要工具,这也是很多介绍算法的教科书在红黑树之前介绍 2-3-4 树的原因,尽管 2-3-4 树在实践中不经常使用。
性质
红黑树(RBT)的定义:它或者是一颗空树,或者是具有一下性质的二叉查找树:
1.节点非红即黑。
2.根节点是黑色。
3.所有NULL结点称为叶子节点,且认为颜色为黑。
4.所有红节点的子节点都为黑色。
5.从任一节点到其叶子节点的所有路径上都包含相同数目的黑节点。
看完红黑树的定义是不是可晕?怎么这么多要求!!这怎么约束啊?我刚看到这5条约束,直接无语了,1-3、4还好说,第5点是怎么回事啊?怎么约束?整这么复杂的条件好干啥啊?我来简单说说呵:
第3条,显然这里的叶子节点不是平常我们所说的叶子节点,如图标有NIL的为叶子节点,为什么不按常规出牌,因为按一般的叶子节点也行,但会使算法更复杂;
第4条,即该树上决不允许存在两个连续的红节点;
第5条,比如图中红8到1左边的叶子节点的路径包含2个黑节点,到6下的叶子节点的路径也包含2个黑节点。
所有性质1-5合起来约束了该树的平衡性能--即该树上的最长路径不可能会大于2倍最短路径。为什么?因为第1条该树上的节点非红即黑,由于第4条该树上不允许存在两个连续的红节点,那么对于从一个节点到其叶子节点的一条最长的路径一定是红黑交错的,那么最短路径一定是纯黑色的节点;而又第5条从任一节点到其叶子节点的所有路径上都包含相同数目的黑节点,这么来说最长路径上的黑节点的数目和最短路径上的黑节点的数目相等!而又第2条根结点为黑、第3条叶子节点是黑,那么可知:最长路径<=2*最短路径。
一颗二叉树的平衡性能越好,那么它的效率越高!显然红黑树的平衡性能比AVL的略差些,但是经过大量试验证明,实际上红黑树的效率还是很不错了,仍能达到O(logN),这个我不知道,我现在不可能做过大量试验,只是听人家这样说,O(∩_∩)O哈哈~但你至少知道他的时间复杂度一定小于2O(logN)!
上边的性质看个10遍,看懂看透彻再看操作!
操作
因为每一个红黑树也是一个特化的二叉查找树,因此红黑树上的只读操作与普通二叉查找树上的只读操作相同。然而,在红黑树上进行插入操作和删除操作会导致不再符合红黑树的性质。恢复红黑树的性质需要少量(O(log n))的颜色变更(实际是非常快速的)和不超过三次树旋转(对于插入操作是两次)。虽然插入和删除很复杂,但操作时间仍可以保持为 O(log n) 次。
插入操作考虑父亲,删除操作考虑儿子。
插入:一根二黑三两红,四双转单五单旋。
删除:一自红二自黑儿红,换位;三N根四S红;五全黑六P红;七P任S左红N左;八P任S右红N左;四七八左右可互换。
插入操作
我们首先以二叉查找树的方法增加节点并标记它为红色。(如果设为黑色,就会导致根到叶子的路径上有一条路上,多一个额外的黑节点,这个是很难调整的。但是设为红色节点后,可能会导致出现两个连续红色节点的冲突,那么可以通过颜色调换(color flips)和树旋转来调整。) 下面要进行什么操作取决于其他临近节点的颜色。同人类的家族树中一样,我们将使用术语叔父节点来指一个节点的父节点的兄弟节点。注意:
- 性质1和性质3总是保持着。
- 性质4只在增加红色节点、重绘黑色节点为红色,或做旋转时受到威胁。
- 性质5只在增加黑色节点、重绘红色节点为黑色,或做旋转时受到威胁。
在下面的示意图中,将要插入的节点标为N,N的父节点标为P,N的祖父节点标为G,N的叔父节点标为U。在图中展示的任何颜色要么是由它所处情形这些所作的假定,要么是假定所暗含 (imply) 的。
对于每一种情形,我们将使用 C 示例代码来展示。通过下列函数,可以找到一个节点的叔父和祖父节点:
node* grandparent(node *n) { return n->parent->parent; } node* uncle(node *n) { if (n->parent == grandparent(n)->left) return grandparent(n)->right; else return grandparent(n)->left; }
情形1:新节点N位于树的根上,没有父节点。在这种情形下,我们把它重绘为黑色以满足性质2。因为它在每个路径上对黑节点数目增加一,性质5符合。完成。
void insert_case1(node *n) { if (n->parent == NULL) n->color = BLACK; else insert_case2(n); }
情形2:新节点的父节点P是黑色,直接插入,完成。
解析:操作后,性质4没有失效(新节点是红色的)。在这种情形下,树仍是有效的。性质5也未受到威胁,尽管新节点N有两个黑色叶子子节点;但由于新节点N是红色,通过它的每个子节点的路径就都有同通过它所取代的黑色的叶子的路径同样数目的黑色节点,所以依然满足这个性质。
void insert_case2(node *n) { if (n->parent->color == BLACK) return; /* 树仍旧有效 */ else insert_case3(n); }
注意:情形1很简单,情形2中P为黑色,一切安然无事,但P为红就不一样了,下边是P为红的各种情况,也是真正要学的地方!在下列情形下我们假定新节点的父节点为红色,所以它有祖父节点;因为如果父节点是根节点,那父节点就应当是黑色。所以新节点总有一个叔父节点,尽管在情形4和5下它可能是叶子节点。
情形3:如果父节点P和叔父节点U二者都是红色,此时新插入节点N做为P的左子节点或右子节点都属于情形3,这里图仅显示N做为P左子的情形。
操作:将父节点P和叔父节点U重绘为黑色,并重绘祖父节点G为红色(用来保持性质4),然后在祖父节点G上递归地进行情形1。
解析:操作后,新节点N有了一个黑色的父节点P。因为通过父节点P或叔父节点U的任何路径都必定通过祖父节点G,在这些路径上的黑节点数目没有改变。但是,红色的祖父节点G的父节点也有可能是红色的,这就违反了性质4。为了解决这个问题,我们在祖父节点G上递归地进行情形1的整个过程。(把G当成是新加入的节点进行各种情形的检查)
void insert_case3(node *n) { if (uncle(n) != NULL && uncle(n)->color == RED) { n->parent->color = BLACK; uncle(n)->color = BLACK; grandparent(n)->color = RED; insert_case1(grandparent(n)); } else insert_case4(n); }
注意:在余下的情形下,我们假定父节点P是其父亲G的左子节点。如果它是右子节点,情形4和情形5中的左和右应当对调。
情形4:父节点P是红色,叔父节点U是黑色或缺少,并且新节点N是其父节点P的右子节点,父节点P是祖父G的左子节点。
操作:进行一次左旋转调换新节点和其父节点的角色; 接着,我们按情形5处理以前的父节点P,以解决仍然失效的性质4。
注意这个改变会导致某些路径通过它们以前不通过的新节点N(比如图中1号叶子节点)或不通过节点P(比如图中3号叶子节点),但由于这两个节点都是红色的,所以性质5仍有效。
void insert_case4(node *n) { if (n == n->parent->right && n->parent == grandparent(n)->left) { rotate_left(n->parent); n = n->left; } else if (n == n->parent->left && n->parent == grandparent(n)->right) { rotate_right(n->parent); n = n->right; } insert_case5(n); }
情形5:父节点P是红色,叔父节点U是黑色或缺少,新节点N是其父节点P的左子节点,父节点P是祖父节点G的左子节点。
操作:进行针对祖父节点G的一次右旋转,切换以前的父节点P和祖父节点G的颜色,完成。
解析:在旋转产生的树中,以前的父节点P现在是新节点N和以前的祖父节点G的父节点。我们知道以前的祖父节点G是黑色,否则父节点P就不可能是红色(如果P和G都是红色就违反了性质4,所以G必须是黑色)。我们切换以前的父节点P和祖父节点G的颜色,结果树满足性质4。性质5也仍然保持满足,因为通过这三个节点中任何一个的所有路径以前都通过祖父节点G,现在它们都通过以前的父节点P。在各自的情形下,这都是三个节点中唯一的黑色节点。
void insert_case5(node *n) { n->parent->color = BLACK; grandparent(n)->color = RED; if (n == n->parent->left && n->parent == grandparent(n)->left) { rotate_right(grandparent(n)); } else { /* Here, n == n->parent->right && n->parent == grandparent(n)->right */ rotate_left(grandparent(n)); } }
删除操作
如果需要删除的节点有两个儿子,那么问题可以被转化成“删除另一个最多只有一个儿子的节点的问题”(为了表述方便,这里所指的儿子,为非叶子节点的儿子)。对于二叉查找树,在删除带有两个非叶子儿子的节点的时候,我们找到要么在它的左子树中的最大元素、要么在它的右子树中的最小元素,并把它的值转移到要删除的节点中(如在这里所展示的那样)。我们接着删除我们从中复制出值的那个节点(即左子树中的最大元素或者右子树中的最小元素),它必定有少于两个非叶子的儿子。因为只是复制了一个值,不违反任何性质,这就把问题简化为如何删除最多有一个儿子的节点的问题。它不关心这个节点是最初要删除的节点还是我们从中复制出值的那个节点。
在本文余下的部分中,我们只需要讨论删除只有一个儿子的节点(如果它两个儿子都为空,即均为叶子,我们任意将其中一个看作它的儿子)。
如果我们删除一个红色节点(此时该节点的儿子将都为叶子节点),它的父亲和儿子一定是黑色的。所以我们可以简单的用它的黑色儿子替换它,并不会破坏性质3和性质4。通过被删除节点的所有路径只是少了一个红色节点,这样可以继续保证性质5。完成。(情形-1)
另一种简单情况是被删除节点是黑色而它的儿子是红色。如果只是去除这个黑色节点,用它的红色儿子顶替上来的话,会破坏性质5,但是如果我们重绘它的儿子为黑色,则曾经通过它的所有路径将通过它的黑色儿子,这样可以继续保持性质5。完成。(情形0)
需要进一步讨论的是在要删除的节点和它的儿子二者都是黑色的时候,这是一种复杂的情况。我们首先把要删除的节点替换为它的儿子。出于方便,称呼这个儿子为N(这里的儿子指原来的儿子,位置互换了,所以原来的儿子到了原来要删除节点位置上,为N。以下就只考虑这个原来的儿子了,不考虑原来要删除的节点了。),称呼它的兄弟(它父亲的另一个儿子)为S(这种情况下必定是存在S的,不然N的父节点违反性质5)。在下面的示意图中,我们还是使用P称呼N的父亲,SL称呼S的左儿子,SR称呼S的右儿子。我们将使用下述函数找到兄弟节点:
struct node *sibling(struct node *n){ if (n == n->parent->left) return n->parent->right; else return n->parent->left;}
我们可以使用下列代码进行上述的概要步骤(包含上述两个简单情况),这里的函数
replace_node
替换 child
到 n
在树中的位置(从下面的delete_one_child代码中的free(n)来看,n还是指向原来要删除的节点,只是replace_node后,child变为了n的父亲。N指的是原来的儿子,也指delete_case1~6中的n)。出于方便,在本章节中的代码将假定空叶子被用不是 NULL 的实际节点对象来表示(在插入章节中的代码可以同任何一种表示一起工作)。voiddelete_one_child(struct node *n){ /* * Precondition: n has at most one non-null child. */ struct node *child = is_leaf(n->right) ? n->left : n->right; replace_node(n, child); if (n->color == BLACK) { if (child->color == RED) child->color = BLACK; else delete_case1(child); } free(n);}如果N和它原来的父亲(不是指P,是指要删除的节点)是黑色,则删除它导致通过原来父亲的路径都比不通过它的路径少了一个黑色节点。因为这违反了性质5,树需要被重新平衡。有几种情形需要考虑:
情形1:N是新的根。
操作:直接删除要删除的节点,完成。
解析:要删除的节点是原来的根节点,我们从所有路径去除了一个黑色节点,而新根是黑色的,所以性质都保持着。
voiddelete_case1(struct node *n){ if (n->parent != NULL) delete_case2(n);}这里代码中n是delete_one_child中的child。
注意: 在情形2、5和6下,我们假定N是它父亲的左儿子。如果它是右儿子,则在这些情形下的左和右应当对调。
情形2: S是红色(那么父节点P一定是黑,子节点一定是黑),N是P的左子节点(或者N是P的右孩子)。
操作:在N的父亲上做左旋转,把红色兄弟S转换成N的祖父,接着对调N的父亲P和祖父S的颜色。接下来按情形4、情形5或情形6来处理。
解析:完成这两个操作后,所有路径上黑色节点的数目没有改变,但现在N有了一个黑色的兄弟和一个红色的父亲(它的新兄弟是黑色,因为它是红色S的一个儿子),所以我们可以按情形4、情形5或情形6来处理。
voiddelete_case2(struct node *n){ struct node *s = sibling(n); if (s->color == RED) { n->parent->color = RED; s->color = BLACK; if (n == n->parent->left) rotate_left(n->parent); else rotate_right(n->parent); } delete_case3(n);}
这里代码跳到情形3,只是为了把所有代码串起来。实际上,从符合情形2的情况会直接进入情形4, 5 或者6。不符合情形2的才可能进入情形3。
情形3:N的父亲P、S和S的儿子都是黑色的,N为P的左子节点。
操作:简单的重绘S为红色。从情形1开始,在P上做重新平衡处理。
解析:结果是通过S的所有路径(它们就是以前不通过N的那些路径),都少了一个黑色节点。因为删除N的初始的父亲使通过N的所有路径少了一个黑色节点,这使事情都平衡了起来。但是,现在通过P的所有路径比不通过P的路径少了一个黑色节点,所以仍然违反性质5。要修正这个问题,我们要从情形1开始,在P上做重新平衡处理。
voiddelete_case3(struct node *n){ struct node *s = sibling(n); if ((n->parent->color == BLACK) && (s->color == BLACK) && (s->left->color == BLACK) && (s->right->color == BLACK)) { s->color = RED; delete_case1(n->parent); } else delete_case4(n);}
情形4:N的父亲是红色,S和S的儿子都是黑色。
操作:简单的交换N的兄弟S和父亲P的颜色。完成。
解析:操作后,不影响不通过N的路径的黑色节点的数目,但是它在通过N的路径上对黑色节点数目增加了一,添补了在这些路径上删除的黑色节点。
voiddelete_case4(struct node *n){ struct node *s = sibling(n); if ((n->parent->color == RED) && (s->color == BLACK) && (s->left->color == BLACK) && (s->right->color == BLACK)) { s->color = RED; n->parent->color = BLACK; } else delete_case5(n);}
情形5:P任意色(不管它),N是P的左孩子,S为黑,S的左孩子SL为红,S的右孩子SR为黑(或者N是P的右孩子,S的右孩子为红,S的左孩子为黑)。
操作:在S上做右旋转,这样S的左儿子成为S的父亲和N的新兄弟。接着交换S和它的新父亲的颜色。进入情形6。
解析:操作后,所有路径仍有同样数目的黑色节点,但是现在N有了一个黑色兄弟,它的右儿子是红色的。所以我们进入了情形6。N和它的父亲都不受这个变换的影响。
voiddelete_case5(struct node *n){ struct node *s = sibling(n); if (s->color == BLACK) { /* this if statement is trivial, due to Case 2 (even though Case two changed the sibling to a sibling's child, the sibling's child can't be red, since no red parent can have a red child). */// the following statements just force the red to be on the left of the left of the parent, // or right of the right, so case six will rotate correctly. if ((n == n->parent->left) && (s->right->color == BLACK) && (s->left->color == RED)) { // this last test is trivial too due to cases 2-4. s->color = RED; s->left->color = BLACK; rotate_right(s); } else if ((n == n->parent->right) && (s->left->color == BLACK) && (s->right->color == RED)) {// this last test is trivial too due to cases 2-4. s->color = RED; s->right->color = BLACK; rotate_left(s); } } delete_case6(n);}
情形6:P任意色,N是P的左孩子,S为黑,S的右孩子SR为红,S的左孩子任意(或者是N是P的右孩子,S的左孩子为红,S的右孩子任意)。
操作:在N的父亲上做左旋转,这样S成为N的父亲P和S的右儿子的父亲。接着交换N的父亲P和S的颜色,并使S的右儿子为黑色。完成。
解析:子树在它的根上的仍是同样的颜色,所以性质3没有被违反。但是,N现在增加了一个黑色祖先: 要么N的父亲P变成黑色,要么P是黑色而S被增加为一个黑色祖父。所以,通过N的路径都增加了一个黑色节点。
此时,如果一个路径不通过N,则有两种可能性:
- 它通过N的新兄弟。那么它以前和现在都必定通过S和N的父亲P,而它们只是交换了颜色。所以路径保持了同样数目的黑色节点。
- 它通过N的新叔父,S的右儿子。那么它以前通过S的父亲P、S和S的右儿子,但是现在只通过S(它被假定为它以前的父亲的颜色)和S的右儿子,它被从红色改变为黑色。合成效果是这个路径通过了同样数目的黑色节点。
在任何情况下,在这些路径上的黑色节点数目都没有改变。所以我们恢复了性质4。在示意图中的白色节点可以是红色或黑色,但是在变换前后都必须指定相同的颜色。
voiddelete_case6(struct node *n){ struct node *s = sibling(n); s->color = n->parent->color; n->parent->color = BLACK; if (n == n->parent->left) { s->right->color = BLACK; rotate_left(n->parent); } else { s->left->color = BLACK; rotate_right(n->parent); }}
同样的,函数调用都使用了尾部递归,所以算法是原地算法。此外,在旋转之后不再做递归调用,所以进行了恒定数目(最多3次)的旋转。
C++ 示例代码
#define BLACK 1#define RED 0 using namespace std; class bst {private: struct Node { int value; bool color; Node *leftTree, *rightTree, *parent; Node() { color = RED; leftTree = NULL; rightTree = NULL; parent = NULL; value = 0; } Node* grandparent() { if (parent == NULL) { return NULL; } return parent->parent; } Node* uncle() { if (grandparent() == NULL) { return NULL; } if (parent == grandparent()->rightTree) return grandparent()->leftTree; else return grandparent()->rightTree; } Node* sibling() { if (parent->leftTree == this) return parent->rightTree; else return parent->leftTree; } }; void rotate_right(Node *p) { Node *gp = p->grandparent(); Node *fa = p->parent; Node *y = p->rightTree; fa->leftTree = y; if (y != NIL) y->parent = fa; p->rightTree = fa; fa->parent = p; if (root == fa) root = p; p->parent = gp; if (gp != NULL) { if (gp->leftTree == fa) gp->leftTree = p; else gp->rightTree = p; } } void rotate_left(Node *p) { if (p->parent == NULL) { root = p; return; } Node *gp = p->grandparent(); Node *fa = p->parent; Node *y = p->leftTree; fa->rightTree = y; if (y != NIL) y->parent = fa; p->leftTree = fa; fa->parent = p; if (root == fa) root = p; p->parent = gp; if (gp != NULL) { if (gp->leftTree == fa) gp->leftTree = p; else gp->rightTree = p; } } void inorder(Node *p) { if (p == NIL) return; if (p->leftTree) inorder(p->leftTree); cout << p->value << " "; if (p->rightTree) inorder(p->rightTree); } string outputColor(bool color) { return color ? "BLACK" : "RED"; } Node* getSmallestChild(Node *p) { if (p->leftTree == NIL) return p; return getSmallestChild(p->leftTree); } bool delete_child(Node *p, int data) { if (p->value > data) { if (p->leftTree == NIL) { return false; } return delete_child(p->leftTree, data); } else if (p->value < data) { if (p->rightTree == NIL) { return false; } return delete_child(p->rightTree, data); } else if (p->value == data) { if (p->rightTree == NIL) { delete_one_child(p); return true; } Node *smallest = getSmallestChild(p->rightTree); swap(p->value, smallest->value); delete_one_child(smallest); return true; } } void delete_one_child(Node *p) { Node *child = p->leftTree == NIL ? p->rightTree : p->leftTree; if (p->parent == NULL && p->leftTree == NIL && p->rightTree == NIL) { p = NULL; root = p; return; } if (p->parent == NULL) { delete p; child->parent = NULL; root = child; root->color = BLACK; return; } if (p->parent->leftTree == p) { p->parent->leftTree = child; } else { p->parent->rightTree = child; } child->parent = p->parent; if (p->color == BLACK) { if (child->color == RED) { child->color = BLACK; } else delete_case(child); } delete p; } void delete_case(Node *p) { if (p->parent == NULL) { p->color = BLACK; return; } if (p->sibling()->color == RED) { p->parent->color = RED; p->sibling()->color = BLACK; if (p == p->parent->leftTree) rotate_left(p->sibling()); else rotate_right(p->sibling()); } if (p->parent->color == BLACK && p->sibling()->color == BLACK && p->sibling()->leftTree->color == BLACK && p->sibling()->rightTree->color == BLACK) { p->sibling()->color = RED; delete_case(p->parent); } else if (p->parent->color == RED && p->sibling()->color == BLACK && p->sibling()->leftTree->color == BLACK && p->sibling()->rightTree->color == BLACK) { p->sibling()->color = RED; p->parent->color = BLACK; } else { if (p->sibling()->color == BLACK) { if (p == p->parent->leftTree && p->sibling()->leftTree->color == RED && p->sibling()->rightTree->color == BLACK) { p->sibling()->color = RED; p->sibling()->leftTree->color = BLACK; rotate_right(p->sibling()->leftTree); } else if (p == p->parent->rightTree && p->sibling()->leftTree->color == BLACK && p->sibling()->rightTree->color == RED) { p->sibling()->color = RED; p->sibling()->rightTree->color = BLACK; rotate_left(p->sibling()->rightTree); } } p->sibling()->color = p->parent->color; p->parent->color = BLACK; if (p == p->parent->leftTree) { p->sibling()->rightTree->color = BLACK; rotate_left(p->sibling()); } else { p->sibling()->leftTree->color = BLACK; rotate_right(p->sibling()); } } } void insert(Node *p, int data) { if (p->value >= data) { if (p->leftTree != NIL) insert(p->leftTree, data); else { Node *tmp = new Node(); tmp->value = data; tmp->leftTree = tmp->rightTree = NIL; tmp->parent = p; p->leftTree = tmp; insert_case(tmp); } } else { if (p->rightTree != NIL) insert(p->rightTree, data); else { Node *tmp = new Node(); tmp->value = data; tmp->leftTree = tmp->rightTree = NIL; tmp->parent = p; p->rightTree = tmp; insert_case(tmp); } } } void insert_case(Node *p) { if (p->parent == NULL) { root = p; p->color = BLACK; return; } if (p->parent->color == RED) { if (p->uncle()->color == RED) { p->parent->color = p->uncle()->color = BLACK; p->grandparent()->color = RED; insert_case(p->grandparent()); } else { if (p->parent->rightTree == p && p->grandparent()->leftTree == p->parent) { rotate_left(p); rotate_right(p); p->color = BLACK; p->leftTree->color = p->rightTree->color = RED; } else if (p->parent->leftTree == p && p->grandparent()->rightTree == p->parent) { rotate_right(p); rotate_left(p); p->color = BLACK; p->leftTree->color = p->rightTree->color = RED; } else if (p->parent->leftTree == p && p->grandparent()->leftTree == p->parent) { p->parent->color = BLACK; p->grandparent()->color = RED; rotate_right(p->parent); } else if (p->parent->rightTree == p && p->grandparent()->rightTree == p->parent) { p->parent->color = BLACK; p->grandparent()->color = RED; rotate_left(p->parent); } } } } void DeleteTree(Node *p) { if (!p || p == NIL) { return; } DeleteTree(p->leftTree); DeleteTree(p->rightTree); delete p; }public: bst() { NIL = new Node(); NIL->color = BLACK; root = NULL; } ~bst() { if (root) DeleteTree(root); delete NIL; } void inorder() { if (root == NULL) return; inorder(root); cout << endl; } void insert(int x) { if (root == NULL) { root = new Node(); root->color = BLACK; root->leftTree = root->rightTree = NIL; root->value = x; } else { insert(root, x); } } bool delete_value(int data) { return delete_child(root, data); }private: Node *root, *NIL;};
C语言示例代码
强调一下:注意哪些分方向的情况,每个分方向的情形就两种情况,不要搞迷了!
下边是转过来的代码,不用关心是什么方向,主要是用一个指针数组即child[2],0代表左,1代表右,进行两个节点的变换(旋转)的时候只需向conversion(&T,direction);传入父节点指针的地址及子节点在父节点的方位(0或1);有兴趣可以看代码.
欢迎大家留言指正哦^_^
下边贴上C代码:
简介:主要是用递归实现插入、删除,回溯时检索并恢复平衡。
#include <stdio.h>#include <stdlib.h>#define RED 0#define BACK 1typedef int Elemtype;//定义一个红黑树的结点typedef struct Red_Back_Tree{ Elemtype e; int color; struct Red_Back_Tree * child[2];}* RBT;// 两个节点变换函数void conversion(RBT *T,int direction);// 删除一个节点的所用函数int DeleteRBT(RBT *T,Elemtype e); // 删除主(接口)函数int find_replace_point(RBT gogal,RBT *l); // 寻找替代点int keep_balance_for_delete(RBT *T,int direction); // 删除的平衡操作int do_with_start_point(RBT gogal,RBT *T,int direction); // 处理第一个起始点// 插入一个节点的所用函数int InsertRBT(RBT *T,Elemtype e); // 插入接口函数int _InsertRBT(RBT *T,Elemtype e); // 插入主函数int keep_balance_for_insert(RBT *T,int firdirection,Elemtype e);// 插入的平衡操作RBT create_one_node(Elemtype e); // 新建一个节点void conversion(RBT *T,int direction){ RBT f=(*T),s=f->child[direction],ss=s->child[!direction]; f->child[direction]=ss; s->child[!direction]=f; *T=s;}//★★★★★★★★★★★★★★★★★删除操作★★★★★★★★★★★★★★★★★★★★★★★★★★★int do_with_start_point(RBT gogal,RBT *T,int direction){ gogal->e=(*T)->e; if(BACK==((*T)->color)) { if(NULL!=(*T)->child[direction]) { (*T)->e=(*T)->child[direction]->e; free((*T)->child[direction]); (*T)->child[direction]=NULL; return 1; } else { free((*T)); *T=NULL; return 0; } } else { free((*T)); (*T)=NULL; return 1; }}int keep_balance_for_delete(RBT *T,int direction){ RBT p=(*T),b=p->child[!direction]; if(RED==b->color) { p->color=RED; b->color=BACK;// conversion(&p,!direction);//很恐怖的一个写法,偶然中发现:这里传的地址是假的!不是T!!// 考我怎么这么傻逼!!如果不是及时发现,到调试时将是无限恐怖// 将是一个巨大的隐藏的BUG!!!将会带来巨大的麻烦!!! conversion(T,!direction); return keep_balance_for_delete(&((*T)->child[direction]),direction); } else if(BACK==p->color && BACK==b->color && (NULL==b->child[0] || BACK==b->child[0]->color) && (NULL==b->child[1] || BACK==b->child[1]->color)) //这里感觉不美,就一次为NULL却每次要 { //判断是否为NULL,不美…… b->color=RED; return 0; } else if(RED==p->color && (NULL==b->child[0] || BACK==b->child[0]->color) && (NULL==b->child[1] || BACK==b->child[1]->color)) { p->color=BACK; b->color=RED; return 1; }// 第一次调试// 调试原因:由于删除0点未按预料的操作应该是情况④,却按⑤操作// 错误的地方:RED==b->child[!direction] ! 丢了->color 这个错误我上边错了几次,不过编译器报错改了过来// 这次的编译器不报错,看代码也看不错来,最后追究到这里,一一对照才发现!!!// else if(BACK==b->color && (NULL!=b->child[!direction] && RED==b->child[!direction])) else if(BACK==b->color && (NULL!=b->child[!direction] && RED==b->child[!direction]->color)) { b->color=p->color; p->color=BACK; b->child[!direction]->color=BACK; conversion(T,!direction); return 1; } else { b->child[direction]->color=p->color; p->color=BACK; conversion(&(p->child[!direction]),direction);//这里的p写的才算不错!即p也(*T)都行,一样! conversion(T,!direction); return 1; }}int find_replace_point(RBT gogal,RBT *l){ if(NULL!=(*l)->child[0]) { if(find_replace_point(gogal,&(*l)->child[0])) return 1; return keep_balance_for_delete(l,0); //... }// 第二次调试---其实没F5,F10,F11,根据结果猜测,到这里看看还真是的!// 调试原因:删除0好了,删除1又错了---2不见了,1还在// 错误的地方:就在这里,找到替代点,却没有“替代”,这等于把替代点删了...// 这里很明显,gogal这个删除点指针根本就没用...我当时忘了吧!!修改如下!// else //替代点为起始点// {// return do_with_start_point(l,1);// } else { return do_with_start_point(gogal,l,1); }}int DeleteRBT(RBT *T,Elemtype e){ if(!(*T)) return -1; else if(e>(*T)->e) { if(DeleteRBT(&((*T)->child[1]),e)) return 1; return keep_balance_for_delete(T,1); //... } else if(e<(*T)->e) { if(DeleteRBT(&((*T)->child[0]),e)) return 1; return keep_balance_for_delete(T,0); //... } else { if(NULL!=(*T)->child[1]) //真正的删除点不是起始点,需找替代点 { if(find_replace_point((*T),&((*T)->child[1]))) return 1; return keep_balance_for_delete(T,1); //... } else //真正的删除点就是起始点 { return do_with_start_point((*T),T,0); } }}//★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★//★★★★★★★★★★★★★★★★★★★插入操作★★★★★★★★★★★★★★★★★★★★★★★★★RBT create_one_node(Elemtype e){ RBT p=(RBT)malloc(sizeof(struct Red_Back_Tree)); p->e=e; p->color=RED; p->child[0]=p->child[1]=NULL; return p;}int keep_balance_for_insert(RBT *T,int firdirection,Elemtype e){ RBT p=(*T)->child[firdirection],u=(*T)->child[!firdirection]; int secdirection=( (e>p->e) ? 1 : 0 ); // 查处第二个方向 if(NULL!=u && RED==u->color) /*****③叔节点为红色*****/ { p->color=BACK; u->color=BACK; (*T)->color=RED; return 1; //继续... } else /*****④叔节点为黑色*****/ { if(firdirection!=secdirection) conversion(&((*T)->child[firdirection]),secdirection); (*T)->color=RED; (*T)->child[firdirection]->color=BACK; conversion(T,firdirection); return 0; }}int _InsertRBT(RBT *T,Elemtype e){ int info=0; if(NULL==(*T)) /*****①插入到根节点*****/ //这里只是包含这种情况 { *T=create_one_node(e); (*T)->color=RED; info=1; } else if(e>((*T)->e)) { info=_InsertRBT(&(*T)->child[1],e); if(info<1) return info; else if(info==1) /*****②父节点为黑******/ { if(BACK==((*T)->color)) info--; else info++; } else { info=keep_balance_for_insert(T,1,e); } } else if(e<((*T)->e)) { info=_InsertRBT(&((*T)->child[0]),e); if(info<1) return info; else if(info==1) { if(BACK==((*T)->color)) info--; else info++; } else { info=keep_balance_for_insert(T,0,e); } } else return info=-1; return info;}int InsertRBT(RBT *T,Elemtype e) //插入节点函数返回值: -1->改点已存在 0->成功插入{ int info=0; // info: -1->已存在 0->结束 1->回溯到父节点 2->回溯到祖节点 //2011年11月30日9:13:47 昨天晚上最后又想来这里这个if可以不要即可,也就是把它也放到_InsertRBT//内处理,在InsertRBT中有个判断即可!即改成下边的写法!// if(NULL==(*T)) /*****①插入到根节点*****/// {// *T=create_one_node(e);// (*T)->color=BACK;// }// else // {// info=_InsertRBT(T,e); // 经过再三思考,这里info的返回值只可能为:-1 0 1// if(info>0) (*T)->color=BACK,info=0; // 查看根节点是否为红// } info=_InsertRBT(T,e); if(info==1) (*T)->color=BACK,info=0; // 为了防止根结点变为红,它其实是处理了两种情况的后遗症// 分别是:③情况回溯上来,根节点变红 ①情况插入点即为根节点,为红// 这里没有直接把根结点变黑,主要是为了与_InsertRBT保持一致的写法,其实都行! return info;}//★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★//******************JUST FOR TEST********************//RBT queue[1000];void print(RBT cur){ int front=0,rear=0; int count=1,temp=0; if(NULL==cur) { printf("NULL\n"); return ; } queue[rear]=cur; while(front<=rear) { cur=queue[front++]; count--; if(NULL!=cur->child[0]) queue[++rear]=cur->child[0],temp++; if(NULL!=cur->child[1]) queue[++rear]=cur->child[1],temp++; printf("%d color->",cur->e); if(BACK==cur->color) printf("BACK |"); else printf("RED |"); if(0==count) { count=temp; temp=0; printf("\n"); } }}//*****************************************************////*****************DEAR MAIN***************************//int main(){ RBT T=NULL; int i,nodenum=100; print(T); printf("\n"); printf("\n插入操作\n"); for(i=0;i<nodenum;i++) { InsertRBT(&T,i); printf("插入%d\n",i); print(T); printf("\n"); }// print(T); printf("\n删除操作:\n"); for(i=0;i<nodenum;i++) { DeleteRBT(&T,i); printf("删除%d\n",i); print(T); printf("\n"); } return 0;}
主要参考资料:
http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html
维基百科:http://zh.wikipedia.org/wiki/%E7%BA%A2%E9%BB%91%E6%A0%91