红黑树

✍ dations ◷ 2025-06-09 07:19:30 #数据结构,树结构,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)}

相关

  • 复活节星期一复活节星期一(英语:Easter Monday)是复活节星期日后的第一天,一些主要的基督教文化都会庆祝这一节日,尤其是信仰罗马天主教会的文化。在罗马天主教圣人历中,复活节星期一位于复活
  • 卡尔海因茨·施托克豪森卡尔海因茨·施托克豪森(德语:Karlheinz Stockhausen,1928年8月22日-2007年12月5日),当代最重要的德国作曲家,音乐理论家,音乐教育家。施托克豪森1928年生于克尔彭。早年其母亲精神
  • 结构异构物结构异构,也称构造异构,是指由于化合物具有不同的原子连接顺序而产生的同分异构现象,与立体异构相对。存在结构异构的化合物互称结构异构体。它们又可分为碳链异构、位置异构和
  • 李愃庄献世子(朝鲜语:장헌세자/莊獻世子;1735年2月13日-1762年7月12日)又思悼世子(朝鲜语:사도세자/思悼世子),讳李愃(朝鲜语:이선/李愃),是朝鲜王朝的第21代君主英祖李昑之次子,被英祖逼迫,绝食而
  • 塞芒族塞芒人(Semang、Seemang、Siamang),又译赛孟人,属于俾格米人种(Pygmies),是一支矮黑人民族,分布于马来半岛北部及泰国南部,被认为是马来半岛上最早的原住民,也是全世界体型最矮小的
  • 台中发电厂台中发电厂,又称台中电厂、台中火力发电厂,简称中火,是位于台湾台中市龙井区的火力发电厂,隶属于台湾电力公司,以煤炭为燃料。厂址位于台中港南端,于1986年开始兴建计划,紧邻大肚溪
  • 吕村 (南乐县)吕村是河南省濮阳市南乐县千口乡的一个自然村,面积2000平方米,人口2千人。邮政编码457400。与山东省莘县毗邻。男性村民主要以李姓居多,还有赵、吴、闫等。村庄地势平坦,适宜各
  • 博物馆岛博物馆岛(德语:Museumsinsel)位于德国柏林市中心,施普雷岛(Spreeinsel)的北端。柏林所有博物馆都由此发展而来,也是柏林的旅游重点。1999年,因建筑与文化的结合,博物馆岛被联合国教科
  • 利奥波德一世 (奥地利)利奥波德一世(Leopold I,940年-994年7月10日),逝世于维尔茨堡,出自巴本堡王朝的奥地利藩侯。利奥波德在成为东部马克藩侯前是巴伐利亚多瑙区的藩候,他在960年代中就已经是奥托一世
  • 官安妮官安妮(1991年5月1日-),嘉义民雄人,台湾文史工作者、社会运动人士,毕业于国立中兴大学历史学系毕业、国立云林科技大学文化资产维护系硕士班。研究所期间接触外籍劳工议题,进而以理