OpenDSA 完整目录

Chapter 7 Algorithm Analysis

| 关于   «  7. 下界与 \(\Theta\) 记法   ::   目录   ::   9. 分析问题  »

8. 计算程序运行时间

8.1. 计算程序运行时间

本模块讨论了对几种简单代码片段的分析。我们将使用算法分析简化规则:

  1. 如果 \(f(n)\) 在 \(O(g(n))\) 中且 \(g(n)\) 在 \(O(h(n))\) 中,那么 \(f(n)\) 在 \(O(h(n))\) 中。

  2. 如果对于任何常数 \(k > 0\) , \(f(n)\) 在 \(O(k g(n))\) 中,则 \(f(n)\) 在 \(O(g(n))\) 中。

  3. 如果 \(f_1(n)\) 在 \(O(g_1(n))\) 中且 \(f_2(n)\) 在 \(O(g_2(n))\) 中,那么 \(f_1(n) + f_2(n)\) 在 \(O(\max(g_1(n), g_2(n)))\) 中。

  4. 如果 \(f_1(n)\) 在 \(O(g_1(n))\) 中且 \(f_2(n)\) 在 \(O(g_2(n))\) 中,那么 \(f_1(n) f_2(n)\) 在 \(O(g_1(n) g_2(n))\) 中。

那其他的控制语句呢? While 循环的分析方式与 for 循环类似。 if 语句在最坏情况下的成本是 then 和 else 子句成本中的较大者。对于平均情况也是如此,假设 \(n\) 的大小不影响执行其中一个子句的概率(这通常是,但不一定是真的)。对于 switch 语句,最坏情况成本是代价最大的分支的成本。对于子程序调用,只需加上执行子程序的成本。

存在罕见的情况,执行 if 或 switch 语句中各分支的概率是输入大小的函数。例如,对于大小为 \(n\) 的输入, if 语句的 then 子句可能以 \(1/n\) 的概率被执行。一个例子是 if 语句,它仅在最小的 \(n\) 个值中执行 then 子句。要对此类程序进行平均情况分析,我们不能简单地将 if 语句的成本计为较昂贵分支的成本。在这种情况下, 摊销分析 技术可以派上用场。

确定递归子程序的执行时间可能很困难。递归子程序的运行时间通常最好用递推关系来表示。例如,递归阶乘函数会以比输入值小一的值调用自身。然后,将递归调用的结果乘以输入值,这需要常数时间。因此,如果我们希望以乘法操作的数量来衡量成本,那么阶乘函数的成本比递归调用在较小输入上所做的乘法数量多一次。因为基本情况不做乘法,其成本为零。因此,该函数的运行时间可以表示为

\[T(n) = T(n-1) + 1 \ \mbox{for}\ n>1;\ \ T(1) = 0.\]

该递推关系的封闭形式解是 \(\Theta(n)\) 。

8.1.1. 案例研究:两种查找算法

本节算法分析的最后一个例子将比较两种在数组中执行查找的算法。之前,我们确定在搜索值 \(K\) 等可能出现在任何位置的数组上执行顺序查找的运行时间是 \(\Theta(n)\) ,在平均情况和最坏情况下都是如此。我们希望将此运行时间与在值按从低到高顺序存储的数组上执行 二分查找 所需的运行时间进行比较。以下是二分查找方法的可视化图。

Settings

Proficient Saving... Error Saving
Server Error
Resubmit

8.1.2. 二分查找练习

8.1.3. 分析二分查找

Settings

Proficient Saving... Error Saving
Server Error
Resubmit

函数 binarySearch 旨在查找 \(K\) 的(单个)出现位置并返回其索引。如果 \(K\) 不在数组中,则返回一个特殊值。此算法可修改以实施变体,例如当允许多个出现时返回 \(K\) 在数组中首次出现的索引,或在 \(K\) 不在数组时返回小于 \(K\) 的最大值的索引。

将顺序查找与二分查找相比,我们看到,随着 \(n\) 的增长,平均情况和最坏情况下的 \(\Theta(n)\) 顺序查找的运行时间迅速变得远大于 \(\Theta(\log n)\) 二分查找的运行时间。孤立来看,二分查找似乎比顺序查找高效得多。尽管二分查找的常数因子大于顺序查找,因为二分查找中计算下一个搜索位置比顺序查找中仅仅递增当前位置更昂贵。

然而,请注意顺序查找的运行时间无论数组值是否按顺序存储大致相同。

8.2. 摘要练习

   «  7. 下界与 \(\Theta\) 记法   ::   目录   ::   9. 分析问题  »

关闭窗口