多项式复杂度、指数复杂度、复杂性类的比较

1,352 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第20天,点击查看活动详情

多项式复杂度

最常用的多项式算法是二次式的,即它们的复杂性随着输入大小的平方而增长。例如,考虑图 11-4 中的函数,该函数实现了一个子集测试。

image.png

每次到达内部循环时,都会执行顺序θ(1en (L2))次。函数is_subset将执行外循环阶次 θ(1en(L1))),因此内循环将达到阶数 θ(1en(L1)*1en(L2))。

现在考虑图 11-5 中的函数相交。构建可能包含重复项的列表的代码部分的运行时间显然是顺序 θ(1en(L1)*1en(L2))。乍一看,构建无重复列表的代码部分在 tmp 的长度上似乎是线性的,但事实并非如此。

image.png

计算表达式 e 不在结果中可能涉及查看结果中的每个元素,因此是 θ(1en(结果));因此,实现的第二部分是顺序 θ(1en(tmp)*1en(结果))。但是,由于结果和 tmp 的长度以 L1 和 L2 中较小的长度为界,并且由于我们忽略了加法项,因此相交的复杂性为 θ(1en(L1)*1en(L2))。.

指数复杂度

正如我们将在本书后面看到的那样,许多重要问题本质上是指数级的,也就是说,完全解决它们可能需要在输入大小上呈指数级的时间。这是不幸的,因为编写一个运行指数时间概率相当高的程序很少是值得的。例如,考虑图 11-6 中的代码。

image.png

函数 gen_powerset (L) 返回一个列表列表,其中包含 L 元素的所有可能组合。例如,如果 L 是 ['x', 'y]],则 L 的幂集将是一个包含列表 []、['x']、['y'] 和 ['x', 'y'] 的列表。

算法有点微妙。考虑一个包含 n 个元素的列表。我们可以通过 n 个 0 和 1 的字符串来表示元素的任意组合,其中 1 表示元素的存在,而 o 表示元素不存在。不包含项目的组合由所有 o 的字符串表示,包含所有项目的组合由所有 1 的字符串表示,仅包含第一个和最后一个元素的组合由 100..001 表示,依此类推。

生成长度为 n 的列表 L 的所有子列表可以按如下方式完成:

生成所有 n 位二进制数。这些是从 o 到 2n-1 的数字。

对于这些 2“ 二进制数 b 中的每一个,通过选择那些具有对应于 b 中的 1 的索引的 L 元素来生成一个列表。例如,如果Lis ['x', 'y', 'z'] 和 bis 101,生成列表 ['x', 'z']。

尝试在包含字母表的前 10 个字母的列表上运行gen_powerset。它将很快完成并生成一个包含1024个元素的列表。接下来,尝试对字母表的前 20 个字母运行gen_powerset。运行它将花费更多的时间,并将返回一个包含大约一百万个元素的列表。如果您尝试对所有26个字母运行gen_powerset,您可能会厌倦等待它完成,除非您的计算机内存不足,试图构建一个包含数千万个元素的列表。甚至不要考虑尝试在包含所有大写和小写字母的列表上运行gen_powerset。算法的步骤 1 生成阶次 θ (2len(L),二进制数,因此算法在 len(L) 中是指数级的。这是否意味着我们不能使用计算来解决指数级的难题?绝对不行。这意味着我们必须找到为这些问题提供近似解决方案的算法,或者在问题的某些实例上找到确切的解决方案。但这是后面几章的主题。

复杂性类的比较

本节中的图旨在传达一种印象,即算法位于这些复杂性类中的一个或另一个类别中的含义。

图 11-7 中左侧的图将常时算法的增长与对数算法的增长进行了比较。请注意,输入的大小必须达到约30,000才能使两者相交,即使对于非常小的常数15也是如此。当输入大小为100,000时,对数算法所需的时间仍然很小。寓意是,对数算法几乎与常量时间算法一样好。

Eigure 11-7 右侧的图说明了对数算法和线性算法之间的巨大差异。

虽然我们需要查看大输入以了解常量时间和对数时间算法之间的差异,但即使在小输入上,对数时间和线性时间算法之间的差异也很明显。对数和线性算法相对性能的巨大差异并不意味着线性算法不好。事实上,很多时候线性算法的效率是可以接受的。

image.png

图 11-8 中左侧的图显示 o (n) 和 o(n 对数(n)之间存在显著差异。考虑到对数(n)的增长速度有多慢,这似乎令人惊讶,但请记住,这是一个乘法因素。还要记住,在许多实际情况下,o(n log(n)) 足够快,很有用。另一方面,如图 11-8 中右侧的图所示,在许多情况下,二次增长率是令人望而却步的。

image.png

图 11-9 中的图。是关于指数级复杂性的。在图 11-9 左侧的图中,y 轴左侧的数字从 o 到 6。但是,左上角的符号 1e29 表示 y 轴上的每个价格变动都应乘以 1029。因此,绘制的y值范围从o到大约6.6 * 1029。二次增长曲线在图 11-9 左侧的图中几乎不可见。这是因为指数函数增长得如此之快,以至于相对于最高点的 y 值(它决定了 y 轴的刻度),指数曲线上早期点(以及二次曲线上的所有点)的 y 值几乎与 o 无法区分。

图 11-9 中的右图。通过在 y 轴上使用对数刻度来解决此问题。人们可以很容易地看出,指数算法对于除最小输入之外的所有输入都是不切实际的。

请注意,在对数刻度上绘制时,指数曲线显示为一条直线。我们将在后面的章节中对此进行更多讨论。

image.png