Skip to content

Latest commit

 

History

History
73 lines (47 loc) · 6.79 KB

Chapter-Eight.md

File metadata and controls

73 lines (47 loc) · 6.79 KB

第八章 算法设计技术

1. 算法 3 和算法 4 使用的代码比较复杂,也很容易出错。请使用第 4 章中的程序验证技术证明代码的正确性,指定循环不变式时请务必小心。

David Gries 在 1982 年第 2 期的 Science of Computer Programming 第 207 页~第 214 页的“A Note on the Standard Strategy for Developing Loop Invariants and Loops”一文中系统地推导并验证了算法 4。

2. 请在你的机器上对本章中的四种算法计时,建立与 8.5 节相类似的表。

3. 我们对四种算法的分析仅限于大 O 层面。请尽可能精确地分析每种算法调用 max 函数的次数。本题对你分析这些程序的运行时间有何启示?每种算法需要多少空间?

算法 1 大约对函数 max 进行了 n^3/6 次调用,算法 2 大约进行了 n^2/2 次调用,算法 4 大约进行了 2n 次调用。算法 2b 为累加数组使用了线性的额外空间,算法 3 为栈使用了对数的额外空间。其他算法仅使用了常数的额外空间。算法 4 是实时的:一趟输入完毕它就计算出答案,这特别适用于处理磁盘文件。

4. 如果输入数组中的各个元素都是从区间[-1,1]中均匀选出的随机实数,那么最大子向量的期望值是多少?

绘制随机遍历的累加和。

5. 为简单起见,我们允许算法 2b 访问 cumarr[-1]。如何使用 C 语言处理该问题?

如果将 cumarr 声明成 float *cumarr 那么赋值 cumarr = realarray + 1 将意味着 cumarr[-1] 指向 realarray[0]。

6. 证明任何计算最大子向量的正确算法都必须检测所有 n 个输入。(有些问题的算法可以正确地忽略某些输入;请思考答案 2.2 中 Saxe 的算法,以及 Boyer 和 Moore 的字串搜索算法。)

7. 当我第一次实现这些算法时,我总是使用脚手架将各种不同算法所产生的答案和算法 4 所产生的答案进行比较。当看到脚手架报告算法 2b 和算法 3 中的错误时,我很烦躁。但是当我仔细研究这些数值答案时,我发现它们尽管不一样,却非常接近。这意味着什么呢?

浮点加法不一定需要关联。

8. 修改算法 3(分治算法),使其在最坏情况下具有线性运行时间。

除了计算区域中的最大和之外,返回数组每端最大向量结束的信息。

9. 我们将负数数组的最大子向量的和定义为 0,即空向量的总和。假设我们重新定义,将最大子向量的和定义为最大元素的值,那么,应该如何修改各个程序呢?

使用赋值 maxsofar = -∞ 替换 maxsofar = 0。如果 -∞ 的使用让你迷惑,也可以使用 maxsofar = x[0],为什么?

10. 假设我们想要查找的是总和最接近 0 的子向量,而不是具有最大总和的子向量。你能设计出的最有效的算法是什么?可以应用哪些算法设计技术?如果我们希望查找总和最接近某一给定实数 t 的子向量,结果又将怎样?

初始化累加数组 cum,使得 cum[i]=x[0]+...+x[i]。如果 cum[l-1]=cum[u],那么子向量 x[l..u] 之和就为 0。因此,可以通过定位 cum 中最接近的两个元素来找出和最接近零的子向量;这可以通过排序数组,在 O(n log n) 时间内完成。这样得到的运行时间不超过最优时间的常数倍,因为任何能够解决这个问题的算法都能够用于解决“元素唯一性”问题(判断数组中是否包含重复元素。Dobkin 和 Lipton 证明“元素唯一性”问题所需的时间跟最坏情况下决策树模型的计算所需的时间差不多)。

11. 收费公路由 n 个收费站之间的 n-1 段公路组成,每一段公路都有相关的使用费。如果在 O(n) 时间内驶过两个收费站,并且仅使用一个费用数组;或在固定时间内驶过两个收费站,并且使用一个具有 O(n^2) 个表项的表,那么给出两站之间的行驶费很容易。请描述一个数据结构,该结构仅需要 O(n^2) 的空间,却可以在固定的时间内完成任意路段的费用计算。

假设收费公路是笔直的,则收费站 i 和收费站 j 之间的总费用为 cum[j]-cum[i-1],其中 cum 是类似上题的累加数组。

12. 将数组 x[0..n-1] 初始化为全 0 后,执行下面 n 个运算:

for i = [l, u]
    x[i] += v

其中 l、u 和 v 为每次运算的参数(l 和 u 为满足 0 <= l <= u < n 的整数,v 为实数)。完成这 n 次运算之后,x[0..n-1] 中的各个值将按顺序排列。上面刚刚描述的方法需要 O(n^2) 的运行时间。你能给出一个更快的算法吗?

本答案使用另一个累加数组。可以使用赋值语句:

for i = [l, u]
    x[i] += v

替代循环

cum[u] += v
cum[l-1] -= v

上面的两个赋值语句先对 x[0..u] 加上 v,然后再从 x[0..l-1] 中减去 v。这些和都计算完毕后,我们用下面的语句计算数组 x:

for (i = n - 1; i >= 0; i--)
    x[i] = x[i+1] + cum[i]

这样就把 n 次求和的最坏情况运行时间从 O(n^2) 降到了 O(n)。在 6.1 节描述的 Appel 的 n 体程序中,收集统计数的时候出现了这个问题。使用上述解决方案后,统计函数的运行时间从 4 小时降到了 20 分钟。当程序的执行需要一年时,这样的加速不是很重要;但是如果程序的执行只需要一天,这样的加速就非常重要了。

13. 在最大子数组问题中,给定 n * n 的实数数组,我们需要求出矩形子数组的最大总和。该问题的复杂度如何?

为了在 O(m^2n) 时间内找出 mn 的数组中总和最大的子数组,可以在长度为 m 的维度上使用算法 2 的方法,在长度为 n 的维度上使用算法 4 的方法。这样就可以在 O(n^3) 时间内解决 n*n 问题,这个结果在长达 20 年的时间内一直是最佳的。在 1998 年的 Symposium on Discrete Algorithms(第 446 页~第 452 页)上,Tamaki 和 Tokuyama 提出了一种稍快一些的算法,运行时间为 O(n^3[(loglogn)/(logn)]^0.5)。他们还给出了一种用于找出总和至少为最大值一半的子数组的 O(n^2logn) 近似算法,并介绍了其在数据挖掘中的应用。最理想的下界仍然正比于 n^2。

14. 给定整数 m、n 和实数向量 x[n],请找出使总和 x[i]+...+x[i+m] 最接近 0 的整数 i(0 <= i < n-m)。

15. 当 T(1)=0 且 n 为 2 的幂时,递推公式 T(n)=2T(n/2)+cn 的解是什么?请用数学归纳法证明你的结果。如果 T(1)=c,结果又怎样?