⼆叉搜索树
基本概念
⼆叉搜索树(也称⼆叉排序树,简称BST)是⼀颗空树,或者是具有以下特性的⼆叉树;
- 若左⼦树⾮空,则左⼦树上所有结点的值均⼩于根结点的值。
- 若右⼦树⾮空,则右⼦树上所有结点的值均⼤于根结点的值。
- 左、右⼦树也分别是⼀颗⼆叉搜索树。
也就是左⼦树结点值<根结点值<右⼦树结点值。
相较于堆,⼆叉搜索树是⼤⼩关系更为严格的数据结构。但是并不需要必须是⼀棵完全⼆叉树,也就是树的形态是任意的。如下图所⽰,都是⼆叉搜索树:
![![[Pasted image 20250320214314.png]]](https://i-blog.csdnimg.cn/direct/de23e68fd7cf4c14af17fc2f8f12f35c.png)
![![[Pasted image 20250320214647.png]]](https://i-blog.csdnimg.cn/direct/3a2e7446fb5e4a13912f706a921a0ac0.png)
根据⼆叉树的定义,左⼦树结点值<根结点值<右⼦树结点值,所以对⼆叉搜索树进⾏中序遍历,可以得到⼀个递增的有序序列。
构造⼀颗⼆叉搜索树的⽬的,其实并不是为了排序,⽽是为了提⾼查找和插⼊删除关键字的速度
查找操作
⼆叉搜索树的查找是从根结点开始,沿某个分⽀逐层向下⽐较的过程,若⼆叉搜索树⾮空,先将给定值与根结点的关键字⽐较,若相等,则查找成功;若不等,如果⼩于根结点的关键字,则在根结点的左⼦树上查找,否则在根结点的右⼦树上查找。⽽这也是⼀个递归的过程。
例如:在下⾯的⼆叉搜索树中,查找30 ,51
![![[Pasted image 20250320214744.png]]](https://i-blog.csdnimg.cn/direct/e246a1e257814b06bd67c21116e35aa7.png)
时间复杂度:
最坏情况下会从根节点开始,查找到叶⼦结点。因此时间复杂度是和树的⾼度有关的,⽽树⾼最差会变成⼀条单链表,因此时间复杂度为O(N)
插⼊操作
⼆叉搜索树作为⼀种动态树表,其特点是树的结构通常不是⼀次⽣成的,⽽是在查找的过程的过程中,当树中不存在关键字值等于给定值的结点时再进⾏插⼊的。插⼊的结点⼀定是⼀个新添加的叶结点,且是查找失败时的查找路径上访问的最后⼀个结点的左孩⼦或右孩⼦。
若原⼆叉树为空树,则直接插⼊结点;否则根据⼆叉搜索树的特性,将插⼊的关键字 key 与根结点对⽐,若关键字 key ⼩于根结点值,则插⼊到左⼦树,若关键字 key ⼤于根结点值,则插⼊到右⼦树。例如在序列[18, 50, 42, 63, 74]中依次插⼊25 与53
![![[Pasted image 20250320215000.png]]](https://i-blog.csdnimg.cn/direct/611f0476b603471daa3b92fd068e5da9.png)
时间复杂度:
插⼊与查找的过程⼀致,因此时间复杂度为O(N)
构造BST树
⼆叉搜索树的构造就是不断向原来的树中插⼊新的结点即可
案例⼀:根据序列a = {51, 68, 59, 27, 25, 33, 75, 70} ,构造⼀棵⼆叉排序树
![![[Pasted image 20250320215249.png]]](https://i-blog.csdnimg.cn/direct/6dc4d34dafeb427eb99fca89b25c6192.png)
案例⼆:根据序列a = {25, 27, 33, 59, 75, 51, 70, 68} ,构造⼀棵⼆叉排序树
![![[Pasted image 20250320215406.png]]](https://i-blog.csdnimg.cn/direct/2727284ca9b6477286c906306ef3f2c6.png)
由此可⻅,虽然结点的值都相同,不同的构造顺序会有产⽣不同的⼆叉搜索树,会影响查找和插⼊的效率。并且,构造序列越有序,⼆叉搜索树的查找效率越低
删除操作
对于⼆叉搜索树的删除,就不是那么容易了,我们不能因为删除了结点,⽽让这棵树变得不满⾜⼆叉搜索树的特性,所以删除需要考虑多种情况
-
若被删除结点是叶⼦结点,则直接删除
![![[Pasted image 20250320215504.png]]](https://i-blog.csdnimg.cn/direct/962437873edc4fac8118a1fb21a6c686.png)
-
若被删除结点x只有⼀颗左⼦树或右⼦树,则让x的⼦树成为x⽗结点的⼦树,替代x的位置
![![[Pasted image 20250320215618.png]]](https://i-blog.csdnimg.cn/direct/0051d7759d7949d1af55503690dbb9ec.png)
-
若结点x既有左⼦树,⼜有右⼦树。两种策略
a. 令x的直接后继替代x ,然后从⼆叉搜索树中删去这个直接后继;
b. 令x的直接前驱替代x ,然后从⼆叉搜索树中删去这个直接前驱。
案例一:删除50这个结点,并⽤直接前驱来替代
![![[Pasted image 20250320215843.png]]](https://i-blog.csdnimg.cn/direct/e7b777fc36ac4556a729f880c2d04134.png)
案例⼆:删除50这个结点,并⽤直接后继来替代
![![[Pasted image 20250320215947.png]]](https://i-blog.csdnimg.cn/direct/53b0a298c19642cda304d62f135ab995.png)
时间复杂度:
查找前驱和后继的操作,最差也会遍历整个⼆叉树,因此时间复杂度为O(N)
平衡⼆叉树
在某些特定的情况下,⼆叉搜索树是会退化成单链表的,并且各种操作的效率也会明显的下降,因此我们需要⼀些特别的⼿段保证这个⼆叉搜索树的“平衡”,进⽽保证各种操作的效率。这就是我们接下来要学习的平衡⼆叉树
基本概念
为了保证⼆叉搜索树的性能,规定在插⼊和删除结点时,要保证任意结点的左、⼦树⾼度差的绝对值不超过1 ,这样的⼆叉树称为平衡⼆叉树(简称AVL树)。
其中结点左⼦树与右⼦树的⾼度差定义为该结点的平衡因⼦(⼀般是左⼦树的⾼度减去右⼦树的⾼度。当然,反过来也是可以的)。由此可⻅,平衡⼆叉树中,每⼀个结点的平衡因⼦只可能是-1,0或1。
如下图所⽰:结点上⽅的数字表⽰平衡因⼦。左图是⼀棵平衡⼆叉树,右图不是⼀棵平衡⼆叉树
![![[Pasted image 20250320220316.png]]](https://i-blog.csdnimg.cn/direct/03c2b8c3414b47b69aa886b486631407.png)
查找操作
与⼆叉搜索树的查找⼀样:从根结点开始,沿某个分⽀逐层向下⽐较的过程,若⾮空,先将给定值与根结点的关键字⽐较,若相等,则查找成功;若不等,如果⼩于根结点的关键字,则在根结点的左⼦树上查找,否则在根结点的右⼦树上查找
时间复杂度:
由于平衡⼆叉树会限制树的⾼度不会过⾼,趋近于log n级别,因此时间复杂度为O(log N)
插⼊操作
可以先按照⼆叉搜索树的插⼊算法插⼊⼀个结点,那么在插⼊过程途经的结点,都有可能受到影响。
如果这些结点的平衡因⼦的绝对值⼤于1,此时就应该想办法调整这棵树的结构,使其重新变成平衡⼆叉树。
调整之前,先认识⼀个概念:最⼩不平衡⼦树。
在⼆叉搜索树中插⼊新结点之后,插⼊路径的点中,可能存在很多平衡因⼦的绝对值⼤于1的,此时找到距离插⼊结点最近的不平衡的点,以这个点为根的⼦树就是最⼩不平衡⼦树
![![[Pasted image 20250320220729.png]]](https://i-blog.csdnimg.cn/direct/a9be5ed3acc648a7b9f4f77854130531.png)
插⼊80之后,会导致52,58结点失衡,其中距离80最近的58结点为根的⼦树,就是最⼩不平衡⼦树。
可以发现,仅需让最⼩不平衡⼦树平衡,所有结点就都平衡了。
⾄于为什么调整这⼀棵⼦树就可以让所有结点平衡?我们可以感性理解⼀下:
- 本来整棵树就是平衡⼆叉树,如果来了⼀个结点导致失衡,那么失衡结点的平衡因⼦只能是-2或者2;
- 当我们把最⼩平衡⼦树调整平衡之后,那么这棵⼦树的⾼度就会减1 ,向上传递的过程中,会让整个路径⾥⾯的平衡因⼦都向0靠近⼀位,原来的2会变成1,原来的-2会变成-1,整棵树就变得平衡了。
最⼩不平衡⼦树的出现可以细分成4中情况,因此调整策略也会分4中情况讨论。为了⽅便叙述,设最⼩不平衡⼦树的根节点为T
LL型-右单旋
LL表⽰:新结点由于插⼊在T结点的左孩⼦(L)的左⼦树(LL)中,从⽽导致失衡。如下图所⽰
![![[Pasted image 20250320221330.png]]](https://i-blog.csdnimg.cn/direct/5fd124bb197c424f96692d0a17c0e102.png)
此时需要将L右旋:
- 将结点L向右上旋转代替结点T作为根结点;
- 将节点T向右下旋转作为结点L的右⼦树的根结点;
- 结点L的原右⼦树(LR)则作为结点T的左⼦树。
旋转之后,依旧满⾜平衡⼆叉树的特性:LL < L < LR < T < R
如下图
![![[Pasted image 20250320221647.png]]](https://i-blog.csdnimg.cn/direct/7d3074267456476b9482b52b827abbf7.png)
RR型-左单旋
RR表⽰:新结点由于插⼊在T结点的右孩⼦®的右⼦树(RR)中,从⽽导致失衡。如下图所⽰
![![[Pasted image 20250320221838.png]]](https://i-blog.csdnimg.cn/direct/ea9991f335304b9e82188fb225ce3034.png)
此时需要⼀次向左的旋转操作,将R左旋:
- 将结点R向左上旋转代替结点T作为根结点;
- 将节点T向左下旋转作为结点R的左⼦树的根结点;
- 结点R的原左⼦树(RL)则作为结点T的右⼦树。
如下图
![![[Pasted image 20250320222055.png]]](https://i-blog.csdnimg.cn/direct/18c8f3860510482b85cc311453792e27.png)
LR型-左右双旋
LR表⽰:新结点由于插⼊在T结点的左孩⼦(L)的右⼦树(LR)中,从⽽导致失衡。如下图所⽰
![![[Pasted image 20250320222725.png]]](https://i-blog.csdnimg.cn/direct/79ab4dff57aa4b49a4d9c45b50c0b153.png)
x有可能在LRR的下面,不影响结果
此时需要两次旋转操作,先将LR左旋,再将LR右旋。
将LR左旋:
- 将结点LR向左上旋转代替结点L作为根结点;
- 将节点L向左下旋转作为结点LR的左⼦树的根结点;
- 结点LR的原左⼦树(LRL)则作为结点L的右⼦树。
将LR右旋: - 将结点LR向右上旋转代替结点T作为根结点;
- 将节点T向右下旋转作为结点LR的右⼦树的根结点;
- 结点LR的原右⼦树(LRR)则作为结点T的左⼦树。
如下图
![![[Pasted image 20250320223057.png]]](https://i-blog.csdnimg.cn/direct/631846e8299f4a58b5544ce267991397.png)
RL型-右左双旋
RL表⽰:新结点由于插⼊在T结点的右孩⼦®的左⼦树(RL)中,从⽽导致失衡。如下图所⽰
![![[Pasted image 20250320223338.png]]](https://i-blog.csdnimg.cn/direct/e3ddf2b297da40bea3267a1078167585.png)
此时需要两次旋转操作,先将RL右旋,再将RL左旋。
将RL右旋:
- 将结点RL向右上旋转代替结点R作为根结点;
- 将节点R向右下旋转作为结点RL的右⼦树的根结点;
- 结点RL的原右⼦树(RLR)则作为结点R的右⼦树。
将RL左旋: - 将结点RL向左上旋转代替结点T作为根结点;
- 将节点T向左下旋转作为结点RL的左⼦树的根结点;
- 结点RL的原左⼦树(RLL)则作为结点T的右⼦树。
如下图
![![[Pasted image 20250320223830.png]]](https://i-blog.csdnimg.cn/direct/5c929d9603264972a8c53abb5172102d.png)
插⼊操作的时间复杂度:
旋转操作仅需修改指针,因此最⼤的时间开销就是先把结点插⼊到空结点的位置,时间复杂度和查找⼀致,因此为O(log N)
构造AVL树
平衡⼆叉树的构造,就是不断向树中插⼊新的结点
案例:根据序列a = {15, 6, 10, 17, 11, 13, 9, 20, 16, 22} ,构造⼀棵⼆叉排序树
![![[Pasted image 20250320224233.png]]](https://i-blog.csdnimg.cn/direct/ca4ad6f88e7f4fd49c37f7ea32f149f8.png)
![![[Pasted image 20250320224720.png]]](https://i-blog.csdnimg.cn/direct/d44933c1e7d74901b62813bca303afd1.png)
![![[Pasted image 20250321103532.png]]](https://i-blog.csdnimg.cn/direct/5985de9ce7ca41aa9afc4974df1a67f8.png)
![![[Pasted image 20250321103856.png]]](https://i-blog.csdnimg.cn/direct/817933ac3f90474aac4d26189b10fa5c.png)
删除操作
与插⼊操作的思想类似,都是先按照⼆叉搜索树的形式操作,然后想办法使其平衡。具体步骤:
- ⽤⼆叉搜索树的⽅法对结点w执⾏删除操作。
- 从结点w开始,向上找到第⼀个不平衡的结点T(即最⼩不平衡⼦树);X为结点T的⾼度最⾼的孩⼦结点,Y是结点X的⾼度最⾼的孩⼦结点
- 然后对以Y为根的⼦树进⾏平衡调整,调整⽅法就根据Y的位置情况。其中T、X和Y可能的位置
有4种情况:
- X是T的左孩⼦,Y是X的左孩⼦(LL,X右单旋转)
- X是T的左孩⼦,Y是X的右孩⼦(LR,Y先左后右双旋转)
- X是T的右孩⼦,Y是X的右孩⼦(RR,X左单旋转)
- X是T的右孩⼦,Y是X的左孩⼦(RL,Y先右后左双旋转)
- 调整之后,继续向上找下⼀个不平衡的⼦树,然后重复3操作,直到找不到为⽌。
也就是说,删除操作可能导致从根节点到删除结点的很多结点都失衡,⼀次调整是不能保证全部结点都平衡的。需要不断向上寻找,遇到⼀个不平衡的就去调整,⼀直到根节点为⽌
时间复杂度:
由于可能会调整很多次,最差情况下会从叶⼦结点开始向上到根,整个过程遍历⼀个树⾼,因此时间复杂度为O(log N)
红⿊树
基本概念
红⿊树(简称RBT),也是⼀棵⼆叉搜索树。它是在搜索树的基础上,使每个结点上增加⼀个存储位表⽰结点的颜⾊,可以是Red或者Black,通过对任意⼀条从根到叶⼦的路径上各个结点着⾊⽅式的限制,确保没有⼀条路径会⽐其他路径⻓出2倍,因⽽是⼀棵接近平衡的⼆叉搜索树。
红⿊树相对于AVL树来说,牺牲了部分平衡性以换取插⼊/删除操作时少量的旋转操作,整体来说性能要优于AVL树
红⿊树的规则
在⼀棵红⿊树中,需要满⾜下⾯⼏条规则,在每次插⼊和删除之后,都应该让红⿊树满⾜下⾯的规则:
- 每个结点要么是红⾊要么是⿊⾊;
- 根节点和叶⼦结点(这⾥的叶⼦结点不是常规意义上的叶⼦结点,⽽是空结点,如下图中的NIL)是⿊⾊的;
- 如果⼀个结点是红⾊的,则它的两个孩⼦结点必须是⿊⾊的,也就是说任意⼀条路径不会有连续的红⾊结点;
- 对于任意⼀个结点,从该结点到其所有叶⼦的路径上,均包含相同数量的⿊⾊结点。
例如下⾯的红⿊树
![![[Pasted image 20250321123212.png]]](https://i-blog.csdnimg.cn/direct/81c1c7e0a85c48e89b2cf0cbba681614.png)
红⿊树的性质
根据红⿊树的规则,我们可以得出红⿊树的两个重要性质:
-
从根结点到叶结点的最⻓路径不⼤于最短路径的2倍。
⽐较容易证明,这⾥就不⽤严谨的数学⽅式了,直接看⼀个具体的例⼦感受感受。
如下图所⽰,最短路径最短有3个结点,全是⿊⾊。因为不能出现连续的红⾊,所以想要最⻓,必须得是红⿊相间的形式,最⻓就是5,不会超过最短路径的2倍
![![[Pasted image 20250321111318.png]]](https://i-blog.csdnimg.cn/direct/379e88f127bf48c9a2c4ba7993c937a7.png)
-
有n个结点的红⿊树,⾼度h≤2log2(n+1)h \le 2\log_{2}(n+1)h≤2log2(n+1),也就是说查找时间复杂度为O(log N)
红⿊树的查找
与⼆叉搜索树的查找⼀样:从根结点开始,沿某个分⽀逐层向下⽐较的过程,若⾮空,先将给定值与根结点的关键字⽐较,若相等,则查找成功;若不等,如果⼩于根结点的关键字,则在根结点的左⼦树上查找,否则在根结点的右⼦树上查找
时间复杂度:
由于平衡⼆叉树会限制树的⾼度不会过⾼,趋近于log n级别,因此时间复杂度为O(log N)
红⿊树的插⼊
第⼀步,也是先按照⼆叉搜索树的插⼊⽅式插⼊新的结点。接下来思考⼀个⼩问题:新插⼊的结点染成红⾊还是⿊的好呢?
答:明显是红⾊较好。
如果染成⿊⾊,⼀定会让这条路径上的⿊⾊结点数量增多,那么就需要调整所有从根节点到叶⼦结点的路径,使其重新符合红⿊树的特性。不仅每次插⼊都要调整,⽽且调整的规模还⾮常庞⼤。
但是,如何染成红⾊,有可能就不需要调整。如果需要调整,也只会破坏根节点不能为红,以及不能出现连续的红⾊结点这两个规则。
因此红⿊树的插⼊过程⼤致为:
- 按照⼆叉搜索树的插⼊⽅式插⼊新的结点;
- 默认该点是红⾊,如果破坏了红⿊树的规则,然后就分情况讨论。
接下来,就详细讨论插⼊新结点之后会遇到的所有情况,以及每种情况需要如何调整。
为了后续叙述⽅便,标记新插⼊的结点为c(cur),⽗结点为p(parent),⽗亲的⽗结点为g(grandfather),⽗结点的兄弟为u(uncle)
![![[Pasted image 20250321132455.png]]](https://i-blog.csdnimg.cn/direct/25e66a9bb848468ba79f7d96b2b4651e.png)
情况⼀:插⼊的是根节点
这是第⼀次插⼊结点,直接将结点的颜⾊变成⿊⾊即可
![![[Pasted image 20250321132537.png]]](https://i-blog.csdnimg.cn/direct/154262b2d64440c38baaa3cb0e299574.png)
情况⼆:叔叔是红⾊
![![[Pasted image 20250321133630.png]]](https://i-blog.csdnimg.cn/direct/df07615cdf2741fb91a0954ba45ec543.png)
这种情况下,不需要旋转,只需要不断变⾊即可。具体的策略是:
- ⽗亲、叔叔和爷爷同时变⾊,然后将爷爷看做新插⼊的结点,继续向上判断。
-
p和u变成⿊⾊,g变成红⾊。这样就相当于g所在的⼦树增加⼀个⿊⾊结点,⼜减少⼀个⿊⾊结点,整个路径⿊⾊结点的数量就不会改变
![![[Pasted image 20250321133911.png]]](https://i-blog.csdnimg.cn/direct/e669ec2da953472f9fdbc386ba63e926.png)
-
但是,由于g变成红⾊,有可能g的⽗亲也是红⾊,或者g是⼀个根,此时就需要把g当成新插⼊的结点,继续判断
![![[Pasted image 20250321134850.png]]](https://i-blog.csdnimg.cn/direct/d69159588eae4779b2018194b8beb39d.png)
情况三:叔叔是⿊⾊
![![[Pasted image 20250321135144.png]]](https://i-blog.csdnimg.cn/direct/aab2ce14234d428682653b8057cb869f.png)
这种情况需要继续分类讨论,根据祖⽗、⽗亲、新结点三者的位置,分情况旋转+变⾊
LL型-右单旋+⽗爷变⾊
如果⽗亲和新结点的位置关系相对于爷爷呈现:新结点是爷爷的左孩⼦的左孩⼦,仅需两步:
- 右旋⽗亲结点;
- 然后将⽗亲和爷爷变⾊
![![[Pasted image 20250321135625.png]]](https://i-blog.csdnimg.cn/direct/346a82662c94428cabdd3f6ea1ddfed0.png)
RR型-左单旋+⽗爷变⾊
如果⽗亲和新结点的位置关系相对于爷爷呈现:新结点是爷爷的右孩⼦的右孩⼦,仅需两步:
- 左旋⽗亲结点;
- 然后将⽗亲和爷爷变⾊
![![[Pasted image 20250321141045.png]]](https://i-blog.csdnimg.cn/direct/bd23a937e2434ad0b0c0e48cc241e625.png)
LR型-左右双旋+⼉爷变⾊
如果⽗亲和新结点的位置关系相对于爷爷呈现:新结点是爷爷的左孩⼦的右孩⼦,仅需两步:
- 新结点先左旋,再右旋;
- 然后将新结点和爷爷结点变⾊
![![[Pasted image 20250321144811.png]]](https://i-blog.csdnimg.cn/direct/cfecd4b465db443f8d27c1d187578328.png)
RL型-右左双旋+⼉爷变⾊
如果⽗亲和新结点的位置关系相对于爷爷呈现:新结点是爷爷的右孩⼦的左孩⼦,仅需两步:
- 新结点先右旋,再左旋;
- 然后将新结点和爷爷结点变⾊
![![[Pasted image 20250321150245.png]]](https://i-blog.csdnimg.cn/direct/1f951852a7b540ee81d319e87a948f4e.png)
红⿊树的构造
红⿊树的构造,就是不断向红⿊树中插⼊新的结点
案例:根据序列a = {18, 19, 25, 40, 30, 11, 9, 1, 6, 4} ,构造⼀棵红⿊树
![![[Pasted image 20250321150615.png]]](https://i-blog.csdnimg.cn/direct/e17bb7ede3224b63bc1a0271f443cf07.png)
![![[Pasted image 20250321150734.png]]](https://i-blog.csdnimg.cn/direct/2397244a89c844749776a57250c3af10.png)
![![[Pasted image 20250321150953.png]]](https://i-blog.csdnimg.cn/direct/9bc4d95578574a188afaa363950f2e50.png)
![![[Pasted image 20250321151218.png]]](https://i-blog.csdnimg.cn/direct/8a448e2c521848d6b64c834f2ba17175.png)
![![[Pasted image 20250321151336.png]]](https://i-blog.csdnimg.cn/direct/40af904c52d14530990e6833753c8249.png)
蓝桥杯备战:二叉搜索树、平衡二叉树与红黑树
2694

被折叠的 条评论
为什么被折叠?



