算法导论 总结索引 | 第五部分 第十八章:B树

news2024/9/22 23:15:51

1、B 树是 为磁盘或其他直接存取的辅助存储设备 而设计的一种平衡搜索树。B 树类似于红黑树,在降低磁盘 I/O 操作次数方面要更好一些。许多数据库系统 使用 B 树 或者 B 树 的变种来存储信息

2、B 树与红黑树的不同之处 在于 B 树的结点 可以有很多孩子,尽管 它通常依赖于所使用的磁盘单元的特性。B 树类似于红黑树,每棵含有 n 个结点的 B 树的高度为 O(lgn)。一棵 B 树的严格高度可能比一棵红黑树的高度要小许多, 这是因为它的分支因子,也就是表示高度的对数的底数 可以非常大
也可以使用 B 树在时间 O(lgn) 内完成一些动态合并的操作

3、如果 B 树的一个内部结点 x 包含 x.n 个关键字,那么结点 x 就有 n+1 个孩子。结点 x 中的关键字 就是分隔点

当在一棵 B 树中查找一个关键字时,基于对存储在 x 中的 x.n 个关键字的比较,做出一个 (x.n + 1) 路的选择
在这里插入图片描述
4、为了掩还机械移动所花费的等待时间,磁盘会一次存取多个数据项 而不是一个。信息被分为一系列等大小的在柱面内 连续出现的页面,并且 每个磁盘读或写一个或多个完整的页面

定位到一页信息 并将其从磁盘里读出的时间 要比对读出信息进行检查的时间要长得多。对运行时间的两个主要组成成分 分别加以考虑:

  • 磁盘存取次数
  • CPU (计算) 时间

使用 需要读出或写入磁盘的信息的页数 来衡量磁盘存取次数。磁盘存取时间 并不是常量——它依赖于 当前磁道和所需磁道之间的距离 以及 磁盘的初始旋转状态。仍使用读或写的页数 作为磁盘存取总时间的主要近似值

在一个典型的 B 树应用中,所要处理的数据量非常大,以至于 所有数据无法一次装入主存。B 树算法 将所需页面从磁盘复制到主存,然后将修改过的页面 写回到磁盘,任何时刻,B树算法 都只需在主存中 保持一定数量的页面

5、设 x 为指向一个对象的指针。如果该对象正在主存中,那么可以像平常一样 引用 该对象的各个属性:如 x.key。如果 x 所指向的对象驻留在磁盘上,那么在 引用它的属性之前,必须先执行 DISK-READ(x),将该对象读入主存中。(假设如果 x 已经在主存中,那么 DISK-READ(x) 不需要磁盘存取,即它是一个空操作。) 类似地,操作 DISK-WRITE(x) 用来保存 对象 x 的属性所做的任何修改

x = a pointer to some object
DISK-READ(x)
operations that access and/or modify the attributes of x
DISK-WRITE(x) // omitted if no attributes of x were changed
other operations that access but do not modify attributes of x

6、在任何时候,这个系统 可以在主存中只保持有限的页数。假定系统不再将 被使用的页从主存中换出;后面的 B 树算法会忽略这一点

在大多数系统中, 一个 B 树算法的运行时间 主要由它所执行的 DISK-READ 和 DISK-WRITE 操作的次数决定,所以 希望这些操作能够 读或写尽可能多的信息。因此,一个 B 树结点通常 和一个完整磁盘页一样大,并且 磁盘页的大小限制了一个 B 树结点可以含有的孩子个数

分支因子在 50~2000 之间,具体取决于一个关键字相对于一页的大小。一个大的分支因子 可以大大地降低树的高度 以及 查找任何一个关键字所需的磁盘存取次数

由于根结点可以持久地保存在主存中,所以在这棵树中 查找某个关键字至多只需两次磁盘存取
在这里插入图片描述

1、B 树的定义

1、任何和关键字相关系的卫星数据 将与关键字一样 存放在同一个结点中。可能只是 为每个关键字存放一个指针,这个指针指向存放该关键字的卫星数据的磁盘页
这一章的伪代码 都隐含地假设了 当一个关键字从一个结点移动到另一个结点时,无论是与关键字相关的卫星数据,还是 指向卫星数据的指针,都会随着关键字一起移动。一个常见的 B 树变种,称为 B+ 树,它把所有的卫星数据 都存储在叶结点中,内部结点 只存放关键字和孩子指针,因此最大化了内部结点的分支因子

