红黑树
程序员文章站
2022-03-24 18:03:03
...
概述
#红黑树是自平衡的二叉搜索树,是计算机科学中的一种数据结构。
# 平衡是指所有叶子的深度基本相同(完全相等的情况并不多见,所以只能趋向于相等)
# 二叉搜索树是指,节点最多有两个儿子,且左子树中所有节点都小于右子树。
# 红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)
# 树中节点有改动时,通过调整节点顺序(旋转),重新给节点染色,使节点满足某种特殊的性质来保持平衡。
# 不是完全平衡的二叉树,但能保证搜索操作在O(log n)的时间复杂度内完成(n是树中节点总数)
# 插入、删除以及旋转、染色操作都是O(log n)的时间复杂度
# 每个节点只需要用一位(bit)保存颜色(仅为红、黑两种)属性,除此以外,红黑树不需要保存其他信息
# 红黑树与普通二叉搜索树(BST)的内存开销基本一样,不会占用太多内存
特性(约束)
1、每个节点或者是黑色,或者是红色
2、根节点是黑色
3、每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
4、如果一个节点是红色的,则它的子节点必须是黑色的。
5、从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点
定理:一棵含有n个节点的红黑树的高度至多为2log(n+1)-------可自证(https://www.cnblogs.com/skywang12345/p/3245399.html)
应用
Java集合中的TreeSet和TreeMap,C++ STL中的set、map,以及Linux虚拟内存的管理
基本操作:左旋
#对5这个节点进行左旋操作
5 8 8
/ \ --(左旋1)--> / \ --(左旋2)--> / \
3 8 5 9 5 9
/ \ / / \
7 9 3 3 7
图上是为了方便记忆画的,结果和左旋一样,但算法过程(直接修改指针指向)并不是这样。
首先左旋第一步3/5/8这几个节点逆时针旋转一个位置,这时发现7这个节点多余了,这时我们把7拿出来就变成了中间那个二叉树
紧接着,我们把7当作一个新的节点插入二叉树,发现7比8小但比5大,于是7就变成了5的右节点。
基本操作:右旋
#对5这个节点进行右旋操作
5 3 3
/ \ --(右旋1)--> / \ --(右旋2)--> / \
3 8 2 5 2 5
/ \ \ / \
2 4 8 4 8
图上是为了方便记忆画的,结果和左旋一样,但算法过程(直接修改指针指向)并不是这样。
首先左旋第一步3/5/8这几个节点顺时针旋转一个位置,这时发现4这个节点多余了,这时我们把4拿出来就变成了中间那个二叉树
紧接着,我们把4当作一个新的节点插入二叉树,发现4比4大但比5小,于是7就变成了5的左节点。
新增新节点
1、新增节点时按照二叉查找树找到位置插入并把节点着红色
#将插入的节点着色为红色,不会影响黑色高度
将插入节点着色为"红色"之后,不会违背"特性(5)"。那它到底会违背哪些特性呢?
对于"特性(1)",显然不会违背了。因为我们已经将它涂成红色了。
对于"特性(2)",显然也不会违背。在第一步中,我们是将红黑树当作二叉查找树,然后执行的插入操作。而根据二叉查找数的特点,插入操作不会改变根节点。所以,根节点仍然是黑色。
对于"特性(3)",显然不会违背了。这里的叶子节点是指的空叶子节点,插入非空节点并不会对它们造成影响。
对于"特性(4)",是有可能违背的!
那接下来,想办法使之"满足特性(4)",就可以将树重新构造成红黑树了
2、通过旋转和着色使之重新成为一颗红黑树
#情况说明
1)首次新增,即被插入的节点是根节点,直接涂为黑色(特性一:根节点为黑色)
2)被插入的节点的父节点是黑色
# 什么也不需要做。节点被插入后,仍然是红黑树(红色不影响黑色高度)
3)被插入的节点的父节点是红色
3.1)父节点为红色、祖父节点一定为黑色、则叔叔节点可能是红色、黑色、空(黑色)
3.2)根据情况不同,新增的节点可能是左子节点、右子节点
3.3)如果叔叔节点是红色右节点,新增节点不需考虑左右节点:将父节点、叔叔节点着黑色、祖父节点着红色
#判断修正前后是否正确,可以分别比较修正前后左子树黑色高度、右子树黑色高度,左右子树黑色高度可以不等,因为示例图只画了部分节点,并不是完整的红黑树
# g(b) g(r)
# / \ / \
# f(r) u(r) --(重新着色)---> f(b) u(b)
# / /
#s(r) s(r)
# g:祖父节点 f:父节点 u:叔叔节点 s:子节点(新增节点) b:黑色 r:红色
# 着色完成后可能会导致祖父节点违反红黑树特性,这时我们要把祖父节点当作当前节点继续进行分析。
3.4)如果叔叔节点是红色左节点,新增节点不需考虑左右节点:将父节点、叔叔节点着黑色、祖父节点着红色
# g(b) g(r)
# / \ / \
# u(r) f(r) --(重新着色)---> u(b) f(b)
# / /
# s(r) s(r)
# g:祖父节点 f:父节点 u:叔叔节点 s:子节点(新增节点) b:黑色 r:红色
# 着色完成后可能会导致祖父节点违反红黑树特性,这时我们要把祖父节点当作当前节点继续进行分析。
3.5)如果叔叔节点是黑色右节点、新增节点为左节点:旋转、把原父节点着黑色、原祖父节点着红色
# g(b) f(r) f(b)
# / \ / \ / \
# f(r) u(b) --(g右旋转)---> s(r) g(b) --(重新着色)---> s(r) g(r)
# / \ \
#s(r) u(b) u(b)
# g:祖父节点 f:父节点 u:叔叔节点 s:子节点(新增节点) b:黑色 r:红色
3.6)如果叔叔节点是黑色右节点、新增节点为右节点:旋转、把原父节点着黑色、原祖父节点着红色、新增节点着黑色
# g(b) g(b)
# / \ / \
# f(r) u(b) --(f左旋转)---> s(r) u(b) (到此得到3.5初始图像)
# \ /
# s(r) f(r)
# g:祖父节点 f:父节点 u:叔叔节点 s:子节点(新增节点) b:黑色 r:红色
3.7)如果叔叔节点是黑色左节点、新增节点为右节点:旋转、把原父节点着黑色、原祖父节点着红色
# g(b) f(r) f(b)
# / \ / \ / \
# u(b) f(r) --(g左旋转)---> g(b) s(r) --(重新着色)---> g(r) s(r)
# \ / /
# s(r) u(b) u(b)
# g:祖父节点 f:父节点 u:叔叔节点 s:子节点(新增节点) b:黑色 r:红色
3.8)如果叔叔节点是黑色左节点、新增节点为左节点:旋转、把原父节点着黑色、原祖父节点着红色
# g(b) g(b)
# / \ / \
# u(b) f(r) --(f右旋转)---> u(b) s(r) (到此得到3.7初始图像)
# / \
# s(r) f(r)
# g:祖父节点 f:父节点 u:叔叔节点 s:子节点(新增节点) b:黑色 r:红色
删除节点
1、删除节点
1)被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就OK了。(删除节点为黑色时需要修正树)
2)被删除节点只有一个儿子,删除该节点,并用该节点的唯一子节点顶替它的位置(删除节点为黑色时需要修正树)
3)被删除节点有两个儿子
3.1)找出后继节点;然后把“后继节点的内容”复制给“该节点的内容”;之后,删除“它的后继节点”
3.2)后继结点最多只有一个非空子叶节点(如果有两个那后继结点就不该是它了),按1)、2)步删除就行
2、修正二叉树
2.1)删除节点后,可能导致根节点不是黑色、父子节点都是红色、黑色高度不相等
#对于case1、case2(黑色叶节点、被删除节点为黑色且有一个儿子节点),这里被删节点我们设为Z节点,取代Z节点位置的节点设为Y节点。
#删除一个黑色节点,为了保持黑高不变,我们假设Y节点可以存储两种颜色(原来的颜色+黑色),如果原来颜色为红色,则把Y节点染成黑色即可保持黑高不变。如果原来颜色为黑色,则Y节点变成(黑+黑)节点,需要进一步分析。(Y节点可为空)
#对于case3(Z节点有两个儿子节点),找到后继节点Y节点,把Y节点内容复制到Z节点并保留Z节点颜色(黑高不变),然后删除Y节点。
#删除Y节点:把Y节点看出Z'节点,Y节点的后继节点看做Y',则此时就变成case1、case2了。
2.2)对于Y节点取代Z节点后,我们把Y节点设为X节点,当X为黑+黑时,修正红黑树有五种情况
#情况一:X节点为根节点,此时X只需保留一个黑色即可保证黑高相等
#情况二:X节点不是根节点,且X的兄弟节点是红色(即父节点不可能为红色)。
#情况三:X节点不是根节点,X的兄弟节点是黑色,X的兄弟节点的两个孩子都是黑色,父节点任意颜色
#情况四:X节点不是根节点,X的兄弟节点是黑色;X的兄弟节点的左孩子是红色,右孩子是黑色的
#情况五:X节点不是根节点,X的兄弟节点是黑色;X的兄弟节点的右孩子是红色的,X的兄弟节点的左孩子任意颜色
2.3)情况二:X节点不是根节点,且X的兄弟节点是红色(即父节点不可能为红色)
# f(b) b(r) b(r)
# / \ / \ / \
# x(b+b) b(r) ---(f左旋)--> f(b) br(b) ---(黑色上移)--> f(b+b) br(b)
# / \ / \ / \
# bl(b) br(b) x(b+b) bl(b) x(b) bl(b)
#最后一遍颜色上移后把f(b+b)看做X节点,此时我们可以发现情况一会变成情况二、情况三或情况四
2.4)情况三:X节点不是根节点,X的兄弟节点是黑色,X的兄弟节点的两个孩子都是黑色,父节点任意颜色
# f(b/r) f(r+b/b+b)
# / \ / \
# x(b+b) b(b) ---(黑色上移)--> x(b) b(b)
# / \ / \
# bl(b) br(b) bl(b) br(b)
#黑色上移后f节点如果是r+b则直接保留黑色即可保证黑高相等;如果f节点是b+b则把f节点看做X节点继续往上分析
2.5)情况四:X节点不是根节点,X的兄弟节点是黑色;X的兄弟节点的左孩子是红色,右孩子是黑色的
# f(b/r) f(b/r) f(b/r)
# / \ / \ / \
# x(b+b) b(b) ---(b右旋)--> x(b+b) bl(r) ---(重新染色)--> x(b+b) bl(b)
# / \ \ \
# bl(r) br(b) b(b) bl(r)
# \ \
# br(b) br(b)
#将X节点的兄弟节点向右旋转后重新染色,此时我们看到X节点兄弟节点为黑色,而兄弟节点的右孩子为红色,变成了情况五
2.6)情况五:X节点不是根节点,X的兄弟节点是黑色;X的兄弟节点的右孩子是红色的,X的兄弟节点的左孩子任意颜色
# f(b/r) b(b) b(b/r)
# / \ / \ / \
# x(b+b) b(b) ---(f左旋)--> f(b/r) br(r) ---(重新染色)--> f(b) br(b)
# / \ / \ / \
# bl(b/r) br(r) x(b+b) bl(b/r) x(b) bl(b/r)
#将f节点向左旋转得到中间图像,交换b、f节点颜色,分析到达各个叶子节点的前后黑高,发现到达x节点的黑高比原来多一,而到达br节点的黑高比原来少一,所以x节点只保留一个黑色、把br节点染成黑色
#
上一篇: 原生JS简单的无缝自动轮播