image/svg+xml
此外还有其他性质,如下:
♾️如果红黑树的所有结点都是黑色,则它一定是一棵满二叉树
RB树的插入
插入结点默认是红色。
- 如果父结点是黑色,无需处理。
- 如果插入结点前为空树,插入结点自己就是树的根结点,则改为黑色,结束。
- 如果父结点是红色,并且叔结点是黑色,但插入结点是右孩子,则插入结点左旋,即变为情况4。
- 如果父结点是红色,并且叔结点是黑色,但插入结点是左孩子,则父结点和爷结点交换颜色,并右旋,结束。
- 如果父结点是红色,并且叔结点是红色,则父结点、叔结点的红色变黑色,爷结点变红色,此时爷结点作为新的插入结点往上推两层。
RB树的删除
待删结点只有右孩子或只有左孩子,其孩子必然是红色(否则违反了性质5),此时直接当孩子着色为黑色,替补自己即可。
待删结点没有孩子,且自己是红色,则可直接删除。
待删结点没有孩子,但自己是黑色,需将自己的虚拟子结点(黑色)代替自己的位置,并且将其附上双重黑色,变为双黑结点,并在后续几种情况中消去双黑属性。
- 兄弟结点是红色,则将兄弟结点与父结点换色,并对父结点左旋。这样可以让新的兄弟结点变为黑色。即变为了情况2。
- 兄弟结点是黑色,并且兄弟结点的孩子左红右黑,则交换兄弟结点和其左孩子的颜色,然后兄弟结点右旋,这样可以使得新的兄弟结点右孩子是红色。即变为了情况3。
- 兄弟结点是黑色,并且兄弟结点的孩子右孩子为红色,则交换兄弟结点和父结点的颜色,兄弟结点的右孩子变黑,然后父结点左旋。此时双黑结点退化为一重黑,完成修正。
- 兄弟结点是黑色,并且兄弟结点的孩子均为黑色,则双黑结点与其兄弟结点同时退化一重黑色,双黑结点变为黑色结点,兄弟结点变为红色结点,然后将父结点套上一层黑色,把父结点作为新的待调整结点,往上一层推。
B & B+树|B-Tree & B+Tree
注意:B树的英文名是 B-Tree
,因此也有翻译将B树称为 “B-树”,而其中的 -
符号属于连接符,而不是“减号”,与 B+树中的 +
号不做对应,也不读作“B减树”!
B树的定义
B 树,又称多路平衡查找树,B 树中所有结点的孩子个数的最大值称为 B 树的阶,通常用m 表示。
一棵m 阶B 树或为空树,或为满足如下特性的m 叉树:
- 树中每个结点至多有m 棵子树,即至多含有m−1 个关键字
- 若根结点不是终端结点,则至少有两棵子树
- 除根结点外的所有非叶结点至少有⌈m/2⌉ 棵子树,即至少含有⌈m/2⌉−1 个关键字,根结点若不是叶结点,则至少有 2 棵子树
- 所有的叶结点都出现在同一层次上,并且不带信息(可以视为外部结点或类似于折半查找判定树的查找失败结点,实际上这些结点不存在,指向这些结点的指针为空)
- 所有非叶结点的结构如下:
其中,Ki(i=1,...,n) 为结点的关键字,且满足K1<K2<⋯<Kn,Pi(i=0,1,....,n) 为指向子树根结点的指针,且指针Pi−1 所指子树中所有结点的关键字均小于Ki,Pi 所指子树中所有结点的关键字均大于Ki。而n 即为结点中关键字的个数。(⌈m/2⌉−1≤n≤m−1)
由此也可以看出,有n 个关键字的B 树,其插入失败的可能性有n+1,即查找失败结点的个数是n+1.
B 树是平衡因子均为0的多路平衡查找树
下图是一棵 4 阶B 树,其中关键字为英文辅音字母,淡色部分是查找字母R 时的路径结点。
B树的高度
首先声明,此处讨论的高度不包括最后一层,即不带任何信息的叶子结点/终端结点/虚拟结点 那一层。(有的教材则包括此层)
对任意一棵包含n(n≥1) 个关键字,高度为h,阶数为m 的 B树:
- 当每个结点尽最大可能地分支子树时,每个结点均有m−1 个关键字,从而分支出m 棵子树;从而关键字个数n≤(m−1)×(1+m1+m2+⋯+mh−1)=mh−1.
即:
h≥logm(n+1)
- 当每个结点尽最大可能地不分支子树时,因为树非空,所以第一层至少 1 个结点、第二层至少 2 个结点,此后每个结点只含有⌈m/2⌉−1 个关键字,分支出⌈m/2⌉ 棵子树,即第三层至少有2⌈m/2⌉ 个结点,以此类推,第h 层有2(⌈m/2⌉)h−1 个结点。而第h+1 层是查找不成功的终端结点,其个数为结点数+1,即n+1,所以有n+1≥2(⌈m/2⌉)h−1.
即:
h≤log⌈m/2⌉(2n+1)+1
🔔综上,已知关键字个数为n 的情况下,m 阶B 树的高度的取值范围是:
logm(n+1)≤h≤log⌈m/2⌉(2n+1)+1
此外,根据类似的推论,我们还有已知高度情况下对结点数、关键字个数的最值讨论:
♾️ 一棵含有n 个非叶结点的m 阶B 树中,包含的关键字个数至少为:(n−1)(⌈m/2⌉−1)+1
【推导】按每个结点包含的最少的关键字个数计算,根结点至少1个关键字,剩下的n−1 个非叶结点至少包含⌈m/2⌉−1 个,故一共是(n−1)(⌈m/2⌉−1)+1 个。
B树的插入
B树的插入主要有两个步骤:定位和插入。
对插入关键字的定位过程其实也就是其查找过程。包含两个基本操作:
- 在 B 树中找到所属结点
- 在结点内查找关键字,若找到则查找成功;若没找到,通过其区间范围得到指向下一棵子树(子结点)的指针信息,并转到第一步,直到指针为空(查找到了虚拟结点)则查找失败
关键字的定位则对应着查找失败的情况,通过查找失败的信息可以得到应该将此关键字插入的最底层非终端结点的插入位置。
接下来是插入过程。
- 如果树为空,则分配根结点并插入关键字。
- 如果树不为空,且插入到插入位置后,满足B树定义,即结点内关键字个数不超过m−1,则插入成功,结束。
- 如果插入到结点后使得其“溢出”,则在中位数处进行左右分裂;
- 将中位数插入原结点的父结点内,即向上提升中间字;然后将左部分设为左子级,将右部分设为右子级。
- 如果上述操作导致父结点也“溢出”,则进行对父结点进行分裂,重复上述过程,若一直上推到根结点,则B树高度+1。
下面是一个m=3 的B 树,依次插入:8、9、10、11、15、16、17、18、20、23 的过程示例。
B树的删除
与 B 树的插入相对,若删除结点后使得结点内的关键字个数低于最低要求⌈m/2⌉−1 则会出现“下溢”,为此需要进行适当调整。
我们先对各自情况进行梳理。
情况一:待删关键字位于叶中(此处的叶并非最底层的终端虚拟结点,而是有关键字的最后一层结点)
- 若删除之并未造成“下溢”,则直接删除;
- 若删除之造成“下溢”,且向兄弟结点中借一个关键字不会造成兄弟结点“下溢”的情况下,进行父子换位法:左兄弟的最右关键字上升到父结点,父结点中相邻关键字下降到被删除的结点中以维持平衡。(或者右兄弟的最左关键字上升)
- 若兄弟结点“不够借”,则将当前结点与兄弟结点进行合并,连接两个兄弟结点的父结点的关键字也需下降和它们合并到一起。
- 若因为上述第三步导致父结点的关键字个数不符合B树要求,则对父结点递归进行调整,直到满足 B树要求为止。
情况二:待删关键字位于非终端结点
被删关键字k 在非终端结点上时,可以将其前驱或后继k′ 代替它现在的位置,然后在k′ 所处的结点中删除之。此时问题转化为了 情况一。
B+树的基本概念
B+树是应文件系统所需而产生的B 树的变形,比起 B 树更加适用于实际的操作系统文件索引和数据库索引,因为其磁盘读写代价更低,查找效率更加稳定。
一棵m 阶的B+ 树需满足下列条件:
- 每个分支结点最多有m 棵子树
- 非叶根结点至少有两棵子树,其他每个分支结点至少有⌈m/2⌉ 棵子树(要追求“绝对平衡”,即所有子树高度要相同)
- 结点的子树个数与关键字个数相等(B树中关键字比子树少一个)
- 所有叶结点包含全部关键字及指向相应记录的指针,叶结点中将关键字按大小顺序排列,并且相邻叶结点按大小顺序相互链接起来(支持顺序查找)
- 所有分支结点中仅包含它的各个子结点中关键字的最大值及指向其子结点的指针
解释: 上面图示中的索引 【15、56】 和 【 3、9、15】 以及 索引 【35、42、56】 是存在不同的磁盘块里面的。
每查找一次结点都要进行一次读磁盘的操作,直到找到最下面的叶子结点,每次读取磁盘块都是一次慢速操作,所以要让树尽可能矮。一个磁盘块只有 1KB 大小,为了让一个磁盘块尽可能包含索引信息, B+树 要求非叶子结点只包含索引和地址,不包含记录。
B树与B+树的比较
关键字个数
m 阶B+ 树 结点中n 个关键字对应n 棵子树
m 阶B 树 结点中n 个关键字对应n+1 棵子树
m 阶B+树 根结点的关键字数n∈[1,m],其他结点的关键字数n∈[⌈m/2⌉,m]
m 阶B树 根结点的关键字数n∈[1,m−1],其他结点的关键字数n∈[⌈m/2⌉−1,m−1]
关键字重复
m 阶B+树 中,叶结点包含全部关键字,非叶结点中出现过的关键字也会出现在叶结点中
m 阶B 树 中,各结点中包含的关键字是不重复的
存储地址
m 阶B+树 中,叶结点包含信息,所有非叶结点仅起索引作用,非叶结点中的每个索引项只含有对应子树的最大关键字和指向该子树的指针,不含有该关键字对应记录的存储地址
m 阶B 树 的结点中都包含了关键字对应的记录的存储地址
顺序查找
m 阶B+树 中,叶结点包含全部关键字,可以顺序查找,同时也支持随机查找
m 阶B 树 只能随机查找
全都是树 BST、AVL、RB、B、B+|数据结构Ⅲ.2