2、一棵 B 树 T 是具有以下性质的有根树
1)每个结点 x 有下面属性:

  1. x.n,当前存放在结点 x 中的关键字个数
  2. x.n 个关键字本身 x.key_1,x.key_2,…,x.key_x.n,以非降序存放,使得 x.key_1 ≤ x.key_2 ≤ … ≤ x.key_x.n
  3. x.leaf,一个布尔值。如果 x 是叶结点,则为 TRUE;如果 x 是内部结点,则为 FALSE

2)每个内部结点 x 还包含 x.n+1 个指向其孩子的指针 x.c_1,x.c_2,…,x.c_n+1。叶结点没有孩子,所以它们的 c_i 属性没有定义

3)关键字 x.key 将存储在各子树中的关键字范围 加以分割:如果 k_i 为任意一个存储在以 x.c 为根的子树中的关键字,那么
在这里插入图片描述
4)每个叶结点具有相同的深度, 即树的高度 h

5)每个结点所包含的关键字个数有上界和下界。用一个数称为 B 树的最小度数(子树的数量)的固定整数 t ≥ 2 来表示这些界:

  1. 除了根结点以外的 每个内部结点必须至少有 t - 1 个关键字。因此,除了根结点以外的每个内部结点至少有 t 个孩子。如果树非空,根结点至少有一个关键字
  2. 每个结点至多可包含 2t - 1 个关键字。因此,一个内部结点至多可有 2t 个孩子(因为超过 2t 就要分裂了)。当一个结点恰好有 2t - 1 个关键字时,称该结点是满的

t = 2 是 B 树最简单的。每个内部结点有 2 个、3 个或 4 个孩子,即一棵 2-3-4 树。t 的值越大, B 树的高度越小

3、B 树的高度:B 树上大部分的操作所需的磁盘存取次数 与 B 树的高度是成正比的

如果 n ≥ 1,那么对任意一棵包含 n 个关键字,高度为 h,最小度数 t ≥ 2 的 B 树 T, 有
在这里插入图片描述
B 树 T 的根 至少包含一个关键字,而且所有其他的结点至少包含 t - 1 个关键字。因此,高度为 h 的 B 树 T 在深度 1 至少包含 2 个结点(根有1个关键字),在深度 2 至少包含 2t 个结点, 在深度 3 至 少包含 2t2 个结点,等等,直到深度 h 至少有 2th−1 结点。关键字的个数 n 满足不等式:(从第1层开始就要 多乘个(t - 1))
在这里插入图片描述
可以得到 th <= (n + 1)/2。两边取以 t 为底的对数就证明了定理
在这里插入图片描述
与红黑树对比,看到了 B 树的能力。尽管二者的高度都以 O(lg n) 的速度增长 (注意 t 是个常数),但对 B 树来说,对数的底可以大很多倍。因此,对大多数树的操作来说,要检查的结点数在 B 树中要比红黑树中少大约 lgt 的因子。由于在一棵树中检查任意一个结点都需要 一次磁盘访问,所以 B 树避免了大量的磁盘访问

4、为什么不允许最小度数 t = 1: B 树中不存在只有 0 个键的节点,也不存在只有 1 个子节点的节点

当 t 取何值时,图示的树是一棵合法的 B 树
在这里插入图片描述
除根节点外的每个节点必须至少有 t−1 个键,并且最多可以包含 2t−1 个键。每个节点(除了根节点)的键的数量要么是2,要么是3。因此,为了使其成为合法的B树,需要保证 t−1≤2 并且 2t−1≥3,这意味着 2≤t≤3。所以 t 可以是2或者3

5、如果红黑树中的每个黑结点吸收它的红色孩子,并把它们的孩子并作为自己的孩子,描 述这个结果的数据结构
将每个红色节点吸收到其黑色父节点中后,每个黑色节点可能包含 1 或 2 个(1 个红色子节点),或者 3 个(2 个红色子节点)键,并且根据红黑树的性质5(对于每个节点,从该节点到后代叶节点的所有路径都包含相同数量的黑色节点),结果树的所有叶节点具有相同的深度。因此,一棵红黑树将变成最小度数为 t=2 的 B 树(因为红黑树是二叉树),即 2-3-4 树

2、B 树的基本操作(搜索、创建、插入)

1、给出 B-TREE-SEARCH、B-TREE-CREATE 和 B-TREE-INSERT 操作的细节,采用两个约定:

  • B 树的根结点 始终在主存中,这样无需对根做 DISK-READ 操作;然而,当根结点被改变后, 需要对根结点做一次 DISK-WRITE 操作
  • 任何被当做参数的结点 在被传递之前,都会对它们先做一次 DISK-READ 操作

2、搜索 B 树:根据结点的孩子数 做多路分支选择。对每个内部结点 x,做的是一 个 (x.n + 1) 路的分支选择

