红黑树原理,算法,和构建过程的分析和学习_不能被着色以形成合法的红黑树。

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Linux运维全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上运维知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注运维)
img

正文

算法导论中的伪代码如下:(我把判断部分单独分开,可以看的清楚一些)

//T:红黑树
//nil[T]:哨兵节点,类似空节点
//每个节点存在5个域:color(颜色),key(关键字),left(左指针),right(右指针),p(父指针)
LEFT-ROTATE(T, x)  
 y ← right[x]              // 前提:假设x的右孩子为y。
 right[x] ← left[y]      // 将 “y的左孩子” 设为 “x的右孩子”。
 p[left[y]] ← x           // 将 “x” 设为 “y的左孩子的父亲”。
 p[y] ← p[x]              // 将 “x的父亲” 设为 “y的父亲”

 if p[x] = nil[T]       
	then root[T] ← y       // 情况1:如果 “x的父亲” 是空节点,则将y设为根节点
 else if x = left[p[x]]  
        then left[p[x]] ← y   // 情况2:如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
 else 
	right[p[x]] ← y         // 情况3:x是它父节点的右孩子, 将y设为“x的父节点的右孩子”

 left[y] ← x             // 将 “x”设为 “y的左孩子”
 p[x] ← y                // 将 “x ”的父节点” 设为 “y”

(2)右旋

右旋操作本质上就是将当前节点变成右节点(具体操作就是固定“当前节点Y”,然后顺时针旋转其“左孩子节点X”,使得X变成Y的父节点)。

算法导论中的伪代码如下:

//T:红黑树
//nil[T]:哨兵节点,类似空节点
//每个节点存在5个域:color(颜色),key(关键字),left(左指针),right(右指针),p(父指针)
RIGHT-ROTATE(T, y)  
 x ← left[y]                // 前提:这里假设y的左孩子为x。
 left[y] ← right[x]      // 将 “x的右孩子” 设为 “y的左孩子”。
 p[right[x]] ← y         // 将 “y” 设为 “x的右孩子的父亲”。
 p[x] ← p[y]              // 将 “y的父亲” 设为 “x的父亲”
 
if p[y] = nil[T]       
	then root[T] ← x         // 情况1:如果 “y的父亲” 是空节点,则将x设为根节点
else if y = right[p[y]]  
        then right[p[y]] ← x   // 情况2:如果 y是它父节点的右孩子,则将x设为“y的父节点的右孩子”
else 
	left[p[y]] ← x    		 // 情况3:y是它父节点的左孩子 将x设为“y的父节点的左孩子”

right[x] ← y            // 将 “y” 设为 “x的右孩子”
p[y] ← x                 // 将 “y的父节点” 设为 “x”
红黑树操作基础—添加:

插入的步骤简单来划分可以分成3步:

(1)插入节点

因为红黑树其实也是二叉树,所以可以根据树的键值来确认插入的位置,然后插入节点。

(2)着色为红色

插入之后需要将节点着色为红色。为什么是红色?

因为需要保证特性(5),如果是黑色的话那么次路径肯定比其他路径多了一个黑色节点,着色就不满足要求了,所以直接着色为红色就没有特性(5)出现的问题。

(3)通过旋转或者再次着色,重新满足红黑树要求

5大特性,在着色之后还会出现哪些特性被影响呢?那就是特性(4),因为当前节点被着色为红色,如果父节点也是红色,立马就不满足要求。所以需要旋转和重新着色。

添加操作伪代码:

//待插入节点z
RB-INSERT(T, z)  
 y ← nil[T]                        // 新建节点“y”,将y设为空节点。
 x ← root[T]                       // 设“红黑树T”的根节点为“x”
 while x ≠ nil[T]                  // 找出要插入的节点“z”在二叉树T中的位置“y”
     do y ← x                      
        if key[z] < key[x] 
	    then x ← left[x]  
        else 
	    x ← right[x]  
 p[z] ← y                   // 设置 “z的父亲” 为 “y”
 
 if y = nil[T] 
    then root[T] ← z        // 情况1:若y是空节点,则将z设为根
 else if key[z] < key[y]        
    then left[y] ← z        // 情况2:若“z的key域” < “y的key域”,则将z设为“y的左孩子”
 else 
    right[y] ← z      	    // 情况3:“z的key域” >= “y的key域”,将z设为“y的右孩子” 
 
 left[z] ← nil[T]           // z的左孩子设为空
 right[z] ← nil[T]          // z的右孩子设为空。
 
 //第一步完成:将“节点z插入到二叉树”中了。
 
 color[z] ← RED             // 将z着色为“红色”
 
 //第二步完成:着色。
 
 RB-INSERT-FIXUP(T, z)      // 通过RB-INSERT-FIXUP对红黑树的节点进行颜色修改以及旋转,让树T仍然是一颗红黑树

 //第三步完成:旋转和着色。

