红黑树

✍ dations ◷ 2025-07-12 16:53:50 #数据结构,树结构,B树

红黑树(英语:Red–black tree)是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。它在1972年由鲁道夫·贝尔发明,被称为"对称二叉B树",它现代的名字源于Leo J. Guibas和Robert Sedgewick于1978年写的一篇论文。红黑树的结构复杂,但它的操作有着良好的最坏情况运行时间,并且在实践中高效:它可以在 O ( log n ) {\displaystyle {\text{O}}(\log n)} 属性的二叉查找树,颜色为或。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:

下面是一个具体的红黑树的图例:

这些约束确保了红黑树的关键特性:从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。

要知道为什么这些性质确保了这个结果,注意到性质4导致了路径不能有两个毗连的红色节点就足够了。最短的可能路径都是黑色节点,最长的可能路径有交替的红色和黑色节点。因为根据性质5所有最长的路径都有相同数目的黑色节点,这就表明了没有路径能多于任何其他路径的两倍长。

在很多树数据结构的表示中,一个节点有可能只有一个子节点,而叶子节点包含数据。用这种范例表示红黑树是可能的,但是这会改变一些性质并使算法复杂。为此,本文中我们使用"nil叶子"或"空(null)叶子",如上图所示,它不包含数据而只充当树在此结束的指示。这些节点在绘图中经常被省略,导致了这些树好像同上述原则相矛盾,而实际上不是这样。与此有关的结论是所有节点都有两个子节点,尽管其中的一个或两个可能是空叶子。

因为每一个红黑树也是一个特化的二叉查找树,因此红黑树上的只读操作与普通二叉查找树上的只读操作相同。然而,在红黑树上进行插入操作和删除操作会导致不再符合红黑树的性质。恢复红黑树的性质需要少量( O ( log n ) {\displaystyle {\text{O}}(\log n)} 来指一个节点的父节点的兄弟节点。注意:

在下面的示意图中,将要插入的节点标为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为红色(用来保持性质5)。现在我们的新节点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);         n = n->left;     } else if(n == n->parent->left && n->parent == grandparent(n)->right) {         rotate_right(n);         n = n->right;     }     insert_case5 (n); }
章节中的代码可以同任何一种表示一起工作)。

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和它初始的父亲是黑色,则删除它的父亲导致通过N的路径都比不通过它的路径少了一个黑色节点。因为这违反了性质5,树需要被重新平衡。有几种情形需要考虑:

情形1: N是新的根。在这种情形下,我们就做完了。我们从所有路径去除了一个黑色节点,而新根是黑色的,所以性质都保持着。

voiddelete_case1(struct node *n){        if(n->parent != NULL)                delete_case2 (n);}

注意:在情形2、5和6下,我们假定N是它父亲的左儿子。如果它是右儿子,则在这些情形下的和应当对调。

情形2: S是红色。在这种情形下我们在N的父亲上做左旋转,把红色兄弟转换成N的祖父,我们接着对调N的父亲和祖父的颜色。完成这两个操作后,尽管所有路径上黑色节点的数目没有改变,但现在N有了一个黑色的兄弟和一个红色的父亲(它的新兄弟是黑色因为它是红色S的一个儿子),所以我们可以接下去按情形4、情形5或情形6来处理。

(注意:这里的图中没有显示出来,N是删除了黑色节点后替换上来的子节点,所以这个过程中由P->X->N变成了P->N,实际上是少了一个黑色节点,也可以理解为Parent(Black)和Silbing(Red)那么他们的孩子黑色节点的数目肯定不等,让他们做新兄弟肯定是不平衡的,还需后面继续处理。这里看英文版本的比较的明了)

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);}
通过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);}
个内部节点的红黑树的高度是 O ( log n ) {\displaystyle {\text{O}}(\log n)}

定义:

引理:以节点 v {\displaystyle v} 为根的子树有至少 2 b h ( v ) 1 {\displaystyle 2^{bh(v)}-1} 个内部节点。

引理的证明(通过归纳高度):

基础: h ( v ) = 0 {\displaystyle h(v)=0}

如果 v {\displaystyle v} 的高度是零则它必定是NIL,因此 b h ( v ) = 0 {\displaystyle bh(v)=0} 。所以:

2 b h ( v ) 1 = 2 0 1 = 1 1 = 0 {\displaystyle 2^{bh(v)}-1=2^{0}-1=1-1=0}

