算法导论第6章习题
Exercises
6.1-1 2^h~2^{h+1}-1,如果root是第1个,而不是第0个。
6.1-2 设heap高度为h,2^h<=n<=2^{h+1}-1,取对数得到\floor{lgn}
6.1-3 传递性, Cormen用的是反证法
6.1-4 因为smallest element在heap中没有children,所以只能在leaf
6.1-5 YES. 在已排序的数组里A[i]的children A[2*i]和A[2*i+1]在A[i]后,所以两个children比A[i]小,所以是min-heap。
6.1-6 No.
6.1-7 用图中的出度=入度计算。废话了点。如果n是奇数,出度=2*#中间节点;入度=#中间节点-1(root没有入度)+#叶子节点。得到#中间节点=#叶子节点-1。总共有n个节点,#中间节点=(n-1)/2。所以第一叶子的index是(n+1)/2=\floor{n}/2。如果是n是偶数,#中间节点=#叶子节点,所以叶子节点从n/2+1=\floor{n/2}+1开始。
6.2-1 略
6.2-2 MIN-HEAPIFY(A, i, n) 把MAX-HEAPIFY中的Line3-7改成寻找最小的element,O(lgn)
6.2-3 MAX-HEAPIFY中的Line8 if语句不成立,函数执行完毕
6.2-4 i>heap-size[A]/2时,由6.1-7知A[i]是叶子节点,没有children,所以MAX-HEAPIFY在Line3跳出
6.2-5 好的编译器可以实现尾递归。伪代码:
MAX-HEAPIFY(A, i) while i < floorA.heap-size/2 l=LEFT(i); r=RIGHT(i); if A[l] > A[i] largest=l; else largest=i; if A[r] > A[largest] largest=r; if largest!=i exchange A[i] with A[largest] i = largest;
6.2-6 对root用MAX-HEAPIFY,如果root值是heap中的最小元那么MAX-HEAPIFY一直会执行碰到一个leaf。因为总共有\Theta(lgn)层,所以时间复杂度是\Theta(lgn),worst-case的时间复杂度\Omega(lgn).
6.3-1 略
6.3-2 BUILD-MAX-HEAP中Line2为什么从\floor(n/2)递减至1,而不是递增?BUILD-MAX-HEAP中间要对A[i]要调用MAX-HEAPIFY,前提A[i]的两个children必须是max-heap的root。如果BUILD-MAX-HEAP中改为递增后,A[i]的两个children都不一定是max-deap。
6.3-3 证明在高度为h的节点中最多有\ceiling{n/2^{h+1}}个节点。一开始这个题很容易与depth深度混淆。如果heap不是一棵完整的二叉树的话,那么有一层的节点不都有高度h。高度heigh是从leaf算起,而depth从root算起。
如果是完全二叉树,结论显而易见。如果不是的话,证明就没有那么简单了。CLRS中有很多看似想当然的东西其实很有难度的题目。用数学归纳法(CLRS中证明算法的correctness都是用loop invariant跟数学归纳法一回事)。
H=0时的节点都是leaf,由前面的6.1-7得到n为偶数是,internal和leaf数量相等,所以确实有n/2=\ceiling{n/2}。n为奇数时,internal node比leaf少一个,所以leaf的数量=\ceiling{n/2}。
假设有H=h的节点有N_h=\ceiling{n/2^h}个。在这棵不完全二叉树中把H=h, h-1, h-2, …, 0高度的节点去掉,剩下n’个节点。那么H=h-1高度的节点都是叶子节点,那么,H=h-1的节点数目为\ceiling{n’/2}<=\ceiling{(#H=h的节点)/2}<=\ceiling{n/2^{h+1}}.QED.
6.4-1 6.4-2 略
6.4-3 increasing order: O(n)+n*O(lgn)=O(nlgn); decreasing order: O(1)+n*O(lgn)=O(nlgn) [只计算交换元素的步骤?]
6.4-4 worst-case running time: 在这里用decision-tree来说heapsort有\Omega(nlgn)时间复杂度都是偷懒。worst-case heapsort发生在root与A[heapsize+1]交换时MAX-HEAPIFY要使root下降到最下一层。由6.2-6知道,MAX-HEAPIFY worst case是\Omega(lgn),所以heapsort worst case是\Omega(nlgn)
6.4-5 这是最难的一道关于heap的题目了。Heapsort提出在1964,但是分析却是在1992年.Schaffer and Sedgewick's "The Analysis of Heapsort" paper.(Sedgewick是Knuth的高徒,也写过一本非常不错的算法书)其中的证明参考一篇stackoverflow的帖子。CLRS为什么要出这样的题呢,因为很多人都没有注意到一个情况:MAX-HEAPIFY best-case只要O(1)就够了,所以“有可能”heapsort只要O(n)就够了。(不要用decision tree来扯)。那这种情况会不会在MAX-HEAP上发生呢?[真讨厌latex画图,Win的机子也没有装latex,ASCII将就一下。]虽然heapsort是棵近似的完全二叉树,左右节点的高度不会超过1.但是那些较小的元素有可能不在leaf节点上。这样的话在用MAX-HEAPIFY的时候,新的root有可能下降到中间几层。比如
7
3 6
1 2 4 5
这里的3在第1层。如果把5换到7的位置,那么5只要下降1层就够了。这个例子自己认为是"best heap"。
Stackoverflow那个帖子倒数两段有2个地方笔误了,也没有讲不是完全二叉树的情况。拿完全二叉树知道大概意思吧:先假设一个有n=2k-1节点组成的max-heap,完全二叉树,高度k,深度从0到k-1。在heapsort中做的工作相似,我们把node一个一个从A[1]里取出来,把最后的元素放进去,MAX-HEAPIFY一下。考虑从中取出\floor{n/2}个node,记为集合X。他们中部分node会分布在原来max-heap中0-k-2层,记为X_1,部分node在leaf中,记为X_2。X_1集合中的node要向从heap中弹出来,他们每次只能向上走一层。所以我们只要计算X_1集合中的node就能得到heapsort的下界(包括worst-case和best-case)。
首先我们要知道总共有多少个最大的2^{k-1},即最大的n/2个节点有多少在最底层。答案是最多有2^{k-2}个。在上面这个例子中就是4和5在bottom level。这个很绕的证明先不管,如果是的话,至少有2^{k-2}个在0~k-2层中,也就是除掉最低层的其他层。那么,k-3层最多有2^{k-3}个,那么这些点中最多有2^{k-3}个在0~k-3层中(这个地方也比较跳跃,估计是自己太菜的缘故,最后一段补充),那么至少有2^{k-3}个X_1的点在k-2中,即倒数第2层中。这些节点到root的距离之和为(k-2)2^{k-3},k=\Theta(lgn),所以要使得这些点跑到root里要\Theta(nlgn)。因为我们只考察了一半的node,所以heapsort至少要\Omega(nlgn)。
再讲中间那个证明:如果在n/2最大的node(那些大于数组中位数的点)中有至少2^{k-2}+1个在最底层。那么这些node的大parent将会在k-2层。这样的parent至少有2^{k-3}+1个。以此类推,一直到0层root,那么这些大的node至少有2^{k-1}+k个大于中位数的node。这是不可能的,顶多有2^{k-1}个大于中位数的点。
终于完了。在不完全二叉树的证明看原始论文吧。结论是heapsort的best-case的时间复杂度是~1/2*nlgn。
自己的注解: k-2层由最大n/2个节点集合X和剩下的点集合A-X构成。因为在最底层的X集合的点要比A-X的点大,所以他们的parent必然也是X集合的点。如果0~k-3层多于2^{k-3}个节点多1,那么k-2层得节点只有2^{k-3}也要少1个,那么最底层k-1层的X集合的点也要减少。所以X集合中点的个数减少了,矛盾。
现在又想到一个直观的方法。"best"heap是尽量使得大的node靠近底层,他们的parent自然也是大的点。如果用小的点替换root,使它下降的话,那么只要走很少的层数就够了。把max-heap看成一个三角形,左边一半x是最大的n/2个node,右边一半是最小的n/2个node。如果有比这更“好”的heap的话,那么x的一个node要移到Y中,Y中有一个node要移到X中,但是这违反了max-heap。这样一个heap中要让左边的X节点从root中出去,必然还是要上升\Theta(lgn)的层数,共有n/2个,那么时间复杂度至少\Omega(nlgn)。
6.5-1 6.5-2 6.5-3 略
6.5-4 没有练code的原因没想到。因为heap是用数组来表示,当delete一个node是仅仅是把A[1]放到数组后面去,他的key可能还没有改变。所以用-infinity赋值一下来消除未初始化的情况。否则HEAP-INCREASE-KEY可能会出错。
6.5-5 略
6.5-6 CLRS第3版新加了一道题。要求用插入排序insertion sort中inner loop使得HEAP-INCREASE-KEY中Line5中的3次赋值变为1次赋值。一般来说exchange要用到3个变量,c=a,a=b,b=c。先把那些小的元素下降到它的children,然后把key插入到正确的位置。
HEAP-INCREASE-KEY(A, i, key) if (key < A[i]) Error; while (i>1 and A[PARENT(i)] < key) A[i]=A[PARENT(i)]; i=PARENT(i); A[i]=key;
6.5-7 实现FIFO queue。只要使得先来的element大于heap的MAXIMUM即可。这是用堆实现的queue。
6.5-8 实现HEAP-DELETE(A, i)
def HEAP-DELETE(A, i): A[i]=A[A.heap-size] A.heap-size=A.heap-size-1 MAX-HEAPIFY(A,i)
6.5-9 O(nlgk),用这k个list的第1个元素构成有k个node的min-heap。每次从中抽取最小的node,加入这个node所在list中的下面一个node,直到排序完所有元素。
Problems
6-1 Building a heap using insertion 用插入方法建堆
a. No, 显而易见
b. BUILD-MAX-HEAP’在worst-case要\Theta(nlgn)的时间复杂度,明显多于标准的BUILD-MAX-HEAP的O(n)。证明\Theta时间复杂性要从上界和下界同时证明。上界易得:MAX-HEAP-INSERT要O(lgn),所以BUILD-MAX-HEAP’要O(nlgn)。下界:worst-case是数组按升序排列,每一次MAX-HEAP-INSERT需要从leaf跑到root。
6-2 Analysis of d-ary heaps 对d叉堆的分析
a. to children: D-ARY-CHILDREN(i,j): d(i-1)+j+1; D-ARY-PARENT(i): \floor{(i-2)/d+1}
b. \Theta(\log_d n)
c. like HEAP-EXTRACT-MAP,但是在MAX-HEAPIFY中要比较i和它d个children,worst-case时间复杂度\Theta(d log_d n)
d. MAX-HEAP-INSERT, worst-case时间复杂度\Theta(\log_d n)
e. D-ARY-INCREASE-KEY也是同样地比较节点与parent的大小,可以采用6.5-6(第3版新题)优化方法。worst-case时间复杂度还是Theta(\log_d n)
6-3 Young tableaus Young氏矩阵
a. Young tableaus可以用Z折线从小到大排列出来,X表示正无穷大
2 4 5 X
3 8 16 X
9 14 X X
12 X X X
b. 如果Y[1][1]是正无穷大的话,那么对于任何一个元素Y[x][y]都有一条路Y[x][y] >= Y[x][y-1] >= … >= Y[x][1] >= Y[x-1][1] >= … >= Y[1][1] >= 正无穷大,所以Y[x][y]是正无穷大,Young tableaus为空。同理,若Y[m][n]=M<正无穷大,那么也用这样的一条路使得Y[x][y]<=M, x<=m, y<=n,所以Young tableaus为满。
c. EXTRACT-MIN(Y)
def EXTRACT-MIN(Y): minimum = Y[1][1] Y[1][1] = INFINITY YOUNG-TABLEAUSIFY(Y, 1, 1) return minimum def YOUNG-TABLEAUSIFY(Y, m, n): smallest = Y[m][n] if u <= Y.M and Y[m+1][n] < smallest: smallest = Y[m+1][n] u = m + 1 v = n if v <= Y.N and Y[m][n+1] < smallest: smallest = Y[m][n+1] v = n + 1 u = m if not(u == m and v == n): Y[u][v] = Y[m][n] Y[m][n] = smallest YOUNG-TABLEAUSIFY(Y, u, v)
T(p)=T(m+n)=T(m+n-1)+O(1)=O(m+n)
d. YOUNG-TABLEAUS-INSERT(Y, m, n)
如果Young tableaus为Y[m][n]若为∞,则Y[m][n]=x, 否则,返回full。找出Y[m-1][n]和Y[m][n-1],Y[m][n]中最小的一个,与其交换,保证不要越界。如果自己与自己交换则停止,否则的话,YOUNG-TABLEAUS-INSERT(Y, u, v),u,v是所交换元素的坐标。一直到停止或者到了Y[1][1]。所以也是O(m+n).
e.不断的EXTRACT-MIN(Y), n*n*O(2n)=O(n^3)
f. IS-IN-YOUNG(Y, key) 只要把key和Y最右上角元素x比较就可以了。x刚好是第一行和最后一列的中位数,是第一行的最大值,最后一列的最小值。如果key比x大,那么去掉第一行。如果key比x小,去掉最后一列,继续搜索。直到找到或者没找到为止。每次必然减少一行或者一列,所以时间复杂度是O(m+n)。
据说这个还是某度的面试题,不过那里的矩阵是方阵,没有∞元素。