输入是 一个指向某子树根结点 x 的指针,以及要在 该子树中搜索的一个关键字 k。顶层调用的形式为 B-TREE-SEARCH (T.root, k)。如果 k 在 B 树中,那么 B-TREE-SEARCH 返回的是由结点 y 和使得 x.key_i = k 的下标 i 组成的有序对 (y, i);否则,过程返回 NIL

B-TREE-SEARCH(x, k)
1 i = 1
2 while i ≤ x.n and k > x.key_i
3 	i = i + 1
4 if i ≤ x.n and k == x.key_i
5 	return (x, i)
6 elseif x.leaf
7 	return NIL
8 else DISK-READ(x, c_i) // c_i指向第i个孩子的结点
9 	return B-TREE-SEARCH(x.c_i, k)

第 1 ~ 3 行找出最小下标 i, 使得 k ≤ x.key_i。若找不到,则 i = x.n + 1。第 4 ~ 5 行检查是否已经找到关键字 k, 如果找到,则返回;否则,第 6 ~ 9 行指定这次 不成功查找 (如果 x 是叶结点),或者在对孩子结点执行 必要的 DISK-READ 后,递归搜索 x 相应子树

在递归过程中 所遇到的结点构成了一条从树根向下的简单路径。因此,由 B-TREE-SEARCH 过程访问的磁盘页面数为 O(h) = O(logt(n)), 其中 h 为 B 树的高度, n 为 B 树中所有关键字个数。由于 x.n < 2t,因此第 2 ~ 3 行的 while 循环在每个结点花费的时间为 O(t),总的 CPU 时间为 O(th) = O(t logt(n))

3、创建一棵空的 B 树
辅助过程 ALLOCATE-NODE,它在 O(1) 时间内为一个新结点 分配一个磁盘页。可以假定由 ALLOCATE-NODE 所创建的结点并不需要 DISK-READ,因为磁盘上 还没有关于该结点的有用信息

B-TREE-CREATE(T)
1 x = ALLOCATE-NODE()
2 x.leaf = TRUE
3 x.n = 0
4 DISK-WRITE(x)
5 T.root = x

B-TREE-CREATE 需要 O(1) 次的磁盘操作和 O(1) 的 CPU 时间

4、向 B 树中插入一个关键字:
树中插入一个关键字要比二叉搜索树中插入一个关键字复杂得多。像二叉搜索树中一样, 要查找插入新关键字的叶结点的位置。然而,在 B 树中,不能简单地创建一个新的叶结点,然后将其插入,因为这样得到的树 将不再是合法的 B 树。相反,将新的关键字插入一个已经存在的叶结点上。由于不能将关键字插入一个满的叶结点,故引入一个操作, 将一个满的结点 y (有 2t - 1 个关键字) 按其中间关键字 x.key_t 分裂为两个各含 t - 1 个关键字的结点。中间关键字被提升到 y 的父结点,以标识两棵新树的划分点。但是如果 y 的父结点也是满的,就必须在插入新的关键字之前将其分裂,最终满结点的分裂 会沿着树向上传播

可以在 从树根到叶子这个单程向下 过程中 将一个新的关键字插入 B 树中。并不是 等到找出插入过程中 实际要分裂的满结点时才做分裂,当沿着树 往下查找新的关键字所属位置时,就分裂沿途遇到的每个满结点 (包括叶结点 本身),因此,每当要分裂一个满结点 y 时,就能确保它的父结点不是满的

5、分裂 B 树中的结点:过程 B-TREE-SPLIT-CHILD 的输入是一个非满的内部结点 x (假定在主存中) 和一个使 x.c_i (也假定在主存中) 为 x 的满子结点的下标 i。这过程 把这个子节点分裂成两个非满结点,并调整 x,使之 包含多出来的孩子。要分裂一个满的结点,首先要让根成为一个新的空根结点的孩子, 这样才能使用 B-TREE-SPLIT-CHILD。树的高度因此增加 1;分裂是树长高的唯一途径

满结点 y = x.c_i 按照其中间关键字 S 进行分裂,S 被提升到 y 的父结点 x
在这里插入图片描述

