算法导论18.2 Exercises 答案
18.2-1
如下是在\(t=2\)的B树中,按序插入这些字母的姿态变化过程。
- F
graph TD A[F];
- S
graph TD A[F S];
- Q
graph TD A[F Q S];
- K
graph TD A[Q];B[F K];C[S]; A---B;A---C;
- C
graph TD A[Q];B[C F K];C[S]; A---B;A---C;
- L
graph TD A[F Q];B[C];C[K L];D[S]; A---B;A---C;A---D;
- H
graph TD A[F Q];B[C];C[H K L];D[S]; A---B;A---C;A---D;
- T
graph TD A[F Q];B[C];C[H K L];D[S T]; A---B;A---C;A---D;
- V
graph TD A[F Q];B[C];C[H K L];D[S T V]; A---B;A---C;A---D;
- W
graph TD A[F Q T];B[C];C[H K L];D[S]; E[V W] A---B;A---C;A---D;A---E;
- M
graph TD A[Q];B[F K];C[T];D[C]; E[H];F[L M];G[S];H[V W]; A---B;A---C;B---D;B---E; B---F;C---G;C---H;
- R
graph TD A[Q];B[F K];C[T];D[C]; E[H];F[L M];G[R S];H[V W]; A---B;A---C;B---D;B---E; B---F;C---G;C---H;
- N
graph TD A[Q];B[F K];C[T];D[C]; E[H];F[L M N];G[R S];H[V W]; A---B;A---C;B---D;B---E; B---F;C---G;C---H;
- P
graph TD A[Q];B[F K M];C[T];D[C]; E[H];F[N P];G[R S];H[V W]; I[L]; A---B;A---C;B---D;B---E; B---I;B---F;C---G;C---H;
- A
graph TD A[Q];B[F K M];C[T];D[A C]; E[H];F[N P];G[R S];H[V W]; I[L]; A---B;A---C;B---D;B---E; B---I;B---F;C---G;C---H;
- B
graph TD A[Q];B[F K M];C[T];D[A B C]; E[H];F[N P];G[R S];H[V W]; I[L]; A---B;A---C;B---D;B---E; B---I;B---F;C---G;C---H;
- X
graph TD A[Q];B[F K M];C[T];D[A B C]; E[H];F[N P];G[R S];H[V W X]; I[L]; A---B;A---C;B---D;B---E; B---I;B---F;C---G;C---H;
- Y
graph TD A[Q];B[F K M];C[T W];D[A B C]; E[H];F[N P];G[R S];H[X Y]; I[L];J[V]; A---B;A---C;B---D;B---E; B---I;B---F;C---G;C---J; C---H;
- D
graph TD A[K Q];B[B F];C[M];D[T W]; E[A];F[C D];G[H];H[L]; I[N P];J[R S];K[V];L[X Y]; A---B;A---C;A---D;B---E; B---F;B---G;C---H;C---I; D---J;D---K;D---L;
- Z
graph TD A[K Q];B[B F];C[M];D[T W]; E[A];F[C D];G[H];H[L]; I[N P];J[R S];K[V];L[X Y Z]; A---B;A---C;A---D;B---E; B---F;B---G;C---H;C---I; D---J;D---K;D---L;
- E
graph TD A[K Q];B[B F];C[M];D[T W]; E[A];F[C D E];G[H];H[L]; I[N P];J[R S];K[V];L[X Y Z]; A---B;A---C;A---D;B---E; B---F;B---G;C---H;C---I; D---J;D---K;D---L;
18.2-2
考虑算法
B-TREE-SEARCH
的DISK-READ
。由于这个DISK-READ
每次都是读取当前节点的子节点信息,因此一路向下查找并不会造成读取冗余。考虑算法
B-TREE-CREATE
的DISK-WRITE
。树\(T\)只会被建立一次,因此这里只会写入一次节点,没有写入冗余。考虑算法
B-TREE-SPLIT-CHILD
第16-18行的DISK-WRITE
。节点\(x\)添加了一个关键字和一个子节点指针;\(y\)节点挪去了一半的关键字和子节点指针到新节点\(z\)。也就是说,\(x,y,z\)都进行了相应的变化,需要更新,因此这里没有写入冗余。考虑
B-TREE-INSERT-NONFULL
的DISK-READ
和DISK-WRITE
。DISK-READ
的分析和B-TREE-SEARCH
一致,它都是只读取当前节点的某一个子节点的信息,向下查找不会造成读取冗余。DISK-WRITE
只是对叶节点进行了一次写入(因为插入了一个元素),因此DISK-WRITE
操作也不是冗余的。
总而言之,无论是DISK-READ
还是DISK-WRITE
操作,都没有冗余。
18.2-3
可以注意到,如下图这棵B树目前每一个节点都是满的,它的高度达到了最小高度\(2\)。
graph TD A[4 8 12];B[1 2 3];C[5 6 7];D[9 10 11]; E[13 14 15] A---B;A---C;A---D;A---E;
这棵B树不可能是某一个插入操作序列所得到的一棵B树。不失一般性,假设它的根节点为\(r\)。在插入这\(15\)个关键字的最后一个关键字之前的B树\(T'\),它只有\(2\)层,考虑两种情况:
存在某个叶子节点\(l\)不是满的,那么根节点\(r\)必定是满的。当进行这个插入操作时,先检测到根节点\(r\)是满的,那么根节点\(r\)必定需要拆分,从而导致这棵树的树高增加。
所有叶子节点都是满的,只有根节点不是满的。这种情况是不可能出现的,如果根节点非满,那这棵B树最多只有\(4\)个节点,最多存储满\(3\times3+2=11\)个关键字,不到\(15\)个。
因此哪怕只有\(14\)个关键字且高度为\(2\)的B树,按照上面的论证,插入一个关键字后,树必定会增高,因此不存在某种插入序列使得这棵B树保持最小的高度,也就是\(2\)。
\(\star\) 18.2-4
首先,每次插入操作都保证了每个节点包含至少一个关键字,因此第\(n\)次操作的节点数\(N(n)\)不超过\(n\),因此有\(N(n)=O(n)\)。同时,每个节点最多只能包含\(3\)个关键字,因此\(N(n)\ge n/3\),因此可以得到\(N(n)=\Omega(n)\)。最终可以得知\(N(n)=\Theta(n)\)。
更进一步的分析是,由于每次插入的关键字都比之前的大,因此插入算法B-TREE-INSERT-NONFULL
只会遍历最右边的子节点。并且,每次分裂算法执行后,左边的子节点都只剩下一个元素,此后这些节点再也不会被遍历到。也就是说,只有从\(r\)到最右边的叶子节点路径上的所有节点才有可能有多个关键字。因此接下来我们只考虑这条路径上的所有节点。
可以观察得到,这条路径上有\(f(n)\)个节点,其中\(f(n)=j\)当且仅当满足\(2^{j}+j\le n+2\le 2^{j+1}+j\)。
此外,还可以得到这条路径上,倒数第\(i\)个节点的关键字个数具有一定的规律,只要路径长度开始超过\(i\),那么从第\(2\)个值起(第\(1\)个值是特殊的,为\(1\)),其节点数是一个周期为\(2^i\)的循环节,先是\(2^{i-1}-1\)个\(1\),再是\(2^{i-1}\)个\(2\),然后是一个\(3\)。总而言之,这条路径上的关键字总数\(g(n)\)不超过\(3n\)。
因此\(N(n)=n-g(n)+f(n)\),其中\(g(n)=\Theta(f(n))\),因此\(N(n)=\Theta(n)\)。以下表的第\(n\)行第\(i\)个值表示最右路径中,插入关键字\(n\)后,倒数第\(i\)个节点的关键字个数。
1 | 1 |
18.2-5
只需要判断当且节点是不是一个叶子节点即可。如果是叶子节点,那么使用另一个最小度数\(t'\)然后再对这个叶子节点进行相同的分割操作即可。
如下是对插入算法和节点拆分算法B-TREE-SPLIT-CHILD
的改写:
1 | CAL-T(x) |
18.2-6
由于单个节点中有\(O(t)\)个关键字,因此节点内部进行一次二分查找需要\(O(\lg t)\)的时间。由于整棵树的高度为\(O(\log_t n)\),因此在B树中进行一次元素查找需要\(O(\lg t\cdot \log_t n)=O(\lg n)\)的时间,这种查找方式最终将时间复杂度中的\(t\)消去了。
18.2-7
由于进行一次查找操作需要\(h=O(\log_t{n})\)次的读取,因此花费在磁盘读取上面的时间为\(h\cdot (a+bt)=O((a+bt)\log_{t}n)\)。
令\(f(t)=(a+bt)\log_t n=\dfrac{\ln n}{\ln t}(a+bt)\)。可以求得\(f'(t)=\ln n\left(\dfrac{b}{\ln t}-\dfrac{a+bt}{t\ln^2 t}\right)\)。
令\(f'(t)=0\),可以化简得到\(b=\dfrac{a+bt}{t\ln t}\)。
代入\(a=5,b=10\),使用Mathematica求解这个方程,可以得到\(t\approx 3.18097\)。由于\(t\)为整数,且\(f(3)<f(4)\),因此取\(t=3\)是一个最优值。