1.1 随处可见的红黑树
本博客总结自零声教育的课程
本博客将和大家一起学习红黑树的相关知识,包括红黑树的性质、红黑树的左旋和右旋、红黑树增加、删除结点的原理,并在附有代码实现和讨论
1、知识树
2、红黑树的性质(3)
① 每个节点是红的或者黑的,根节点、叶子结点是黑色的
② 如果结点是红的,则两个儿子都是黑的(意思是红黑树中不能出现两个红色的结点相邻)
③ 对每个结点,从该节点到其子孙结点的所有路径上包含相同数目的黑结点(该条性质用来控制红黑树的平衡性,不是平衡所有结点,只是平衡的黑色结点的高度,平衡黑高)
(默认叶子结点都是黑色的)
(默认叶子结点都是黑色的,上图中没有画出叶子结点)
3、红黑树的使用(2)
★红黑树使用分两种情况:
①key,value:通过key查找对应的value
②利用红黑树的中序遍历,红黑树的中序遍历是顺序的
4、红黑树用在哪里(举例)
(1)map是红黑树的封装
(2)ngix
(3)定时器
(4)进程调度的cfs:用红黑树存储进程的集合,把调度的时间作为key值,每次从时间最小的值开始调度,即从红黑树的左下角开始查找
(5)内存管理:红黑树的平衡性使得我们能够更好地进行查找;表述一块内存有两种方法:①起始位置 + 长度 ②起始位置 + 结束位置,内存管理用红黑树进行存储的话,key的表示就是这两种方式
5、判断是否为叶子节点的方法(2)
① 判断左右子树是否同时为空
② 定义【一个公共的叶子结点】,看结点是否为这个公共的叶子结点,就能判断是否到达了叶子结点
6、红黑树的旋转
红黑树的旋转分为左旋和右旋:
左旋、右旋是我们实现红黑树的过程中的一个基础的原语操作,不要关注颜色
(1)左旋
左旋(逆时针旋转):x原本是y的父节点,现在将y变为x的父节点
★三步,每步对应修改一个树枝的两个方向,修改6个指针(因为每一个树枝对应两个方向的指针):
①将x的右子树指向y的左子树;若y的左子树不为叶子结点,则将y的左子树的父节点改为x的右子树
②将y的parent指向x的parent(判断x是否为根节点,即x的parent是否为叶子结点);原本x的parent的左子树或右子树指针指向y(先要判断x为x的parent的左子树还是右子树)
③将x的parent的指针指向y;将y的左子树的指针指向x
(2)右旋
右旋:复制上述代码,①将左右互换,②将x和y互换 就实现了,逻辑不需要修改
1、将y的左子树指向x的右子树,若x的右子树不为空则将x的右子树指向y
2、将x的parent指向y的parent,将y的parent指向x
3、将x的右子树指向y,y的parent指向x
7、操作红黑树结点(增删)的分情况讨论、原理和代码实现
(1)添加一个红黑树结点
如何插入一个结点:★新增加的一个结点,一定是加到非底层的叶子结点处
举例:若加一个结点,key为500,则加到图中红笔处:
在红黑树中插入一个结点,分为以下几种情况:
★★★在分情况讨论的时候一定要牢记两个点:
① 插入当前结点之前就是一棵红黑树
② 只讨论所插入的结点z是红色的
解读上图:
1、插入z结点,若z为黑色,则比较麻烦,建议直接看代码
2、插入z结点,若z为红色,则要z要插入的位置的父节点的颜色
(1)若父结点是黑色,则不需要调整,直接插入即可
(2)父节点是红色,祖父结点只能是黑色,此时要再按照叔父结点的颜色分为两类(知道叔父结点的颜色是为了方便做旋转):
① 若叔父结点是红色,则只有一种情况
② 若叔父结点是黑色,则还需要讨论两种情况:
a. 插入结点是父节点的左子树
b. 插入结点是父节点的右子树
注意:【若叔父结点是黑色的且下面还有叶子结点】这种情况不成立,因为父节点是红色的,所以若叔父结点是黑色的则只能是叶子结点
a. 若插入结点是父节点的右子树:先左旋,再修改颜色,再右旋
举例:
1)左旋344,将350放到344的位置上
2)右旋359,变成350、344、359(中左右)
b. 若插入结点是父节点的左子树:先修改颜色,再右旋
举例:
b. 叔父结点是红色的,此时不用看插入结点是父节点的左子树还是右子树,都是一样的:将父节点和叔父结点都变黑,将祖父结点变红即可,这样变换后插入结点的父节点就为黑色了,这时将红色的插入结点直接插入即可;变换颜色后,祖父结点388变成了红色,为了不让祖父结点和祖父的父节点颜色冲突,要将插入z结点变成祖父结点(将祖父结点赋值给z结点并递归调用),递归调用函数向上判断:
★红黑树左右子树高度差最多不超过2倍(2n-1)
举例:
若左子树高度是5,则右子树高度最多不超过9
(2)删除一个红黑树结点
有3个结点参与删除:
① 删除的key所对应的结点z
② 要删除的结点的后继结点y(注意要删除的结点不是key对应的结点,而是key对应的结点的后继节点)
③ 轴心结点x(后面需要调整的结点)
★★★过程:将y的值复制到z结点上,删除y结点,再调整x结点;z是要被覆盖的结点,y才是真正要被删除的结点
注意,若删除的y结点是黑色的,才需要调整;若y为红色则不需要调整
举例:这种情况下,删除key为172的结点(注意删除的不是key为172的结点,而是该节点的后继节点)
①删除的key所对应的结点z:key为172对应的结点
②所要删除的结点y(注意要删除的结点不是key对应的结点,而是key对应的结点的后继节点):key为206所对应的结点
③轴心结点x(后面需要调整的结点):key对224所对应的结点
★★★过程:将y(206对应的结点)的值复制到z结点上,删除y结点,再调整x结点
红黑树结点的删除的分情况讨论比较麻烦,建议直接看删除红黑树结点的代码;面试手撕红黑树结点的可能性不大,手撕红黑树代码主要能够帮助我们在以后的工作中修改底层源码。
8、代码实现
上述详细介绍了红黑树的相关原理,红黑树相关的代码均在我的github中,代码中每条语句都有详细的注释,以下是我的github代码链接,恳请大家批评指正!
GitHub代码
我自己实现的rbtree.c代码在rbtree文件夹中
课程中的参考代码在0voice文件夹的rbtree文件夹中