B-TREE-SPLIT-CHILD(x, i)
1 z = ALLOCATE-NODE()
2 y = x.c
3 z.leaf = y.leaf
4 z.n = t - 1
5 for j = 1 to t - 1
6 	 z.key_j = y.key_j+t // z和y为兄弟
7 if not y.leaf
8 	 for j = 1 to t
9 		z.c_j = y.c_(j+t)
10 y.n = t - 1 // 直接删掉了
11 for j = x.n + 1 downto i + 1
12 	 x.c_j+1 = x.c_j
13 x.c_i+1 = z // 插入到x的孩子
14 for j = x.n downto i
15 	 x.key_(j+1) = x.key_j
16 x.key_i = y.key_t // 上移
17 x.n = x.n + 1
18 DISK-WRITE(y)
19 DISK-WRITE(z)
20 DISK-WRITE(x)

y 是被分裂的结点,y 是 x 的第 i 个孩子。开始时, 结点 y 有 2t 个孩子 (2t - 1 个关键字), 在分裂后减少至 t 个孩子 (t - 1 个关键字)。结点 z 取走 y 的 t 个最大的孩子 (t - 1 个关键字),并且 z 成为 x 的新孩子, 它在 x 的孩子表中仅位于 y 之后。y 的中间关键字上升到 x 中,成为分隔 y 和 z 的关键字

第 18 ~ 20 行写出所有修改过的磁盘页面。B-TREE-SPLIT-CHILD 占用的 CPU 时间为 Θ(t),是由第 5 ~ 6 行和第 8 ~ 9 行的循环引起的。(其他循环执行 O(t) 次迭代)这个过程执行 O(1) 次磁盘操作

6、以沿树单程下行方式向 B 树插入关键字:以沿树单程下行方式 插入一个关键字 k 的操作需要 O(h) 次磁盘存取。所需的 CPU 时间为 O(h) = O(t logt(n))。过程 B-TREE-INSERT 利用 B-TREE-SPLIT-CHILD 来保证递归始终不会降至一个满结点上

B-TREE-INSERT(T, k)
1 r = T.root
// 这不是一个循环,只针对根,s为r的父结点,也为整棵树的新的根结点(1个关键字)
// 只要树不是满的,就可以调用 B-TREE-INSERT-NONFULL 插入
2 if r.n == 2t - 1
3 	s = ALLOCATE-NODE()
4 	T.root = s
5 	s.leaf = FALSE
6 	s.n = 0
7 	s.c_1 = r
8 	B-TREE-SPLIT-CHILD(s, 1)
9 	B-TREE-INSERT-NONFULL(s, k)
10 else B-TREE-INSERT-NONFULL(r, k)

对根进行分裂 是增加 B 树高度的唯一途径。与二叉搜索树不同,B 树高度的增加 发生在顶端而不是底部。过程通过调用 B-TREE-INSERT-NONFULL 完成将关键字 k 插入以非满的根结点为根的树中。B-TREE-INSERT-NONFULL 在需要时沿树向下递归,在必要时通过调用 B-TREE-SPLIT-CHILD 来保证 任何时刻它所递归处理的结点都是非满的
在这里插入图片描述
辅助的递归过程 B-TREE-INSERT-NONFULL 将关键字插入结点 x,要求假定在调用该过程 时 x 是非满的

B-TREE-INSERT-NONFULL(x, k)
1 i = x.n // 初始位置在队尾
2 if x.leaf
3 	while i ≥ 1 and k < x.key_i
4 		x.key_i+1 = x.key_i
5 		i = i - 1
6 	x.key_i+1 = k // 最终插入位置
7 	x.n = x.n + 1
8 	DISK-WRITE(x)
9 else while i ≥ 1 and k < x.key_i
10		i = i - 1
11 	 i = i + 1 // 加回去,让 key_i 刚好比K大
12 	 DISK-READ(x.c_i) // 在key_i 前面那个空挡
13 	 if x.c_i.n == 2t - 1 // 孩子结点满了,要确保分裂后有每个子树有 t个关键字
14   	B-TREE-SPLIT-CHILD(x, i) // 把孩子结点拆开
15 		if k > x.key_i
16 			i = i + 1 // 找往下递归的口子
17 		B-TREE-INSERT-NONFULL(x.c_i, k)

第 3 ~ 8 行处理 x 是叶结点的情况,将 关键字 k 插入 x。如果 x 不是叶结点,则必须将 k 插入内部结点 x 为根的子树中 适当的叶结点去。这种情况,9 ~ 11 行决定向 x 的哪个子结点递归下降。第 13 行检查是否是递归降至一个满子结点上,若是,则第 14 行用 B-TREE-SPLIT-CHILD 将 该子结点分裂为两个非满的孩子,第 15 ~ 16 行确定向两个孩子中的哪个下降是正确的。第 13 ~ 16 行 保证该程序始终不会降至一个满结点上。然后第 17 行递归地将 k 插入合适的子树中

