第二章 算法基础 - 思考题

2-1 在归并排序中对小数组采用插入排序

【描述】虽然归并排序最坏情况运行时间为 \Theta(nlgn),而插入排序的最坏运行时间是 \Theta(n^2),但插入排序的常量因子可能使它在较小规模的 n 时,在许多机器上执行得更快。因此在归并排序中当子问题足够小时,使用插入排序来使递归的叶变粗是有意义的。考虑对归并排序进行一种修改,其中使用插入排序来排序长度为 k 的 n/k 个子表,然后使用标准的合并机制来合并这些子表,这里 k 是一个特定的值。

(1)证明:插入排序最坏情况下可以在 \Theta(nk) 时间内排序每个长度为 k 的 n/k 个子表。

(2)证明:在最坏情况下合并这些子表所需要的运行时间为 \Theta(nlg(n/k)))

(3)假定修改后的算法的最坏情况运行时间为 \Theta(nk+nlg(n/k))),要使修改后的算法与标准的归并排序具有相同的运行时间,作为 n 的一个函数,借助 \Theta 记号,k 的最大值是什么?

(4)在实践中,我们应该如何选择 k


【2-1 解答】

(1)证明:

已知插入排序在规模为 n 时,最坏情况运行时间为 \Theta(n^2),因此每个长度为 k 的子表最坏情况运行时间为 \Theta(k^2)。那么, n/k 个子表最坏情况运行时间为 \Theta(k^2) * n/k = \Theta(nk)

(2)证明:

归并排序会构造一个二叉树,自底向上进行归并,每一层两两合并左右节点有序子表至根节点,无论左右每一对有序子表长度如何,一层的所有子表总长度总是为 n,因此。每一层的比较次数总是为 n,每一层的比较耗时为 \Theta(n)

由于归并排序拆分出了  n/k 个子表,因此其构造的二叉树的层数为 lg(n/k)

结合上述结论即可证明,整棵二叉树全部运行耗时为 \Theta(n) * lg(n/k) = \Theta(nlg(n/k))

(3)求解:

令 \Theta(nk+nlg(n/k)) = \Theta(nlgn),则有 \Theta(k+lg(n/k)) = \Theta(lgn)

当 k=1 时,等式成立。

当 k=lgn 时,\Theta(lgn+lg(\frac{n}{lgn})) = \Theta(lgn) 中后一项增长级数不如前一项,所以\Theta(lg\frac{n}{lgn}) 可以忽略不计,因此等式成立。

故,k=lgn

 (4)求解:

所选取的 k 应该小于 \Theta(lgn)


2-2 冒泡排序的正确性

【描述】冒泡排序是一种流行但低效的排序算法,它的作用是反复交换相邻的未按次序排列的元素。伪代码如下:

  BUBBLE-SORT(A)
1     for i = 1 to A.length - 1
2         for j = A.length downto i + 1
3             if A[j] < A[j-1]
4                 exchange A[j] with A[j-1]

(1)假设 A' 表示 BUBBLE-SORT(A) 的输出。为了证明 BUBBLE-SORT 正确,我们必须证明它将终止,并且有:

A'[1]\leqslant A'[2]\leqslant \cdot \cdot\cdot \leqslant A'[n] \qquad (2.2)

其中,n = A.length。为了证明 BUBBLE-SORT 确实完成了排序,我们还需要证明什么?

(2)为第 2~4 行的 for 循环精确说明一个循环不变式,并证明该循环不变式成立。

(3)使用(2)证明的循环不变式终止条件,为 1~4 行的 for 循环说明一个循环不变式,该不变式能使你证明不等式(2.2)。试着证明。

(4)冒泡排序最坏运行时间是多少?与插入排序相比性能如何?


【2-2 解答】

(1)解答:

还需要证明 A' 中元素均来自于 A

(2)证明:

  • 循环不变式:子数组 A[j...n] 中的元素仍然在 A[j...n] 中,且 A[j] = min\left \{ A[k]\quad|\quad j \leqslant k \leqslant n \right \}
  • 初始化:第一次迭代之前,j=n,子数组 A[j...n] 中只有一个 A[j],显然,成立。
  • 保持:迭代一个给定值的 j。首先假设此次迭代前循环不变式成立,那么根据循环不变式,A[j] 是 A[j..n] 中最小的元素。第 3 ~ 4 行代码表示如果 A[j]<A[j-1] 就交换 A[j]A[j-1],显然这使得 A[j-1] 成为 A[j-1..n] 中最小的元素。由于唯一改变子数组 A[j-1..n] 的操作仅仅是那次可能发生的交换操作,且在迭代开始时,A[j..n] 中的元素最初都是在 A[j..n] 中,所以在迭代开始时 A[j-1..n] 中的元素最初都是在 A[j-1..n] 中。然后 j 自减,准备开始进入下一轮迭代。
  • 终止:循环终止时 j=i。根据循环不变式,A[j]=A[i]=min{A[k] | i<=k<=n},并且 A[i..n] 中的元素最初都在 A[i..n] 中。 所以在 2 ~ 4 行的 for 循环执行结束过后,A[i] 将是 A[i..n] 中最小的元素。

 证毕。

