连贯的学习黑树(插入节点)

红 - 黑树是一种平衡二叉树被广泛用于,但对于很多人谁是新算法,红黑树实在是太复杂,看之前July博文,觉得自己的写作非常具体的。不过还是有点乱。不能急。要每天看一点点,所以我把插入和删除分开来写,仅仅要看懂并记住插入后是如何操作的,那么删除也就easy了。

红黑树的规则:

性质1. 节点是红色或黑色。



性质2. 根是黑色。



性质3. 全部叶子都是黑色(叶子是NIL节点)。

性质4. 每一个红色节点的两个子节点都是黑色。(从每一个叶子到根的全部路径上不能有两个连续的红色节点)

性质5. 从任一节点到其每一个叶子的全部简单路径都包括同样数目的黑色节点。

最基本的两条是4和5。一旦插入一个新的节点,那么4和5的性质就有可能破坏。从5中我们能够推出新增节点必须设为红色,而再依据4。新增节点的父节点必须是黑色。

我们推出的这最后一条很重要。由于一旦发现插入节点要插在一个红色节点下,就要開始折腾了!

红黑树的最基本操作是旋转(左旋和右旋)。因为对称性,将一个即可。

下图介绍右旋的一个样例(本文图皆来自《STL源代码剖析》)


我们从图中发现,右旋事实上就是改变了k1,k2的父子关系,那么其它节点怎么变呢?

能够这样理解。父亲要和儿子换位置,父亲好悲剧,那父亲的还有一个儿子和他的父子关系不能变(再变就太慘了),儿子长辈分了,那么儿子就把他的儿子交给原来的父亲当儿子吧,而操作是右旋,那就移动儿子的右孩子。

简记:右旋:孩子的右孩子给父亲当左孩子。

            左旋:孩子的左孩子给父亲当右孩子。

默念三遍。就会发现旋转的思路就是这么简单!

代码例如以下(STL源代码。大师作品):

inline void _rb_rotate_right(_rb_tree_node_base* x,_rb_tree_node_base*& root)\\x为k2。root为根节点
   _rb_tree_node_base* y=x->left;   \\y为左孩子。也就是k1
    x->left=y->right;  \\孩子的左孩子成为父亲的右孩子 B接k2
    if(y->right!=0)
      y->right->parent=x;  \\  回马枪设定父节点
    y->parent=x->parent; \\k1開始跑到k2的位置
    if(x==root)
      root=y;
     else if(x==x->parent->right)   
       x->parent->right=y;   \\y代替x的地位,但要和x的原父亲的关系还原
     else
     x->parent->left=y;
     y->right=x;       
     x->parent=y;  \\最后,父亲变成孩子,悲剧。
}

代码思路:1先把孩子的左孩子接到这个节点的右孩子上(2句)  2这个右孩子的父亲是这个节点(1句) 3、节点的左孩子代替节点的位置(1句)  4、和原节点父亲节点的关系保留(3种推断)  5、节点成为孩子节点的孩子(2句)

以下进入正题:插入节点

插入节点可能会带来3种不同情况的破坏,下图给出的是4种情况:(4仅仅是3的更复杂一点的情况)


我们细致观察这三种不同情况,同样的是插入节点的父亲都是红的。不同点有2:1是父亲的兄弟节点(叔节点)是什么颜色的?2新增节点是作为左孩子还是右孩子?

第一种情况:

叔黑+左孩子(黑左bl)

解决方法:右旋。

例如以下图:


说是右旋,旋哪个呢?新增节点肯定不动,以新节点的父亲P,爷爷为轴右旋G。旋转后这两个节点颜色都变(新增点肯定是红的)。

简记:黑左(bl)=右旋PG

助记:bl=r+pg(Boys’ Love RPG)

另外一种情况:

叔黑+右孩子(br)

解决方法:左旋+右旋

例如以下图:


我们从图中能够看出,先左旋X和P,再右旋X和G(变化X和G)。细致想想,第一次左旋似乎就是构造出第一种情况(图2先把8和10的颜色变化了,应是8红,10黑,那就和第一种情况一样)。

简记:黑右(br)=左旋XP。到达叔左。

助记:嘿哟,郎咸平(lXP)

第三种情况

叔红

这样的情况很easy。我们肯定叔节点不能有孩子了,有的话必须是黑色的,那叔节点那条分支的黑色就多了,所以我们能够随便改变叔节点的颜色(这是前提)。

例如以下图(来自July)


4是新增节点,4和5都红了。那就把5变黑,5黑后为了平衡的把8(叔节点)也变黑,都黑了后7这条分支比1分支多了一个黑色节点。所以7也要红,这样和2又都是红色的了,还得往上递归。这时候7相当玉新插入节点。得三种情况推断(图中这个符合另外一种情况),一直递归到根节点。

简记:变变变(PGU都变了),再推断。

最后来看SLT里面大师是如何写源码的。

首先,他的思路有所变换,他感觉第三种情况一直往上递归太慢,所以他採用了先处理的方法,例如以下图:


大致思路是:沿着插入节点往上看。假设有一个节点的两个子节点都是红色。那就把这个节点改成红色,他的子节点都改成黑色。假设此时这个节点的父节点也是红色(此时父节点的兄弟节点一定是黑的,请自己分析)就要像第一种或者第一种情况处理。

最后,插入节点的推断就仅仅剩第1和第2两种情况。

代码例如以下:

// 这是一个全局函数  
// 又一次令树形平衡(改变颜色及旋转树形)  
inline void _rb_tree_rebalance(_rb_tree_node_base* x , _rb_tree_node_base*& root)   //x新增节点,root为根节点 
{  
    x->color = _rb_tree_red;    //新节点必为红  
    while(x != root && x->parent->color == _rb_tree_red)    // 假设父节点为红  
    {  
        if(x->parent == x->parent->parent->left)      // 父节点为祖父节点之左子节点  
        {  
            _rb_tree_node_base* y = x->parent->parent->right;    // 令y为伯父节点  
            if(y && y->color == _rb_tree_red)    // 伯父节点存在。且为红(第三种情况)  
            {  
                x->parent->color = _rb_tree_black;           // 更改父节点为黑色  
                y->color = _rb_tree_black;                   // 更改伯父节点为黑色  
                x->parent->parent->color = _rb_tree_red;     // 更改祖父节点为红色  
                x = x->parent->parent;  //祖父节点变成新增结点了
            }  
            else    // 无伯父节点,或伯父节点为黑色  
            {  
                if(x == x->parent->right)   // 假设新节点为父节点之右子节点 (另外一种情况) 
                {  
                    x = x->parent;  
                    _rb_tree_rotate_left(x , root);    // 第一个參数为左旋点  
                }  
                x->parent->color = _rb_tree_black;     // 先改变颜色了  
                x->parent->parent->color = _rb_tree_red;  
                _rb_tree_rotate_right(x->parent->parent , root);    // (第一种情况)
            }  
        }  
        else          // 父节点为祖父节点之右子节点(和上面对立)  
        {  
            _rb_tree_node_base* y = x->parent->parent->left;    // 令y为伯父节点  
            if(y && y->color == _rb_tree_red)    // 有伯父节点,且为红
(第三种情况) 
  
            {  
                x->parent->color = _rb_tree_black;           // 更改父节点为黑色  
                y->color = _rb_tree_black;                   // 更改伯父节点为黑色  
                x->parent->parent->color = _rb_tree_red;     // 更改祖父节点为红色  
                x = x->parent->parent;          // 准备继续往上层检查  
            }  
            else    // 无伯父节点,或伯父节点为黑色  
            {  
                if(x == x->parent->left)        // 假设新节点为父节点之左子节点 (另外一种情况) 
 
                {  
                    x = x->parent;  
                    _rb_tree_rotate_right(x , root);    // 第一个參数为右旋点 (第一种情况)
 
                }  
                x->parent->color = _rb_tree_black;     // 先改变颜色  
                x->parent->parent->color = _rb_tree_red;  
                _rb_tree_rotate_left(x->parent->parent , root);    // 第一个參数为左旋点  
            }  
        }  
    }//while  
    root->color = _rb_tree_black;    // 根节点永远为黑色  
}  



事实上,红黑树确实挺复杂的,变化太多,但仅仅要略微注意下几种情况的相互关系,比方3->2->1,理解就简单多了。

下一步学习删除节点。

版权声明:本文博客原创文章,博客,未经同意,不得转载。