B-TREE-INSERT 要做 O(h) 次磁盘存取,因为在每次调用 B-TREE-INSERT-NONFULL 之间,只做了 O(1) 次 DISK-READ 和 DISK-WRITE 操作。所占用的总 CPU 时间为 O(h) = O(t logt(n))
在这里插入图片描述
在这里插入图片描述
7、如何在一棵 B 树中找到最小关键字,以及如何找到某一给定关键字的前驱

B-TREE-FIND-MIN(x)
    if x == NIL           // T is empty
        return NIL
    else if x.leaf        // x is leaf
        return x.key[1]   // return the minimum key of x,从1开始
    else
        DISK-READ(x.c[1]) // 往下探
        return B-TREE-FIND-MIN(x.c[1])

B-TREE-FIND-MAX(x)        // 跟找最小是对称的
    if x == NIL           // T is empty
        return NIL
    else if x.leaf        // x is leaf
        return x.[x.n]    // return the maximum key of x
    else
        DISK-READ(x.c[x.n + 1])
        return B-TREE-FIND-MIN(x.c[x.n + 1])

B-TREE-FIND-PREDECESSOR(x, i)
    if !x.leaf
        DISK-READ(x.c[i]) // 右子树最大值
        return B-TREE-FIND-MAX(x.c[i])
    else if i > 1      // x is a leaf and i > 1
        return x.key[i - 1] // 前面那个元素
    else
        z = x            // 是右子节点但是是节点中第一个元素
        while true
            if z.p == NIL    // z is root
                return NIL   // z.key[i] is the minimum key in T; no predecessor
              y = z.p        // 找靠左父元素,从目标结点的父结点开始找
              j = 1
              DISK-READ(y.c[1])
              while y.c[j] != x
                  j = j + 1
                  DISK-READ(y.c[j])
              if j == 1
                  z = y // 往上找
              else
                  return y.key[j - 1]

8、假设关键字 {1, 2, …, n} 被插入一棵最小度数为 2 的空 B 树中,那么最终的 B 树有多少个结点
每次插入的键都比现有的键大,形成右脊(在树的最右边,从根结点到叶结点的一条路径),有 h+1 个结点(根结点高度为0),除了右脊上的点,其他结点都是分裂的结果,每次分裂产生 两个子节点 N=h+1 (右脊) + h(其他结点数)

9、由于叶节点不需要指向子节点的指针,它们可以使用比内部节点更大的 t 值以适应相同的磁盘页面大小。展示如何修改创建和插入 B 树的过程以处理这种变化
可以通过以下方法修改插入过程:在 B-TREE-INSERT 中,检查节点是否是叶节点,如果是,则仅在其中存储的键数量是预期的两倍时才分裂它。此外,如果一个元素需要插入到已满的叶节点中,我们将叶节点分裂成两个独立的叶节点,每个叶节点中都没有存储过多的键

10、假设磁盘每允许我们任意选择磁盘页面的大小,但该取磁盘页面的时间是 a + bt, 其中 a 和 b 为规定的常数,t 为确定磁盘页大小后的 B 树的最小度数。请描述如何选 择 t 以 (近似地) 最小化 B 树的查找时间
在这里插入图片描述

3、从 B 树中删除关键字

1、B 树上的删除操作与插入操作类似,只是略微复杂一点,因为可以从任意一个结点中删除一 个关键字,而不仅仅是叶结点,而且当从一个内部结点删除一个关键字时,还要重新安排这个结点的孩子

必须保证 一个结点不会在删除期间变得太小(根结点除外, 因为它允许 有比最少关键字数 t-1 还少的关键字个数)

与简单插入法类似,当删除关键字时,一个简单删除算法,当要删除关键字的路径上结点(非根)有最少关键字个数时,也可能需要向上回溯

2、过程 B-TREE-DELETE 从以 x 为根的子树中删除关键字 k。必须保证无论何时,结点 x 递归调用自身时,x 中关键字个数 至少为最小度数 t。这个条件要求比 通常 B 树中的最少关键字个数多一个以上,使得有时在递归下降至子节点之前,需要把一个关键字移到子节点中。这个加强的条件允许在一趟下过程中, 就可以将一个关键字从树中删除, 无需任何向上回溯 (有一个例外)。如果根结点 x 成为一个不含任何关键字的内部结点,那么 x 就要被删除,x 的唯一孩子 x.c1 成为树的新根, 从而树的高度降低 1,同时也维持 树根结点必须包含一个关键字的性质 (除非树为空)

3、从 B 树中删除关键字的各种情况
1)如果关键字 k 在结点 x 中,并且 x 是叶结点,则从 x 中删除 k

