2. 算法基础总题

来源:互联网 发布:淘宝直通车类目出价 编辑:程序博客网 时间:2024/05/02 01:28

2-1 (在归并排序中对小数组使用插入排序)虽然归并排序的最坏情况运行时间为Θ(nlgn),而插入排序的最坏情况是Θ(n²),但是插入排序中的常量因子可能使得它在n较小时,在许多机器上实际运行的更快。因此,在归并排序中当子问题变的足够小时,采用插入排序来使递归的叶变粗是有意义的。考虑对归并排序的一种修改,其中使用插入排序来排序长度为k的n/k个子表,然后使用标准的合并机制来合并这些子表,这里k是一个待定的值。

  1. 证明:插入排序最坏情况可以再Θ(nk)时间内排序每个长度为k的n/k个子表
  2. 表明在最坏情况下如何在Θ(nlg(n/k))时间内合并这些子表
  3. 假定修改后的算法的最坏情况运行时间为Θ(nk+nlg(n/k)),要是修改后的算法与标准的归并排序具有相同的运行时间,作为n的一个函数,借助Θ记号,k的最大渐进值是什么?
  4. 在实践中,我们应该怎么选择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]
  1. 假设A'表示BUBBLESORT(A)的输出,为了证明BUBBLESORT正确,我们必须证明他将终止并且有:A'[1]≤A'[2]≤...A'[n],其中n=A.length。为了证明他确实完成了排序,我们还需要证明什么?
  2. 为第2-4行的for循环精确地说明一个循环不变式,并证明该循环不变式成立。你的证明应该使用本章给出的循环不变式证明结构。
  3. 使用2部分证明的循环不变式终止条件,为第1-4行的for循环说明一个循环不变式,该不变式将使你能证明1中的不等式。你的证明应该使用本章给出的循环不变式证明结构。
  4. 冒泡排序的最坏情况的运行时间是多少?于插入排序的运行时间相比,性能如何?

答:

问题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=0for 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)))

的霍纳规则。

  1. 借助Θ记号,实现霍纳规则的以上代码片段的运行时间是多少?
  2. 编写伪代码来实现朴素的多项式求值算法,该算法从头开始计算多项式的每个项,该算法的运行时间是多少?与霍纳规则相比性能如何?
  3. 考虑以下循环不变式:在第2-3行for循环每次迭代的开始有  
    y =
    把没有项的和式解释为等于0.遵照本章中给出的循环不变式证明结构,使用该循环不变式来证明终止时有 y =
  4. 最后证明上面给出的代码片段将正确地求由系数a0,a1,...,an刻画的多项式的值。

答:

问题1:Θ(n)

问题2:

朴素的算法(每次都重新计算了x^k。如果可以把x^k的值保留下来,下一次循环直接可以再乘x得到x^(k+1),这种情况不需要两层循环,也是Θ(n)

y = 0for 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).

  1. 列出数组<2,3,8,6,1>的5个逆序对
  2. 由集合{1,2,...,n}中的元素构成的什么数组具有最多的逆序对?他有多少逆序对?
  3. 插入排序的运行时间与输入数组中逆序对的数量之间是什么关系?证明你的回答。
  4. 给出一个确定在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   


















0 0