红黑树(英语:Red–black tree)是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。它是在1972年由鲁道夫·贝尔发明的,他称之为"对称二叉B树",它现代的名字是在Leo J. Guibas和Robert Sedgewick于1978年写的一篇论文中得到的。它是复杂的,但它的操做有着良好的最坏状况运行时间,而且在实践中是高效的:它能够在O(log n)时间内作查找,插入和删除,这里的n是树中元素的数目。 php
红黑树和AVL树同样都对插入时间、删除时间和查找时间提供了最好可能的最坏状况担保。这不仅是使它们在时间敏感的应用如实时应用(real time application)中有价值,并且使它们有在提供最坏状况担保的其余数据结构中做为建造板块的价值;例如,在计算几何中使用的不少数据结构均可以基于红黑树。 html
红黑树在函数式编程中也特别有用,在这里它们是最经常使用的持久数据结构(persistent data structure)之一,它们用来构造关联数组和集合,每次插入、删除以后它们能保持为之前的版本。除了O(log n)的时间以外,红黑树的持久版本对每次插入或删除须要O(log n)的空间。 node
红黑树是2-3-4树的一种等同。换句话说,对于每一个2-3-4树,都存在至少一个数据元素是一样次序的红黑树。在2-3-4树上的插入和删除操做也等同于在红黑树中颜色翻转和旋转。这使得2-3-4树成为理解红黑树背后的逻辑的重要工具,这也是不少介绍算法的教科书在红黑树以前介绍2-3-4树的缘由,尽管2-3-4树在实践中不常用。 web
红黑树是每一个节点都带有颜色属性的二叉查找树,颜色为红色或黑色。在二叉查找树强制通常要求之外,对于任何有效的红黑树咱们增长了以下的额外要求: 算法
下面是一个具体的红黑树的图例: 编程
这些约束确保了红黑树的关键特性:从根到叶子的最长的可能路径很少于最短的可能路径的两倍长。结果是这个树大体上是平衡的。由于操做好比插入、删除和查找某个值的最坏状况时间都要求与树的高度成比例,这个在高度上的理论上限容许红黑树在最坏状况下都是高效的,而不一样于普通的二叉查找树。 数组
要知道为何这些性质确保了这个结果,注意到性质4致使了路径不能有两个毗连的红色节点就足够了。最短的可能路径都是黑色节点,最长的可能路径有交替的红色和黑色节点。由于根据性质5全部最长的路径都有相同数目的黑色节点,这就代表了没有路径能多于任何其余路径的两倍长。 数据结构
在不少树数据结构的表示中,一个节点有可能只有一个子节点,而叶子节点包含数据。用这种范例表示红黑树是可能的,可是这会改变一些性质并使算法复杂。为此,本文中咱们使用"nil叶子"或"空(null)叶子",如上图所示,它不包含数据而只充当树在此结束的指示。这些节点在绘图中常常被省略,致使了这些树好像同上述原则相矛盾,而实际上不是这样。与此有关的结论是全部节点都有两个子节点,尽管其中的一个或两个多是空叶子。 app
由于每个红黑树也是一个特化的二叉查找树,所以红黑树上的只读操做与普通二叉查找树上的只读操做相同。然而,在红黑树上进行插入操做和删除操做会致使再也不符合红黑树的性质。恢复红黑树的性质须要少许(O(log n))的颜色变动(实际是很是快速的)和不超过三次树旋转(对于插入操做是两次)。虽然插入和删除很复杂,但操做时间仍能够保持为O(logn)次。 ide
咱们首先以二叉查找树的方法增长节点并标记它为红色。(若是设为黑色,就会致使根到叶子的路径上有一条路上,多一个额外的黑节点,这个是很难调整的。可是设为红色节点后,可能会致使出现两个连续红色节点的冲突,那么能够经过颜色调换(color flips)和树旋转来调整。)下面要进行什么操做取决于其余临近节点的颜色。同人类的家族树中同样,咱们将使用术语叔父节点来指一个节点的父节点的兄弟节点。注意:
在下面的示意图中,将要插入的节点标为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);
}
注意:在下列情形下咱们假定新节点的父节点为红色,因此它有祖父节点;由于若是父节点是根节点,那父节点就应当是黑色。因此新节点总有一个叔父节点,尽管在情形4和5下它多是叶子节点。
情形3:若是父节点P和叔父节点U两者都是红色,(此时新插入节点N作为P的左子节点或右子节点都属于情形3,这里右图仅显示N作为P左子的情形)则咱们能够将它们两个重绘为黑色并重绘祖父节点G为红色(用来保持性质4)。如今咱们的新节点N有了一个黑色的父节点P。由于经过父节点P或叔父节点U的任何路径都一定经过祖父节点G,在这些路径上的黑节点数目没有改变。可是,红色的祖父节点G多是根节点,这就违反了性质2,也有可能祖父节点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又是其父节点的左子节点。在这种情形下,咱们进行一次左旋转调换新节点和其父节点的角色;接着,咱们按情形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又是其父节点G的左子节点。在这种情形下,咱们进行针对祖父节点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。另外一种简单状况是在被删除节点是黑色而它的儿子是红色的时候。若是只是去除这个黑色节点,用它的红色儿子顶替上来的话,会破坏性质5,可是若是咱们重绘它的儿子为黑色,则曾经经过它的全部路径将经过它的黑色儿子,这样能够继续保持性质5。
须要进一步讨论的是在要删除的节点和它的儿子两者都是黑色的时候,这是一种复杂的状况。咱们首先把要删除的节点替换为它的儿子。出于方便,称呼这个儿子为N(在新的位置上),称呼它的兄弟(它父亲的另外一个儿子)为S。在下面的示意图中,咱们仍是使用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在树中的位置。出于方便,在本章节中的代码将假定空叶子被用不是NULL的实际节点对象来表示(在插入章节中的代码能够同任何一种表示一块儿工做)。
void delete_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和它初始的父亲是黑色,则删除它的父亲致使经过N的路径都比不经过它的路径少了一个黑色节点。由于这违反了性质5,树须要被从新平衡。有几种情形须要考虑:
情形1: N是新的根。在这种情形下,咱们就作完了。咱们从全部路径去除了一个黑色节点,而新根是黑色的,因此性质都保持着。
void delete_case1(struct node *n) {
if(n->parent != NULL) delete_case2 (n);
}
注意:在情形二、5和6下,咱们假定N是它父亲的左儿子。若是它是右儿子,则在这些情形下的左和右应当对调。
情形2: S是红色。在这种情形下咱们在N的父亲上作左旋转,把红色兄弟转换成N的祖父,咱们接着对调N的父亲和祖父的颜色。完成这两个操做后,尽管全部路径上黑色节点的数目没有改变,但如今N有了一个黑色的兄弟和一个红色的父亲(它的新兄弟是黑色由于它是红色S的一个儿子),因此咱们能够接下去按情形4、情形5或情形6来处理。 |
void delete_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: N的父亲、S和S的儿子都是黑色的。在这种情形下,咱们简单的重绘S为红色。结果是经过S的全部路径,它们就是之前不经过N的那些路径,都少了一个黑色节点。由于删除N的初始的父亲使经过N的全部路径少了一个黑色节点,这使事情都平衡了起来。可是,经过P的全部路径如今比不经过P的路径少了一个黑色节点,因此仍然违反性质5。要修正这个问题,咱们要从情形1开始,在P上作从新平衡处理。 |
void delete_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: S和S的儿子都是黑色,可是N的父亲是红色。在这种情形下,咱们简单的交换N的兄弟和父亲的颜色。这不影响不经过N的路径的黑色节点的数目,可是它在经过N的路径上对黑色节点数目增长了一,添补了在这些路径上删除的黑色节点。 |
void delete_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: S是黑色,S的左儿子是红色,S的右儿子是黑色,而N是它父亲的左儿子。在这种情形下咱们在S上作右旋转,这样S的左儿子成为S的父亲和N的新兄弟。咱们接着交换S和它的新父亲的颜色。全部路径仍有一样数目的黑色节点,可是如今N有了一个黑色兄弟,他的右儿子是红色的,因此咱们进入了情形6。N和它的父亲都不受这个变换的影响。 |
void delete_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: S是黑色,S的右儿子是红色,而N是它父亲的左儿子。在这种情形下咱们在N的父亲上作左旋转,这样S成为N的父亲(P)和S的右儿子的父亲。咱们接着交换N的父亲和S的颜色,并使S的右儿子为黑色。子树在它的根上的还是一样的颜色,因此性质3没有被违反。可是,N如今增长了一个黑色祖先:要么N的父亲变成黑色,要么它是黑色而S被增长为一个黑色祖父。因此,经过N的路径都增长了一个黑色节点。 此时,若是一个路径不经过N,则有两种可能性:
在任何状况下,在这些路径上的黑色节点数目都没有改变。因此咱们恢复了性质4。在示意图中的白色节点能够是红色或黑色,可是在变换先后都必须指定相同的颜色。 |
void delete_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次)的旋转。
#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; };
包含n个内部节点的红黑树的高度是O(log(n))。
定义:
引理:以节点v为根的子树有至少个内部节点。
引理的证实(经过概括高度):
基础:h(v) = 0
若是v的高度是零则它一定是nil,所以bh(v) = 0。因此:
概括假设:h(v) = k的v有个内部节点暗示了h(
) = k+1的
有
个内部节点。
由于有h(
)> 0因此它是个内部节点。一样的它有黑色高度要么是bh(
)要么是bh(
)-1(依据
是红色仍是黑色)的两个儿子。经过概括假设每一个儿子都有至少
个内部接点,因此
有:
个内部节点。
使用这个引理咱们如今能够展现出树的高度是对数性的。由于在从根到叶子的任何路径上至少有一半的节点是黑色(根据红黑树性质4),根的黑色高度至少是h(root)/2。经过引理咱们获得:
所以根的高度是O(log(n))。
![]() |
维基共享资源中相关的多媒体资源:红黑树 |
|