2)如果关键字 k 在结点 x 中,并且 x 是内部结点,则做以下操作:
a. 如果位于 x 的子树 y 中的结点个数至少包含 t 个关键字(多一个),则找出 k 在 y 为根的子树的前驱 k’。递归地删除 k’,并在 x 中替换 k。 (找到 k’ 并删除它可在沿树下降的单过程中完成)

b. 对称地,如果 y 有少于 t 个关键字,则检查结点 x 中后于 k 的子节点 z。如果 z 至少有 t 个关键字,则找出 k 在以 z 为根的子树中的后继 k’。递归地删除 k’,并在 x 中用 k’ 替换 k。 (找到 k’ 并删除它可在沿树下降的单过程中完成)

c. 否则, 如果 y 和 z 都只有 t - 1 个关键字,则将 k 和 z 的全部合并进 y。这样 x 就失去了 k 和 指向 z 的指针,并且 y 现在包含 2t - 1 个关键字,然后释放 z 并递归地从 y 中删除 k

3)如果关键字 k 当前不在内部结点 x 中,则确定必包含 k 的子树中的根 x.c_i(如果 k 确实在树中)。如果 x.ci 有 t - 1 个关键字,则必须执行步骤 3a 或步骤 3b 或来保证降至一个至少含 t 个关键字的结点。然后通过对 x 的某个合适的子结点进行递归而结束
a. 如果 x.c_i 只有 t - 1 个关键字,但是它的一个相邻的兄弟 至少包含 t 个关键字,则将 x 中的某一个关键字降至 c_i 中,将 c_i 的相邻左兄弟 或 右兄弟的一个关键字升至 x,将该兄弟中相应的孩子指针转移到 c_i 中, 这样就使得 x.c_i 增加了一个额外的关键字

b. 如果 x.c_i 以及 x.c_i 的所有相邻兄弟只包含 t - 1 个关键字,则将 x.c_i 与一个兄弟合并,即将 x 的一个关键字移至新合并的结点,使之成为该结点中的间关键字

在这里插入图片描述
在这里插入图片描述

由于一棵 B 树的中大部分关键字都在叶结点中,可以预期在实际中,删除操作很经常用于 从叶结点中删除关键字。这样 B-TREE-DELETE 过程只要沿树 下降一趟即可,不需要向上回溯。要删除 某个内部结点的关键字时,该过程也要沿树下降一趟。但可能还要 返回删除了关键字的那个结点,用以 其前驱或后继 来取代被删除的关键字(情况 2a 和情况 2b)

一棵高度为 h 的 B 树,它只需要 O(h) 次磁盘操作,因为 在递归调用该过程之间,仅需 O(1) 次对 DISK-READ 和 DISK-WRITE 的调用。所需 CPU 时间 为 O(th) = O(t logt(n))

依次从图中删除 C, P 和 V 后的结果
在这里插入图片描述
delete C
在这里插入图片描述
delete P
在这里插入图片描述
delete V
在这里插入图片描述
4、B-TREE-DELETE 的伪代码

B-TREE-DELETE(T, x, k)
    if x 是叶节点 then
        删除 x 中的 k
    else
        找到 k 在 x 中的位置 i
        if x 的第 i 个子节点 y 至少有 t 个键 then
            k' = y 中的前驱
            用 k' 替换 k
            递归地调用 B-TREE-DELETE(T, y, k')
        else if x 的第 i+1 个子节点 z 至少有 t 个键 then
            k' = z 中的后继
            用 k' 替换 k
            递归地调用 B-TREE-DELETE(T, z, k')
        else
            将 k 和 z 合并到 y 中
            删除 x 中的 k
            递归地调用 B-TREE-DELETE(T, y, k)
    if x 是根节点 and x 没有键 then
        T.root = x 的第一个子节点
    修复 B 树的性质

修复 B 树的性质
    if x 的子节点 c 有少于 t 个键 then
        if c 的相邻兄弟节点有至少 t 个键 then
            从相邻兄弟节点借一个键
            将 x 中的一个键下降到 c
            将相邻兄弟节点中的一个键提升到 x
        else
            将 c 与一个相邻兄弟节点合并
            将 x 中的一个键下降到新合并的节点中

1)叶节点删除:如果要删除的键在一个叶节点中,直接删除该键。

2)内部节点删除:

  • 找到要删除的键在节点中的位置 i。
  • 检查该键的两个子节点:
    如果前驱子节点(左子节点)至少有 t 个键,找到前驱键,用前驱键替换要删除的键,然后递归地删除前驱键
    如果后继子节点(右子节点)至少有 t 个键,找到后继键,用后继键替换要删除的键,然后递归地删除后继键
    如果两个子节点都少于 t 个键,将要删除的键与其右子节点合并到左子节点,然后递归地删除合并后的子节点中的键