修正操作伪代码:

RB-INSERT-FIXUP(T, z)
while color[p[z]] = RED                                                  // 若“当前节点(z)的父节点是红色”,则进行以下处理。
    do if p[z] = left[p[p[z]]]                                           // 若“z的父节点”是“z的祖父节点的左孩子”,则进行以下处理。
          then y ← right[p[p[z]]]                                        // 将y设置为“z的叔叔节点(z的祖父节点的右孩子)”
               if color[y] = RED                                         // Case 1条件:叔叔是红色
				  then color[p[z]] ← BLACK                    ▹ Case 1   //  (01) 将“父节点”设为黑色。
                       color[y] ← BLACK                       ▹ Case 1   //  (02) 将“叔叔节点”设为黑色。
                       color[p[p[z]]] ← RED                   ▹ Case 1   //  (03) 将“祖父节点”设为“红色”。
                       z ← p[p[z]]                            ▹ Case 1   //  (04) 将“祖父节点”设为“当前节点”(红色节点)
               else if z = right[p[z]]                                   // Case 2条件:叔叔是黑色,且当前节点是右孩子
					then z ← p[z]    	                      ▹ Case 2   //  (01) 将“父节点”作为“新的当前节点”。
                               LEFT-ROTATE(T, z)              ▹ Case 2   //  (02) 以“新的当前节点”为支点进行左旋。
                          color[p[z]] ← BLACK                 ▹ Case 3   // Case 3条件:叔叔是黑色,且当前节点是左孩子。(01) 将“父节点”设为“黑色”。
                          color[p[p[z]]] ← RED                ▹ Case 3   //  (02) 将“祖父节点”设为“红色”。
                          RIGHT-ROTATE(T, p[p[z]])            ▹ Case 3   //  (03) 以“祖父节点”为支点进行右旋。
	   else (same as then clause with "right" and "left" exchanged)      // 若“z的父节点”是“z的祖父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。
color[root[T]] ← BLACK

修正代码比较复杂,场景较多,但是涉及到的场景基本都是在保证一个红黑树不被破坏,一般就是性质(2),(4),(5)容易被破坏,所以需要左旋,右旋等操作。

修正操作中的场景总结:

伪代码中也列出来3个场景以及修正过程:

CaseA1:当前节点的父节点是红色,且当前节点的父节点的另一个节点(叔叔节点)也是红色。

处理策略—> (01)父节点设置黑色;

(02)叔叔节点设置黑色;

(03)祖父节点设置红色。

(04)设置祖父节点为当前节点。

CaseA2:当前节点的父节点是红色,叔叔节点是黑色且当前节点是父节点的右孩子。

处理策略—> (01)将父节点作为新的当前节点;(02)以当前节点为支点左旋;

CaseA3:当前节点的父节点是红色,叔叔节点是黑色且当前节点是父节点的左孩子。

处理策略—> (01)设置父节点为黑色;(02)设置祖父节点为红色;(03)以祖父节点为支点右旋;

当然,三种场景需要图的结合才能更好的看出具体操作,下面列出参考文章内的图,做了一点改正,(有些地方不对)。

需要注意伪代码中在进入do if的操作时需要一次判断,else的场景下需要将left和right互换,也就是相反的操作,但是场景还是原有的场景,本系列学习就称作CaseB1,CaseB2,CaseB3。

修正操作中的场景总结图展示:

  1. 前提:

基础红黑树(图1-1);

2. 插入节点45:

