2-1 (在归并排序中对小数组使用插入排序)虽然归并排序的最坏情况运行时间为Θ(nlgn),而插入排序的最坏情况是Θ(n²),但是插入排序中的常量因子可能使得它在n较小时,在许多机器上实际运行的更快。因此,在归并排序中当子问题变的足够小时,采用插入排序来使递归的叶变粗是有意义的。考虑对归并排序的一种修改,其中使用插入排序来排序长度为k的n/k个子表,然后使用标准的合并机制来合并这些子表,这里k是一个待定的值。
- 证明:插入排序最坏情况可以再Θ(nk)时间内排序每个长度为k的n/k个子表
- 表明在最坏情况下如何在Θ(nlg(n/k))时间内合并这些子表
- 假定修改后的算法的最坏情况运行时间为Θ(nk+nlg(n/k)),要是修改后的算法与标准的归并排序具有相同的运行时间,作为n的一个函数,借助Θ记号,k的最大渐进值是什么?
- 在实践中,我们应该怎么选择k?
答:
先写伪代码:
具体方法的代码省略了,参考前面的。MERGE_INSERTION(A,p,q) if q-p>k m=(p+q)/2 MERGE_INSERTION(A,p,m) MERGE_INSERTION(A,m+1,q) MERGE(A,p,m,q) else INSERTION(A,p,q)
问题1:
插入排序的时间是Θ(n²),当规模为k,执行次数为n/k时,(n/k)*Θ(k²)=Θ(nk) 不知道可不可以这样
问题2:
合并过程的时间是Θ(n),现在需要确定的是执行次数。
由于只需要将递归树展开到k,所以一共是lgn - lgk 层,也就是lg(n/k)层,每层是Θ(n),所以是Θ(nlg(n/k))。
问题3:
这里是不太懂所以借鉴了别人的答案
T(nk+nlg(n/k))=Θ(nlgn)
观察nk这一项,可以发现除非k≤Θ(lgn),否则将会有T(nk+nlg(n/k))>Θ(nlgn)。
设k = Θ(lgn)
T(nk+nlg(n/k))=Θ(nlgn+n(lg(n/lgn)))=Θ(nlgn+nlgn-nlg(lgn)) = Θ(2nlgn-nlg(lgn))
按照大O符号的规则简化,得到Θ(nlgn)
发现题干得证。
问题4:
选取插入排序比归并排序快的输入规模作为k的值。(话虽这么说。。怎么确定啊?)
2-2(冒泡排序的正确性)冒泡排序是一种流行但低效的排序算法,他的作用是反复交换相邻的未按次序排序的元素。
BUBBLESORT(A) for i=<span style="font-family:Courier New;">1</span> to A.length-1 for(j=A.length downto i+1) if A[j]<A[j-1] exchange A[j] with A[j-1]
- 假设A'表示BUBBLESORT(A)的输出,为了证明BUBBLESORT正确,我们必须证明他将终止并且有:A'[1]≤A'[2]≤...≤A'[n],其中n=A.length。为了证明他确实完成了排序,我们还需要证明什么?
- 为第2-4行的for循环精确地说明一个循环不变式,并证明该循环不变式成立。你的证明应该使用本章给出的循环不变式证明结构。
- 使用2部分证明的循环不变式终止条件,为第1-4行的for循环说明一个循环不变式,该不变式将使你能证明1中的不等式。你的证明应该使用本章给出的循环不变式证明结构。
- 冒泡排序的最坏情况的运行时间是多少?于插入排序的运行时间相比,性能如何?
答:
问题1:
还要证明A'中的元素全部都是A中原来的元素。
问题2:
循环不变式是,在A[j..n]中,A[j]是最小的那个。
初始化:循环开始齐纳,j=n。A[j..n]只有一个元素,无需讨论。
保持:循环中,A[j]是最小的,若A[j]比A[j-1]更小,则会被与A[j-1]交换,则A[j-1]成为A[j-1..n]中最小的,这为下一个循环奠定了循环不变式的成立。
终止:循环结束时,j = i, A[i]为A[i..n]中最小的元素。
问题3:
循环不变式是,对于每次循环A[1..i-1]都是已经排序好了的。
初始化:循环开始前,i=1,A[1..0]视为排序完毕的。
保持:由于2,每一次的循环,都把A[i..n]中最小的元素放到A[i]的位置,所以每一次循环都保证了下一次循环开始之前A[1..i-1]是排序好了的。
终止:循环结束时,i=n+1,所以A[1..n]排序完成。
问题4:
外层循环n次,内层循环最多n次,由于只取最高项,所以可以大略估算,为Θ(n²),和插入排序一样。
但是插入排序的best-case情况下,只需要Θ(n),而冒泡无论如何都是Θ(n²),所以,还是插入排序要好一些。
2-3 (霍纳Horner规则的正确性)给定系数a0,a1,...,an和x的值,代码片段:
y=0
for i=n downto 0
y = ai+x*y
实现了用于求值多项式:
P(x) =
= a0 + a1*x + a2*x^2 +...+an*x^n
= a0 + x(a1 + x(a2 + ... + x(an-1 + x*an)))
的霍纳规则。
- 借助Θ记号,实现霍纳规则的以上代码片段的运行时间是多少?
- 编写伪代码来实现朴素的多项式求值算法,该算法从头开始计算多项式的每个项,该算法的运行时间是多少?与霍纳规则相比性能如何?
- 考虑以下循环不变式:在第2-3行for循环每次迭代的开始有
y =把没有项的和式解释为等于0.遵照本章中给出的循环不变式证明结构,使用该循环不变式来证明终止时有 y = - 最后证明上面给出的代码片段将正确地求由系数a0,a1,...,an刻画的多项式的值。
答:
问题1:Θ(n)
问题2:
朴素的算法(每次都重新计算了x^k。如果可以把x^k的值保留下来,下一次循环直接可以再乘x得到x^(k+1),这种情况不需要两层循环,也是Θ(n))
y = 0 for i = 0 .. n m = 1 for j = 1..i m = m*x y = ai * m
里外两层循环,所以Θ(n²),肯定是比Θ(n)要差的。问题3:
初始化:在循环开始前,y=0,i=n,此时
k = 0..-1,此时没有项,为0,符合预期.
保持:每次循环都会 有
,将x并入求和式中:
可以发现,如果设k=-1,ai可以变化为
,满足求和式,并入求和式中得:
,设 k' = k+1,得到
当下一次循环,i' = i-1,代入后得到:
,循环不变式被重新建立。
终止:终止时,i=-1,所以有:
成立。
问题4:
问题3明明已经证明了。。。不知道还要怎么证明啊。。
证明当n=0的时候成立?n=0,y=a0,成立。
剩下的见问题3.
2-4 (逆序对)假设A[1..n]是一个有n个不同数的数组,若i<j且A[i]>A[j],则对偶(i,j)称为A的一个逆序对(inversion).
- 列出数组<2,3,8,6,1>的5个逆序对
- 由集合{1,2,...,n}中的元素构成的什么数组具有最多的逆序对?他有多少逆序对?
- 插入排序的运行时间与输入数组中逆序对的数量之间是什么关系?证明你的回答。
- 给出一个确定在n个元素的任何排列中逆序对数量的算法,最坏情况需要Θ(nlgn)时间。(提示:修改归并排序)
答:
问题1:
<2,1> <3,1> <8,1> <8,6> <6,1>
问题2:
降序排列的数组具有最多逆序对。
每一个元素,对他之后的多有元素都能构成逆序对。
从第一个元素开始到倒数第二元素结束,分别具有n-1,n-2,n-3,...,1个逆序对。求和:
[1+(n-1)]/2 *(n-1) =n(n-1)/2个逆序对。
问题3:
逆序对越多,插入排序需要的时间越长。
逆序对的多少直接决定了内层循环的次数,逆序对越多,需要执行移位的次数就越多。
问题4:
伪代码:
主要修改MERGE方法
MERGER(A,p,q,r) A1 = A[p..q] A2 = A[q+1..r] i=1,j=1 count = 0 for k=p to r if i > A1.length A[k] = A2[j] j++ else if j>A2.length A[k] = A1[i] i++ else if A1[i] >A2[j] count = count + A.length - i + 1 A[k] = A2[j] j++ else A[k] = A1[I] i++ return count