红黑树

✍ dations ◷ 2025-08-07 22:02:02 #数据结构,树结构,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)}

相关

  • 拉瓦尔大学拉瓦尔大学(法语:Université Laval) 是位于加拿大魁北克市的一所历史悠久的综合大学,为北美地区建立的第四所高等教育机构,也是该地区最古老的法语大学和加拿大主要大学之一,学
  • 古菌分类表#泉古菌门(Crenarchaeota)本表以LPSN网站的分类为基础(当前版本2007年3月29日),本分类代表原核生物分类的权威杂志IJSEM的分类系统,同时参考NCBI Taxonomy,但目前其它中文维基分类表可能依照其它标准,请注
  • 国际标准音像制品编码国际标准音像制品编码(英语:International Standard Recording Code,ISRC),由ISO 3901定义,是国际通用的录音及音乐录像制品出版物的代码。IFPI被ISO指定为注册ISRC编码的机构。TC
  • 鹅膏菌科鹅膏菌科(学名:Amanitaceae)是伞菌纲伞菌目的一个科,包含多种蕈类。本科最大的属为鹅膏菌属。真菌学的研究显示在伞菌目下面,各科之间的定义有着非常大的歧异度;并且现今的权威性
  • 吴江市吴江区是中国江苏省苏州市所辖的一个市辖区,位于江苏省东南部,全区总面积为1176.68平方公里(不包括所辖太湖水面)。区政府驻松陵镇。在撤市建区前,吴江为中国经济发达的县市之一,
  • 战友《战友》(韩语:전우;英语:Comrades/Legend of the Patriots)是韩国KBS电视台于2010年6月19日起播出的大河连续剧,该剧是1975年的重拍版本,亦是纪念韩战爆发60周年的电视剧。由崔秀宗
  • 日本动漫动漫是动画或漫画的合称与缩写,原意也是动画和漫画,但现代一般词意(被误解成)单纯的指是动画或是漫画,并且为了区别对“动画等于幼稚”这种刻板印象而才有了动漫现代大部分误解的
  • 履带履带(Continuous tracks,字面意思“连续轨道”)是一种使用于坦克车、工程车等运输工具的装置,用来使这些机器移动。履带的构造有两大类别,一类时是以坚硬的材料制成,并由许多小片
  • 韩国铁道8100型电力机车韩国铁道8100型电力机车(朝鲜语:한국철도공사 8100호대 전기 기관차)是韩国铁道公社的电力机车车型之一,也是韩国第一种交流传动电力机车,由德国西门子交通集团设计、通过技术转
  • 普布利乌斯·德西乌斯·穆斯普布利乌斯·德西乌斯·穆斯(Publius Decius Mus,?-前279年?),古罗马将军和政治家,并在前279年当选执政官。穆斯与同为执政官的普布利乌斯·苏尔皮基乌斯·萨维里奥率领军队与伊庇鲁