(3)证明:

  • 循环不变式:子数组 A[1..i-1] 包含了 A[1..n] 中前 i-1 小的所有元素,并且它们是已排好序的。A[i..n] 中包含了 A[1..n] 中余暇的元素。
  • 初始化:第一次迭代之前 i=1。子数组 A[1..i-1] 为空,循环不变式显然成立。
  • 保持:迭代一个给定值的 i。首先假设此次迭代前循环不变式成立,那么根据循环不变式,A[1..i-1] 包含了 A[1..n] 中前 i-1 小的所有元素,并且它们是已排好序的。第一部分已经证明:在执行 2 ~ 4 行的 for 循环后 A[i] 是 A[i..n] 中最小的元素。所以在执行了 2 ~ 4 行的 for 循环后 A[1..i] 中就包含了 A[1..n] 中前 i 小的所有元素,并且它们已经排好序。子数组 A[i+1..n] 就包含了 n-i 个 A[1..n] 中余下的元素。
  • 终止:循环终止时 i=n+1 \Rightarrow i-1=n。所以根据循环不变式,A[1..i-1] 中包含了 A[1..n] 中前 i-1 小的元素(即 A[1..n] 的全部元素),并且它们是排好序的。

 证毕。

(4)解答:

冒泡排序的最坏运行时间是 \Theta(n^2)

最坏情况下,冒泡排序与插入排序耗时几乎差不多,但是最好情况下,冒泡排序比插入排序性能差。


2-3 霍纳(horner)规则的正确性

【描述】给定系数 a_0, a_1, \cdot \cdot \cdot, a_n 和 x 的值,代码片段:

1 y = 0
2 for i = n downto 0
3    y = a[i] + x * y

实现了用于求值多项式

P(x) = \sum_{k=0}^{n}a_kx^k = a_0+x(a_1+x(a_2+\cdot\cdot\cdot+x(a_{n-1}+xa_n)\cdot\cdot\cdot))

的霍纳规则。

(1)说明上述代码片段的运行时间。

(2)编写伪代码来实现朴素的多项式求值算法,该算法从头开始计算多项式的每一个项。并说明它的运行时间是多少?与霍纳规则相比,性能如何?

(3)考虑以下循环不等式:在第 2~3 行 for 循环每次迭代的开始有

y = \sum_{k = 0}^{n-(i+1))}a_{k+i+1}x^k

把没有项的和式解释为等于 0。遵循本章中给出的循环不变式证明的结构,使用该循环不变式来证明终止时有 y = \sum_{k=0}^{n}a_kx^k

(4)最后证明上面给出的代码片段将正确地求由系数 a_0, a_1, \cdot \cdot \cdot, a_n 描述的多项式的值。


【2-3 解答】

(1)解答:

运行时间为 \Theta(n)

(2)解答:

朴素的多项式求值算法伪代码:

NAIVE-HORNER(a, x)
    y = 0
    for k = 0 to a.length
        temp = 1
        for i = 1 to k
            temp = temp * x
        y = y + a[k] * temp
    return y

上述朴素多项式求值算法的运行时间为 \Theta(n^2),比霍纳规则慢了一个数量级。

(3)证明:

  • 初始化:第一次迭代前,y = 0,成立
  • 保持: 在第 i 次循环的结尾,有:

  • 终止:循环终止时,i=-1,有:

证毕。

 (4)显然,循环的不变量是与给定系数的多项式相等的和。

 


2-4 逆序对

【描述】假设A[1...n] 是一个有 n 个不同数的数组。若 i < j 且 A[i] > A[j],则对偶 (i, j) 称为 A 的一个逆序对。

(1)列出数组 {2,3,8,6,1} 的 5 个逆序对。

(2)由集合 {1, 2, ..., n} 中元素构成的什么数组具有最多的逆序对?它有多少个逆序对?

(3)插入排序的运行时间与输入数组中逆序对的数量之间是什么关系?证明你的回答。

(4)给出一个确定在 n 个元素的任何排列中逆序对数量的算法,最坏情况需要 \Theta(nlgn) 时间。(提示:修改归并排序)


【2-4 解答】

(1)解答:

(1,5)\quad(2,5)\quad(3,5)\quad(4,5)\quad(3,4)

(2)解答:

全递减序列 \left \{ n, n-1,...,1 \right \} 具有最多的逆序对。

逆序对个数:(n-1)+(n-2)+...+1 = n(n-1)/2

(3)解答:

插入排序运行时间与输入数组中逆序对数量呈正相关。

因为逆序对正巧一一对应着插入排序过程中元素之间的比较操作。

(4)解答:

MERGE-INVERSIONS(A, p, q, r)
    m = q - p + 1
    n = r - q
    let L[1...m+1] and R[1...n+1] be new arrays
    for i = 1 to m
        L[i] = A[p + i - 1]
    for j = i to n
        R[j] = A[q + j]
    L[m+1] = INF
    R[n+1] = INF
    i = 1
    j = 1
    inversions = 0
    for k = p to r
        if L[i] <= R[j]
            A[k] = L[i]
            i = i + 1
        else 
            inversions = inversions + m - i + 1
            A[k] = R[j]
            j = j + 1
    return inversions
COUNT-INVERSIONS(A, p, r)
    if p < r
        q = floor((p + r) / 2)
        left = COUNT-INVERSIONS(A, p, q)
        right = COUNT-INVERSIONS(A, q+1, r)
        inversions = MERGE-INVERSIONS(A, p, q, r) + left + right
        return inversions

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值