3)根节点特殊处理:如果删除后根节点没有键,将根节点设为其第一个子节点

4)修复 B 树性质:如果一个子节点少于 t 个键,从相邻兄弟节点借一个键,或者与相邻兄弟节点合并

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1928290.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《JavaScript权威指南第7版》中文PDF+英文PDF+源代码 +JavaScript权威指南(第6版)(附源码)PDF下载阅读分享推荐

JavaScript是Web编程语言。绝大多数网站都使用JavaScript&#xff0c;所有现代Web浏览器&#xff08;无论是桌面、平板还是手机浏览器&#xff0c;书中以后统称为浏览器&#xff09;都包含JavaScript解释器&#xff0c;这让JavaScript成为有史以来部署最广泛的编程语言。过去十…

怎么把自己写的组件发布到npm官方仓库??

一.注册npm账号 npm官网 1.注册npm 账号 2.登陆 3.登陆成功 二.搭建一个vue 项目 具体步骤参考liu.z Z 博客 或者初始化一个vue项目 vue create XXX &#xff08;工程名字&#xff09;运行代码 npm run serve三.组件封装 1.在src文件下建一个package文件&#xff0…

EMQX开源版安装

一、EMQX是什么 EMQX 是一款开源的大规模分布式 MQTT 消息服务器&#xff0c;功能丰富&#xff0c;专为物联网和实时通信应用而设计。EMQX 5.0 单集群支持 MQTT 并发连接数高达 1 亿条&#xff0c;单服务器的传输与处理吞吐量可达每秒百万级 MQTT 消息&#xff0c;同时保证毫秒…

参考——CCS联合CLion调试__开发TI芯片

一、简介 随着电赛临近&#xff0c;有些题必须指定使用TI芯片&#xff0c;那么就不得不学一下CCS。虽然CCS相较于Keil和IAR&#xff0c;显得更现代化一些&#xff0c;但还是没有代码样式、代码格式化、代码补全等功能。如果你用惯了CLion再用CCS&#xff0c;就会有些许一言难尽…

LLM之Prompt(四)| OpenAI、微软发布Prompt技术报告

摘要 生成式人工智能 &#xff08;GenAI&#xff09; 系统正越来越多地部署在各行各业和研究机构。开发人员和用户通过使用提示或提示工程与这些系统进行交互。虽然提示是一个广泛提及且被研究的概念&#xff0c;但由于该领域的新生&#xff0c;存在相互矛盾的术语和对构成提示…

十七、【文本编辑器(三)】图像坐标变换

目录 一、缩放功能 二、旋转功能 三、镜像功能 四、QMatrix简单介绍 一、缩放功能 &#xff08;1&#xff09;在头文件中添加 “protected slots:" 变量&#xff1a; void ShowZoomln( ); &#xff08;2&#xff09;在 createActionso函数的最后添力口事件关联&…

sealos快速安装k8s

Sealos 提供一套强大的工具&#xff0c;使得用户可以便利地管理整个集群的生命周期。 功能介绍 使用 Sealos&#xff0c;您可以安装一个不包含任何组件的裸 Kubernetes 集群。此外&#xff0c;Sealos 还可以在 Kubernetes 之上&#xff0c;通过集群镜像能力组装各种上层分布式…

数据结构——考研笔记(三)线性表之单链表

文章目录 2.3 单链表2.3.1 知识总览2.3.2 什么是单链表2.3.3 不带头结点的单链表2.3.4 带头结点的单链表2.3.5 不带头结点 VS 带头结点2.3.6 知识回顾与重要考点2.3.7 单链表的插入和删除2.3.7.1 按位序插入&#xff08;带头结点&#xff09;2.3.7.2 按位序插入&#xff08;不带…

AI写作不懂提示词 大象Prompt 保姆级系列教程三

一、提示词的核心价值究竟是啥&#xff1f; 最近跟不少业内朋友探讨这事儿&#xff0c;我觉得&#xff1a;提示词的核心价值在于对方法论的封装以及由此带来的知识传播速度加快。 通俗讲&#xff0c;假如你熟悉的行业里有个厉害的“老师傅”&#xff0c;他在核心业务上有好多心…

通用图形处理器设计GPGPU基础与架构(三)

