目录
一、引入
1.搜索算法
搜索是一个生活中十分常见的行为,比如从衣柜中找到某一件衣服。搜索同样在计算机领域有着十分重要的使用:在好友列表中找到想聊天的朋友;网站上搜索各种问题;在文件管理器中找到某个文件等等。而红黑树就是为了解决搜索问题而提出的。要说清楚红黑树,还是从最基本的搜索算法说起吧。
2. 暴力搜索
暴力搜索或者说遍历搜索,在范围内逐个查看并和目标做对比,匹配即可。
最好查找次数:1(太幸运了,第一个就是你想找的)
最坏查找次数:n (最后一个才找到)
假设每个元素被搜索的概率相同,平均搜索次数:
n
+
1
2
\frac {n+1}{2}
2n+1
(每个元素需要搜索的次数分别是1,2,3,…,n。求和是
n
(
n
+
1
)
2
\frac {n(n+1)}{2}
2n(n+1),再除以项数)
时间复杂度:O(N)
适用范围:任何存储方式,任何的数据结构,对数据没有任何要求
3. 二分查找
事实上二分查找指的是一种思想,但这里的二分查找指的是不需要任何特殊数据结构的支持的针对基本数据类型的查找,例如整型数组。
在有序的数组中,取中值,比较中值,确定新的搜索子集,重复此步骤至找到目标
最好查找次数:1(第一个中值就是目标)
最坏查找次数:
l
o
g
2
N
log_{2}N
log2N
时间复杂度:O(logN)
适用范围:顺序存储、有序的数组
4. 二叉查找树(BST)
当存储的内容很庞大时,显然我们是没有办法开辟出一个那么大的连续内存空间,因此使用数组方式管理数据就捉襟见肘。引入树这种数据结构就可以使数据分散在内存中,通过父结点的指针找到该结点。
将要搜索的数据组织成一颗中序遍历有序的二叉树,从根结点开始与目标作比较,选择进入对应子树,并继续对比直至找到目标。
最好查找次数:1(根结点就是目标)
最坏查找次数:
l
o
g
2
N
log_{2}N
log2N
时间复杂度:O(logN)
如果数据从一开始就确定下来,没有增删行为,这个树搜索起来效率还是不错的。但是某些增删行为可能导致树退化成一个链式结构,此时搜索效率就大幅降低,可能会使时间复杂度增加至O(N)
5. 二叉平衡树(AVL)
二叉平衡树就是为了解决,增删导致树的退化引起的搜索效率下降的问题。
平衡因子:平衡因子等于左子树高度减去右子树高度
当因插入或删除结点导致某一结点的平衡因子的绝对值大于1,即:失衡。就需要通过调整结点位置维持平衡。一共有四种情况即:LL、RR、LR、RL。(无论怎么旋转该树的中序遍历始终保持有序,即要保证二叉查找树的特性)
5.1 LL型
5.2 LR型
5.3 RR型
5.4 RL型
类似LR型首先转换成RR型再通过旋转使树平衡
6. 红黑树(RBT)
诚然二叉平衡树解决了二叉查找树的查找效率退化的问题,但是当数据有大量的增删操作时,AVL树有可能会反复进行平衡操作,导致大量操作耗费在“维持平衡”上,效率下降。因此引入红黑树解决严格平衡导致的性能问题。事实上红黑树只是近似平衡,不至于失衡就立即进行平衡操作。在减少平衡操作的同时,也使树近似平衡,保证了查找效率。
二、红黑树的实现
1.红黑树的定义
一种用红色和黑色标记结点的二叉自平衡查找树。
2.红黑树的性质
1.结点只能是黑色或者红色
2.根结点是黑色
3.叶子结点是不存储数据的黑色空结点
4.任何相邻的两个结点不同时为红色
5.任意结点到期可到达的叶子结点间包含数量相同的黑色结点
3.红黑树是如何做到近似平衡的
同样的结点数量,查找效率与由这些结点构成的树的高度成反比,即:树越矮,搜索效率越高。
以一颗红黑树为例,移除全部红色结点,红色结点的子节点全部接在父节点上,如图:
4.构建红黑树
4.1 插入结点
红黑树规定插入结点必须为红色。因此可以分为以下几种情况
1、红黑树为空树,插入结点为根结点
由于红黑树要求根结点为黑色,因此只需将红色变色设置为黑色即可
2、 插入结点的父结点是黑色
此种情况插入后仍然符合红黑树定义,无需调整
3、 插入结点的父结点为红色
当这种情况出现时,不符合红黑树性质要求,会破坏平衡,需要进行平衡操作。
具体有会细分为一下几种子情况
3.1 插入结点的父节的兄弟结点不存在或为黑色
(1) 插入结点与其父结点在同侧子树,即LL或RR型
此时需要进行左旋或右旋调整。图例如下:(LL 为例)
插入节点为25
调整后为:
(2) 插入结点与其父结点在不同侧子树,即LR或RL型
类似与AVL树中的操作,需要先转换为LL或RR型,再进行左旋或右旋,图例如下:
插入节点为126
调整后成为3.1 情况继续调整
3.2 插入结点的叔叔节点为红色
此种情况无需进行旋转操作,只需将父辈结点(父结点和父结点的兄弟结点),调整为黑色。祖父结点调整为红色。后将祖父结点当做是新插入的结点递归的调整该红黑树。
插入 125 节点
调整后,将140及其子树当作新的插入的节点继续调整,仍然是只改变颜色
最终为:
4.2 删除结点
1、 该结点只有一个子结点
根据红黑树性质决定,如果只有一个子结点,该子结点必定为红色且该结点必定为黑色,该情况教简单
直接令该子结点变为黑色替换该结点位置即可
2、该结点有两个子结点
2.1 右子结点不存在左孩子
删除结点,让右子结点顶替即可。因为此时右子结点就是最小的,并不复杂
2.2 右子结点存在左孩子
此时右子结点存在左孩子(或者说是左子树),就需要找到该分支下的最小值,来顶替要删除的结点的位置
方法:一路找左子树,直到不存在左子树。然后看当前结点存不存在右子结点,不存在就算了,存在的话要把当前结点的右子结点放到当前结点的父结点的左子结点位置,然后把当前结点放到最初要删除的结点的位置
2.3 左子结点不存在右孩子
和2.1类似
2.4左子结点存在右孩子
和2.2类似
3、不存在子结点
3.1 该结点为红色
删除结点不影响性质,可以直接删除
3.2 该结点为黑色
此种情况最为复杂,需要进行自底向上的递归调整。
Tips:此种情况下,必定存在兄弟结点,且兄弟结点所在子树上到达叶子结点的路径上有且只有一个黑色结点。
3.2.1 该结点的兄弟结点为红色
此时兄弟结点一定存在两个子结点且都为黑色。同时,父结点也为黑色
这里只是先旋转,然后父亲结点和祖父结点颜色互换
将其转华为兄弟结点为黑色的情况
3.2.2 该结点的兄弟结点为黑色
3.2.2.1 远端侄子为红色
删除结点、兄弟结点和父结点颜色对调,然后将父亲节点,侄子节点设置成黑色然后进行旋转操作 done
删除110节点
调整后
3.2.2.2 近端侄子为红色
调整兄弟子树:将兄弟结点和侄子结点位置颜色互换,然后进行旋转,变成情况3.2.1
删除80节点
调整后:
注意此时并未调整完成。需要回到3.2.2.1的情况,需要交换兄弟节点和父节点颜色,设置父节点和侄子节点为黑色,然后旋转。
最终结果:
3.2.2.3 不存在侄子结点
先删除该节点,然后兄弟改成红色,父亲节点改成黑色
例:删除150 节点
调整后:
三、红黑树的应用
·广泛用于C++的STL中,map和set都是用红黑树实现的.
·IO多路复用epoll的实现采用红黑树组织管理sockfd,以支持快速的增删改查.
·ngnix中,用红黑树管理timer,因为红黑树是有序的,可以很快的得到距离当前最小的定时器.
·java中TreeMap的实现.
·linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块,进程的虚拟内存区域都存储在一颗红黑树上,每个虚拟地址区域都对应红黑树的一个结点,左指针指向相邻的地址虚拟存储区域,右指针指向相邻的高地址虚拟地址空间.