红黑树

✍ dations ◷ 2024-09-20 14:48:35 #数据结构,树结构,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)}

相关

  • 胺碘酮胺碘酮(英语:Amiodarone)为一种抗心律失常药,可用于预防或治疗数种心律不整,包含心室频脉(英语:ventricular tachycardia)(VT)、心室颤动(VF)、宽QRS复合波心搏过速、心房颤动,以及阵发性
  • 利己主义利己主义或自我主义是凡事只为自己或对自己有关系的团体着想的行为。与利他主义相反。单在道德判断上,自己的幸福快乐比别人的来得重要,所以利己主义在许多思想和文化中是一种
  • 农杆菌属农杆菌属(学名:Agrobacterium),又称土壤杆菌属,是革兰氏阴性菌的一个属,常通过水平基因转移导致植物根瘤的产生,该属以向植物导入DNA的能力而闻名,因此农杆菌属也是用于改良植物的重
  • 波尔卡点波尔卡点也称为波卡圆点(英语:Polka dot),是一种由一系列实心圆点构成的花纹。这些圆点通常大小一致,排列紧密,但也有波尔卡点是由许多大小不同的圆点构成。波尔卡点常见于儿童服
  • 梅德韦梅德韦(Medway)是英国肯特郡的一个单一管理区,1998年由梅德韦河畔罗切斯特(Rochester-upon-Medway)、吉灵厄姆区议会和肯特郡郡议会的一部分合并成立梅德韦议会而形成,该地区独立
  • 大鹏街道大鹏街道是中国广东省深圳市龙岗区下辖的一个街道,位于龙岗区东部。辖区总面积82.81平方公里,下辖7个社区。总人口6万人。村:
  • 圣安东尼奥马刺圣安东尼奥马刺(英语:San Antonio Spurs,简称:SAS),是一支位于美国德克萨斯州圣安东尼奥的国家篮球协会(NBA联盟)旗下职业球队,分属于西部的西南赛区,球队主场为AT&T中心。目前球队之
  • 反坦克导弹反坦克导弹或反坦克制导武器是一种被专门用来袭击并摧毁坦克或者其它装甲战斗车辆的制导导弹。坦克于第一次世界大战出现在战场之后,各种反坦克武器相继出笼,在二次世界大战爆
  • EA-6BEA-6徘徊者(Prowler)式是一种双引擎、中单翼的舰载机,专门担任电子作战任务,机体沿用A-6闯入者式攻击机的基本设计,由格鲁门公司(现为诺斯洛普·格鲁门公司)制造。目前服役的是EA-6
  • 京苏菜京苏菜又称京苏大菜、金陵菜,指以南京为中心,一直延伸到江西九江的地方风味。金陵菜起源于先秦,隋唐已负盛名,至明清成流派。为苏菜的一个分支。金陵菜原料多以水产为主,注重鲜活