归纳假设: h ( v ) = k {\displaystyle h(v)=k} v {\displaystyle v} 2 b h ( v ) 1 1 {\displaystyle 2^{bh(v)-1}-1} 个内部节点暗示了h( v {\displaystyle v'} ) = k+1的 v {\displaystyle v'} 2 b h ( v ) 1 {\displaystyle 2^{bh(v')}-1} 个内部节点。

因为 v {\displaystyle v'} 有h( v {\displaystyle v'} )> 0所以它是个内部节点。同样的它有黑色高度要么是bh( v {\displaystyle v'} )要么是bh( v {\displaystyle v'} )-1(依据 v {\displaystyle v'} 是红色还是黑色)的两个儿子。通过归纳假设每个儿子都有至少 2 b h ( v ) 1 1 {\displaystyle 2^{bh(v')-1}-1} 个内部接点,所以 v {\displaystyle v'} 有:

2 b h ( v ) 1 1 + 2 b h ( v ) 1 1 + 1 = 2 b h ( v ) 1 {\displaystyle 2^{bh(v')-1}-1+2^{bh(v')-1}-1+1=2^{bh(v')}-1}

个内部节点。

使用这个引理我们现在可以展示出树的高度是对数性的。因为在从根到叶子的任何路径上至少有一半的节点是黑色(根据红黑树性质4),根的黑色高度至少是 h ( r o o t ) 2 {\displaystyle {\frac {h(root)}{2}}} 。通过引理我们得到:

n 2 h ( r o o t ) 2 1 log ( n + 1 ) h ( r o o t ) 2 h ( r o o t ) 2 log ( n + 1 ) {\displaystyle n\geqslant 2^{\frac {h(root)}{2}}-1\leftrightarrow \;\log {(n+1)}\geqslant {\frac {h(root)}{2}}\leftrightarrow \;h(root)\leqslant 2\log {(n+1)}}

因此根的高度是 O ( log n ) {\displaystyle {\text{O}}(\log n)}

相关

  • 清辅音语音学中,将发音时声带振动的音称为浊音(又称有声音,英语:voiced sound),声带不振动的音称为清音(又称无声音,英语:voiceless sound)。辅音(子音)有清有浊,而多数语言中元音(母音)均为浊音,
  • 布鲁斯·艾姆斯布鲁斯·艾姆斯(英语:Bruce Ames,1928年12月16日-),美国生物化学学家,加州大学生物化学与分子生物学系教授。Template:加拿大盖尔德纳国际奖获得者 Template:Authority control
  • 萨达姆·侯赛因萨达姆·侯赛因·阿卜杜勒-迈吉德·提克里蒂(阿拉伯语:صدام حسين عبد المجيد التكريتي‎,1937年4月28日-2006年12月30日),简称萨达姆·侯赛因(阿拉伯语:ص
  • 当量浓度当量浓度(英语:Equivalent Concentration 或 Normality)是指要提供产物一摩尔,所需要的反应物质量。这个物质在酸碱反应指放出 H
  • 桃园医院徐永年(院长) 王伟杰(副院长)陈厚全(副院长)陈日昌(副院长) 郑明德(分院长)桃园总院:区域教学医院 新屋分院:地区医院卫生福利部桃园医院,为中华民国卫生福利部所属医院,1979年设立,简称
  • 文森佐·尼巴里文森佐·尼巴利(意大利语:Vincenzo Nibali;1984年11月14日-)是一位意大利自行车运动员。他现在效力于巴林·美利达一级职业车队Team Bahrain Merida。他是2014年环法自行车赛的冠
  • 欧仁亲王欧仁·德·博阿尔内(法语:Eugène de Beauharnais;1781年9月3日-1824年2月21日)法国军事家,为亚历山大·德·博阿尔内子爵与妻子约瑟芬的独子。拿破仑随身少将副官出生在法国首都
  • 东京梦华录《东京梦华录》,凡十卷,作者为孟元老,是一本描写北宋宣和年间东京汴梁城(现 河南省开封市)的社会生活旧事之著作。孟元老于史无载,清代学者常茂徕以为孟元老可能是孟揆。《东京梦
  • 北京四合院北京四合院是位于中国北京的四合院建筑。由于北京是四合院最常见也最有特色的一个城市,因此人们提起四合院常常就是指北京四合院。北京的四合院现在已经和北京胡同一起,成为北
  • 家族计划《家族计划》(日语:家族計画),常被称作“家计”,是一款最初发行于2001年11月2日的D.O.公司(日语:ディーオー)发行的十八禁美少女游戏。其曾被移植到多个平台。家族计划本身在日文有