45节点插入进去之后如图2-1,插入过程非常简单,因为本身红黑树就是有序了,所以只要查找到待插入位置,然后插入,着色就行。

2.1 针对插入45之后的第一次修正(满足CaseA1):

很显然,这次插入违背了特性(4),父节点和子节点都是红色了,所以进行调整。

2.2 针对插入45之后的第二次修正(依然满足CaseA1):

第一次修正之后当前节点变成60,此时又出现了违背了特性(4),父节点和孩子节点都为红色。因此还需要继续修正。

2.3 针对插入45之后的第三次修正(都不满足Case的要求,直接执行最后的Root着色操作):

至此插入动作和修正操作结束,红黑树重新变为合法的。但是此次插入没有涉及到Case2和Case3的场景,需要再次分析一下这两个场景。

一次完整的插入操作实例。

这里贴一下算法导论里的结论:这里在上面给的插入完整示例内也发现了,在执行CaseA2(CaseB2)或者CaseA3(CaseB3)之后就会变成一个合法的红黑树。不会超过2次,超过2次就要去看看是不是场景分析错了。

红黑树操作基础—删除:

红黑树的删除分成2步:

(1)删除节点

删除节点和普通二叉树一样,需要分成3种情况:

(1.1)叶子节点:直接删除;

(1.2)只有一个孩子:直接删除,并将孩子节点顶替该位置;

(1.3)有两个孩子:找出后继节点

(2)旋转和着色重新变为合法红黑树

删除操作伪代码:

RB-DELETE(T, z)

if left[z] = nil[T] or right[z] = nil[T]         
	then y ← z                          // 若“z的左孩子” 或 “z的右孩子”为空,则将“z”赋值给 “y”;
else 
	y ← TREE-SUCCESSOR(z)               // 否则,将“z的后继节点”赋值给 “y”。

if left[y] ≠ nil[T]
	then x ← left[y]                    // 若“y的左孩子” 不为空,则将“y的左孩子” 赋值给 “x”;
else 
	x ← right[y]                        // 否则,“y的右孩子” 赋值给 “x”。
	
p[x] ← p[y]                             // 将“y的父节点” 设置为 “x的父节点”

if p[y] = nil[T]                               
	then root[T] ← x                    // 情况1:若“y的父节点” 为空,则设置“x” 为 “根节点”。
else if y = left[p[y]]                    
	then left[p[y]] ← x                 // 情况2:若“y是它父节点的左孩子”,则设置“x” 为 “y的父节点的左孩子”
else 
	right[p[y]] ← x                		// 情况3:若“y是它父节点的右孩子”,则设置“x” 为 “y的父节点的右孩子”

if y ≠ z                                    
   then key[z] ← key[y]                 // 若“y的值” 和“z的值不等”(也就是z有两个孩子),则赋值给 “z”。注意:这里只拷贝z的值给y,而没有拷贝z的颜色!!!
   copy y's satellite data into z         

if color[y] = BLACK                            
   then RB-DELETE-FIXUP(T, x)           // 若“y为黑节点”,则调用

return y

基于第一步的删除节点来简单归纳一下它可能违反的特性:

特性(2):如果删除y的是根,那么y的一个红色孩子成为了根。

特性(4):x和p[y]可能都是红色的。也就是删除的y是黑色,p[y]和x都是红色的。

特性(5):删除的y可能是黑色,导致此路径上黑色节点少1。

和插入一样,算法导论里还是在优先满足特性(5),这样在修正的时候就少一种不满足,怎么去保证的呢?

假定“x包含一个额外的黑色”,这样就解决了特性(5)的问题,but,why?

因为x节点原本是有颜色的,要么红,要么黑,当删除的y是黑色时,x节点上移时,该路径上就减少了一个黑色节点。所以我们假定x包含一个额外的黑色的话就解决了这个问题了,特性(5)满足了。

但是x节点现在就会出现两种颜色了,“红”+“黑”或者“黑”+“黑”;这就不满足特性(1)了,因此现在需要解决的问题就是如何去满足特性(1),特性(2),特性(4)。

修正操作伪代码:

RB-DELETE-FIXUP(T, x)
while x ≠ root[T] and color[x] = BLACK  
    do if x = left[p[x]]      
          then w ← right[p[x]]                                             // 若 “x”是“它父节点的左孩子”,则设置 “w”为“x的叔叔”(即x为它父节点的右孩子)                                          
               if color[w] = RED                                           // Case 1: x是“黑+黑”节点,x的兄弟节点是红色。(此时x的父节点和x的兄弟节点的子节点都是黑节点)。
                  then color[w] ← BLACK                        ▹  Case 1   //   (01) 将x的兄弟节点设为“黑色”。
                       color[p[x]] ← RED                       ▹  Case 1   //   (02) 将x的父节点设为“红色”。
                       LEFT-ROTATE(T, p[x])                    ▹  Case 1   //   (03) 对x的父节点进行左旋。
                       w ← right[p[x]]                         ▹  Case 1   //   (04) 左旋后,重新设置x的兄弟节点。
               if color[left[w]] = BLACK and color[right[w]] = BLACK       // Case 2: x是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色。
                  then color[w] ← RED                          ▹  Case 2   //   (01) 将x的兄弟节点设为“红色”。
                       x ←  p[x]                               ▹  Case 2   //   (02) 设置“x的父节点”为“新的x节点”。
                  else if color[right[w]] = BLACK                          // Case 3: x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的。
                          then color[left[w]] ← BLACK          ▹  Case 3   //   (01) 将x兄弟节点的左孩子设为“黑色”。


为了做好运维面试路上的助攻手,特整理了上百道 **【运维技术栈面试题集锦】** ,让你面试不慌心不跳,高薪offer怀里抱!

这次整理的面试题,**小到shell、MySQL,大到K8s等云原生技术栈,不仅适合运维新人入行面试需要,还适用于想提升进阶跳槽加薪的运维朋友。**

![](https://img-blog.csdnimg.cn/img_convert/de84d317de0faaa29f714e2b26a42793.png)

本份面试集锦涵盖了

*   **174 道运维工程师面试题**
*   **128道k8s面试题**
*   **108道shell脚本面试题**
*   **200道Linux面试题**
*   **51道docker面试题**
*   **35道Jenkis面试题**
*   **78道MongoDB面试题**
*   **17道ansible面试题**
*   **60道dubbo面试题**
*   **53道kafka面试**
*   **18道mysql面试题**
*   **40道nginx面试题**
*   **77道redis面试题**
*   **28道zookeeper**

**总计 1000+ 道面试题, 内容 又全含金量又高**

*   **174道运维工程师面试题**

> 1、什么是运维?

> 2、在工作中,运维人员经常需要跟运营人员打交道,请问运营人员是做什么工作的?

> 3、现在给你三百台服务器,你怎么对他们进行管理?

> 4、简述raid0 raid1raid5二种工作模式的工作原理及特点

> 5、LVS、Nginx、HAproxy有什么区别?工作中你怎么选择?

> 6、Squid、Varinsh和Nginx有什么区别,工作中你怎么选择?

> 7、Tomcat和Resin有什么区别,工作中你怎么选择?

> 8、什么是中间件?什么是jdk?

> 9、讲述一下Tomcat8005、8009、8080三个端口的含义?

> 10、什么叫CDN?

> 11、什么叫网站灰度发布?

> 12、简述DNS进行域名解析的过程?

> 13、RabbitMQ是什么东西?

> 14、讲一下Keepalived的工作原理?

> 15、讲述一下LVS三种模式的工作过程?

> 16、mysql的innodb如何定位锁问题,mysql如何减少主从复制延迟?

> 17、如何重置mysql root密码?

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注运维)**
![img](https://img-blog.csdnimg.cn/img_convert/3f674f734dbe5e17c7463e4cad64f339.jpeg)

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
、什么叫网站灰度发布?

> 12、简述DNS进行域名解析的过程?

> 13、RabbitMQ是什么东西?

> 14、讲一下Keepalived的工作原理?

> 15、讲述一下LVS三种模式的工作过程?

> 16、mysql的innodb如何定位锁问题,mysql如何减少主从复制延迟?

> 17、如何重置mysql root密码?

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注运维)**
[外链图片转存中...(img-tMPmFdGw-1713635340349)]

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值