一、前言 前两篇已经介绍了 GPGPU 的背景 和 GPGPU 的编程模型相关的内容&#xff0c;本文将在 SIMT 计算模型的基础上&#xff0c;介绍 GPGPU 控制核心架构和微体系结构的设计。 二、CPU-GPGPU 异构计算系统 一个由 CPU 和 GPGPU 构成的异构计算平台如下图所示&#xff0c;GP…

【常见开源库的二次开发】基于openssl的加密与解密——Base的编解码(二进制转ascll)(二)

目录&#xff1a; 目录&#xff1a; 一、 Base64概述和应用场景 1.1 概述 1.2 应用场景 二、Base16 2.1 Base16编码 2.2 Base16编解码 三、Base64 四、OpenSSL BIO接☐ 4.1 Filter BIOs&#xff1a; 4.2 Source/Sink BIOs&#xff1a; 4.3 应用场景&#xff1a; 4.4 具体使用&…

HCIE是什么等级的证书?

HCIE&#xff08;华为认证互联网专家&#xff0c;Huawei Certified Internetwork Expert&#xff09;是华为认证体系中的最高等级证书。它要求考生具备在复杂网络环境中规划、设计、部署、运维和优化网络的能力。HCIE认证是华为认证体系中最具挑战性和含金量的认证之一&#xf…

EPLAN 去掉PDF中的红色跳转标识

EPLAN PDF图纸导出后体验跳转标识会有红色标识&#xff0c;如何去掉呢&#xff1f;下面介绍一下方法&#xff1a; 此为现象&#xff1a; EPLAN 2.9的帮助文档里提示&#xff1a; 在导出的 PDF 文档中&#xff0c;跳转后的跳转目标现在通过红色的闪烁框进行标识。可能的跳转目…

探索Node.js中的node-xlsx:将Excel文件解析为JSON

在Node.js开发中&#xff0c;处理Excel文件是一个常见需求&#xff0c;特别是在需要导入大量数据或生成报表的场景中。node-xlsx 是一个强大的库&#xff0c;它提供了Excel文件的解析和生成功能。本文将深入探讨 node-xlsx 的使用&#xff0c;并通过一个案例演示如何将Excel文件…

蒙特卡洛树搜索

目录 1. 选择&#xff08;Selection&#xff09;2. 扩展&#xff08;Expansion&#xff09;3. 模拟&#xff08;Simulation&#xff09;4. 反向传播&#xff08;Backpropagation&#xff09;为什么蒙特卡洛树搜索很厉害&#xff1f;应用实例 蒙特卡洛树搜索介绍 蒙特卡洛树搜索…

【从0到1进阶Redis】哨兵模式

笔记内容来自B站博主《遇见狂神说》&#xff1a;Redis视频链接 小伙伴们可以看一下上一篇我的Redis笔记 —— 【从0到1进阶Redis】主从复制 这样可以更好的理解原理。 一、概述 主从切换技术的方法是&#xff1a;当主服务器宕机后&#xff0c;需要手动把一台从服务器切换为主服…

Parallels Desktop 19 for Mac(PD19虚拟机)详细图文安装教程分享

Parallels Desktop 19是一款功能丰富、性能强大且易于使用的虚拟机软件&#xff0c;它可以让您在Mac上同时运行多个操作系统&#xff0c;为您提供更大的灵活性和兼容性。 Parallels Desktop 19 for Mac(PD19虚拟机)下载安装包 Parallels Desktop 19 for Mac(PD19虚拟机)详细图…

在VS2017下FFmpeg+SDL编写最简单的视频播放器

1.下载ShiftMediaProject/FFmpeg 2.下载SDL2 3.新建VC控制台应用 3.配置include和lib 4.把FFmpeg和SDL的dll 复制到工程Debug目录下&#xff0c;并设置调试命令 5.复制一下mp4视频到工程Debug目录下&#xff08;复制一份到*.vcxproj同一目录&#xff0c;用于调试&#xff09; 6…

虚拟机的状态更新

文章目录 虚拟机的更新一、检查虚拟机的配置1.已连接状态2. 保证镜像源挂载 二、进行更新三、其余事项 虚拟机的更新 虚拟机的更新是确保系统软件包和库的更新&#xff0c;以获得最新的修复和改进&#xff1b;如果长期没有打开单机或者集群&#xff0c;可以考虑先进行一次更新…

CentOS 停服后,服务器 OS 路在何方?

2024 年 6 月 30 日&#xff0c;CentOS Linux 7 终止其生命周期&#xff08;EOL&#xff09;&#xff0c;至此 CentOS 全系列版本也已停止维护&#xff0c;属于 CentOS 的时代彻底终结。CentOS 停止维护后&#xff0c;用户将无法获得包括问题修复和功能更新在内的任何软件维护和…