<think>嗯,用户现在想了解B插入操作规则以及红通过旋转调整结构的原理。首先,我需要回顾一下B和红的基本概念,确保自己理解正确。B是一种平衡多路搜索,常用于数据库和文件系统,而红是一种自平衡二叉查找,通过颜色标记和旋转操作来保持平衡。 先处理B插入部分。用户提到的是插入叶子节点的规则,所以需要详细说明B插入的步骤。首先,B节点有最大和最小度数的限制。插入时,总是先找到合适的叶子节点,然后插入数据。如果插入后叶子节点的关键字数量超过最大限制,就需要进行分裂。分裂时,中间关键字上移到父节点,左右分裂成两个子节点。如果父节点也满了,继续递归分裂,可能影响到根节点,导致的高度增加。这部分需要分步骤解释清楚,可能需要举例说明。 接下来是红的旋转调整原理。用户之前的问题是关于叔叔节点色时的旋转调整,现在需要更全面的解释红通过旋转保持平衡的机制。红插入或删除节点后可能会破坏红性质,需要通过颜色调整和旋转来恢复。旋转包括左旋和右旋,用于调整的结构,而颜色翻转则是改变节点的颜色来满足红的性质。需要将插入后的各种情况(如父节点是左孩子还是右孩子,叔叔节点的颜色等)分类讨论,并说明相应的旋转策略。 需要注意的是,用户可能对B和红的区别和联系感兴趣,所以应该在回答中适当比较两者,比如B是多路平衡,红是二叉平衡,它们的应用场景有何不同。同时,用户提供的引用中有关于红节点结构和旋转的具体代码,可以引用这些内容来增强回答的可信度。 用户可能存在的疑问点包括:为什么B的分裂是中间关键字上移?红的旋转如何保证平衡?颜色调整的具体规则是什么?需要确保这些点在回答中得到清晰的解释。此外,用户之前的问题涉及红的叔叔节点情况,现在的问题更广泛,可能需要简要回顾之前的回答,但重点放在旋转调整的原理上。 还要检查是否需要补充示意图或代码示例,但根据用户要求,可能不需要代码,但需要结构化的步骤说明。同时,用户希望回答结构清晰,逐步解决问题,所以需要分点列出B插入步骤和红的旋转原理,并用例子辅助说明。 最后,生成的相关问题要围绕B和红插入、调整机制,以及它们的比较和应用场景。比如,B的删除操作如何处理,红AVL的区别,或者它们在不同场景下的性能比较。 现在整理思路,组织内容,确保每个部分逻辑连贯,术语准确,同时符合用户的中文回答要求,避免使用Markdown,使用自然的口语化表达。</think>### B插入操作的规则 #### 一、B的基本性质 B是一种**多路平衡搜索**,每个节点最多包含$M-1$个关键字($M$为阶数)和$M$个子节点,且满足: 1. 根节点至少有1个关键字(除非为空)。 2. 非根节点至少有$\lceil M/2 \rceil -1$个关键字。 3. 所有叶子节点位于同一层[^2]。 #### 二、插入操作步骤 1. **定位插入位置** 从根节点开始,递归向下查找目标关键字应插入的叶子节点。 2. **插入新关键字** 若叶子节点的关键字数量未超过$M-1$,直接插入并保持有序性。 3. **处理节点溢出** 若插入后叶子节点的关键字数达到$M$,需进行**分裂**: - 选择中间关键字$k$(位置为$\lceil M/2 \rceil$)。 - 将$k$提升到父节点。 - 剩余关键字分裂为两个子节点,每个子节点包含$\lfloor M/2 \rfloor$个关键字。 - 若父节点因此溢出,递归向上分裂,直到根节点(可能导致高度增加)。 **示例**:在3阶B($M=3$)中插入关键字序列`[5, 8, 3, 7, 2]`: 1. 插入5、8、3后,根节点为`[3,5,8]`(溢出)。 2. 分裂为根节点`[5]`,左子节点`[3]`,右子节点`[8]`。 3. 插入7、2后,左子节点变为`[2,3]`,右子节点变为`[7,8]`。 --- ### 红通过旋转调整结构的原理 #### 一、红的核心性质 1. 每个节点为红或。 2. 根节点。 3. 叶子节点(NIL)为。 4. 红节点的子节点必须为(无连续红节点)。 5. 从任一节点到其叶子节点的路径包含相同数量的节点高一致)[^2]。 #### 二、旋转调整的触发条件 当插入或删除节点导致**双红冲突**(父子均为红)或**高不一致**时,需通过旋转和颜色调整恢复性质。 #### 三、旋转操作的类型作用 1. **左旋** - **目的**:将右子节点提升为父节点,原父节点变为左子节点。 - **示意图**: ```text P C \ / C → P / \ GC GC ``` 2. **右旋** - **目的**:将左子节点提升为父节点,原父节点变为右子节点。 - **示意图**: ```text P C / \ C → P \ / GC GC ``` #### 四、插入后的调整场景 以插入后触发双红冲突为例(父节点为红,当前节点为红): 1. **叔叔节点为红** - **操作**:颜色翻转(父、叔变,祖父变红),递归检查祖父节点[^1]。 2. **叔叔节点(或NIL)** - 根据父节点祖父节点位置关系,分为四种旋转类型(LL/RR/LR/RL): - **LL/RR型**:单次旋转(右旋/左旋)祖父节点,并交换父祖父颜色。 - **LR/RL型**:两次旋转(先旋转父节点再旋转祖父节点),新节点,祖父变红[^1]。 **示例**(LR型调整): ```text G(B) G(B) N(B) / \ / \ / \ P(R) U(B) → N(R) U(B) → P(R) G(R) \ / N(R) P(R) ``` --- ### B的对比 | 特性 | B | 红 | |---------------------|----------------------------------|---------------------------------| | **结构** | 多路平衡,节点多个关键字 | 二叉平衡,节点含单个关键字 | | **应用场景** | 磁盘存储(减少I/O次数) | 内存数据(高效动态操作) | | **调整操作** | 节点分裂/合并 | 旋转+颜色翻转 | | **时间复杂度** | 插入/删除:$O(\log n)$ | 插入/删除:$O(\log n)$ | --- ### 相关问题 1. **B在分裂时为何选择中间关键字上移?** 2. **红的删除操作如何通过旋转恢复平衡?** 3. **B在数据库索引中的优劣比较?** 4. **为什么红的旋转操作时间复杂度是$O(1)$?** [^1]: 红通过旋转和颜色翻转恢复平衡性 [^2]: B通过节点分裂保持多路平衡特性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值