解释算法渐近复杂度与算法设计实践的相关性


40

在算法和复杂度中,我们关注算法的渐近复杂度,即,随着输入大小达到无穷大,算法使用的资源量。

在实践中,需要一种能够在有限(尽管可能非常大)实例数量上快速运行的算法。

在我们感兴趣的有限数量的实例上在实践中运行良好的算法不需要具有良好的渐近复杂度(有限数量的实例上的良好性能并不意味着关于渐进复杂性的任何内容)。类似地,具有渐近复杂性的算法在实际中对我们感兴趣的有限数量的实例(例如,由于常量较大)可能无法很好地工作。

为什么我们使用渐近复杂性?这些渐近分析在实践中如何与算法设计相关?


另一个相关的问题是:为什么我们忽略恒定因素
拉斐尔

Answers:


24

有趣的问题是:替代方案是什么?我知道的唯一其他方法是测试/基准测试。我们对算法进行编程,让它们在有限输入集(的代表样本)上运行并比较结果。有两个问题。

  • 就机器而言,结果并不一般。在另一台计算机上运行基准测试,可以肯定地,定量地甚至定性地获得不同的结果。
  • 就编程语言而言,结果并不通用。不同的语言可能会导致非常不同的结果。
  • 就实施细节而言,结果并不一般。您实际上是在比较程序,而不是算法;实施中的微小变化会导致性能上的巨大差异。
  • 如果最坏情况很少发生,则随机输入样本可能不包含不良实例。如果您关心平均性能,那是公平的,但是某些环境需要最坏情况的保证。
  • 实际上,输入集会更改。通常,输入随时间而变大。如果您不每六个月重复一次基准测试(是的,一些数据增长很快),那么您的结果很快就一文不值¹。

就是说,在分析中忽略各种影响和常数是很典型的,但是可以称为懒惰的(相对于实践而言)。它不仅可以用于比较算法思想,还可以用于查明给定(甚至伪代码)实现的性能。社区众所周知,这很粗糙,通常需要仔细观察。例如,对于(非常)小的输入,快速排序的效率不如插入排序。公平地说,更精确的分析通常很难 ²。

另一个形式上的抽象观点的后验理由是,在这个层次上,事情通常更清晰。因此,数十年来的理论研究提出了许多在实践中有用的算法思想和数据结构。从理论上讲,最佳算法并不总是您想在实践中使用的算法-除性能外还需要考虑其他因素;认为斐波那契堆积如山-这个标签甚至可能不是唯一的。对于关心优化算术表达式的典型程序员而言,很难在这个级别上提出新的想法(并不是说它不会发生)。但是,她可以(并且应该)对同化的想法进行优化。

有一些形式上的理论工具可以在一定程度上缩小实践差距。例子是

  • 考虑内存层次结构(和其他I / O),
  • 分析平均情况(在适当情况下),
  • 分析单个报表的数量(而不是抽象成本度量),并且
  • 确定恒定因素。

例如,众所周知,Knuth可以对不同语句的数量进行字面统计(对于给定模型中的给定实现),从而可以对算法进行精确比较。这种方法在抽象层面上是不可能的,并且在更复杂的模型中很难做到(以Java为例)。有关现代示例,请参见[4]。

理论与实践之间始终存在差距。我们目前正在开发一种工具³,其目标是结合两者的优势,对算法成本和运行时间(平均)做出合理的预测,但到目前为止,我们还无法消除一种算法具有更高的场景成本,但运行时(在某些计算机上)比等效运行时小(尽管我们可以检测到并支持查找原因)。

我建议从业人员在运行基准测试之前使用理论来过滤算法的空间:

if ( input size forever bounded? ) {
  benchmark available implementations, choose best
  schedule new benchmarks for when machine changes
}
else {
  benchmark implementations of all asymptotically good algorithms
  choose the best
  schedule new benchmarks for when machine changes or inputs grow significantly
}

  1. 一旦高速缓存未命中的数量增加,绝对和相对性能可能会发生疯狂的变化,这通常在输入增加但计算机保持不变时发生。
  2. 在这种情况下,该领域的领先研究人员无法做到这一点。
  3. 此处找到该工具。S. Wild等人在Engineering Java 7的使用MaLiJAn的Dual Pivot Quicksort中发布了一个示例用法。(2012)[ 预印本 ]
  4. Java 7中的双枢轴快速排序的平均案例分析由S.野生和M.内贝尔(2012) - [ 预印 ]

3
可以说,纯粹学习算法理论的行为将使您眼神敏锐,并为算法训练您的抽象头脑,从而为您提供了另一种在日常编程中评估代码的工具。从代码中抽象出来,评估原理,加以改进,然后再转换回代码。示例:“啊,我知道,您想编写字典。但是您本质上是编写列表;为什么不尝试树呢?”
拉斐尔

深入研究后,渐近分析的极限变得显而易见。Quicksort是一个突出的例子
拉斐尔

1
FWIW,我已经写了一个更近的我对朗多表示法的意见快照在这里
拉斐尔

11

我认为这个问题源于教授一门包括渐近分析的课程。关于为什么在入门班中讲授此材料的问题,有几种可能的答案:

  • 渐近分析是一种数学抽象,可用于分析。作为(可以说)数学家,我们希望能够分析算法,而他们要解决其复杂性的唯一方法是使用渐近分析。

  • 评估算法的渐近性能确实指出了一些在实践中有用的原理:例如,专注于花费大部分时间的那部分代码,并折衷花费一部分时间的渐近可忽略的代码的任何部分。

  • 渐近分析的某些技术很有用。我在这里主要指的是所谓的“主定理”,它在许多情况下都是对现实的很好描述。

  • 还有一个历史原因:当人们第一次开始分析算法时,他们真诚地认为渐近复杂性反映了实际用法。但是,最终他们被证明是错误的。P是可有效解决的问题,NP是难解决的问题,这两者在实践中都产生了误导。

我个人认为,渐进分析是课程的合理组成部分。更可疑的部分包括形式语言理论和复杂性理论(与图灵机有关的任何事物)。有人认为,尽管这些主题对想成为程序员的人本身没有用,但他们确实向她灌输了一定的思维方式,这对于成为一名优秀的实践者是必不可少的。其他人则认为理论有时会影响实践,而这些罕见的案例足以证明向一般计算机科学受众教授这些相当神秘的主题。我希望他们学习历史或文学,或者他们真正感兴趣的其他任何学科;两者都与他们未来的工作前景息息相关,对他们来说与人类一样重要。


谢谢尤瓦尔。主要感兴趣的动机是如何向学生解释渐近分析的有用性及其与在实际应用中设计和使用算法的实践的相关性(在大多数情况下,很明显,我们只对有限度感兴趣,尽管可能实例很多),不能证明课程的合理性。
卡夫(Kaveh)2012年

1
我被你的前提弄糊涂了。您似乎假设目标群体既是数学家又是有抱负的程序员,这是一个怪异的组合,而且都不具有计算机科学家的特征。(此外,我对正式语言不认同您的看法,但这是另一个主题。)
拉斐尔

相反,我认为目标群体是有抱负的程序员。但是,许多课程是为理论计算机科学家而设的。当然,这两个群体有相互矛盾的需求。由于大多数本科生都是可能的程序员,所以我认为课程应该针对他们,但是一些学者对此表示反对。也许他们想教未来的教授。也许您可以解释他们的观点。
Yuval Filmus 2012年

3
@YuvalFilmus我经常解释说,我不相信CS = TCS +编程。如果您(在大学里)教授CS课程,并且大多数学生都想成为程序员,那么某些东西会出错(恕我直言)。我认为,任何计算机科学家都可以从扎实的算法,正规语言甚至是复杂性理论(以及许多其他方面,例如编译器和CPU的工作原理)方面受益。
拉斐尔

2
@Wildcard计算机体系结构,计算机图形学,AI,编程语言研究,...-列表无穷无尽!TCS确实是一个利基市场,而编程只是(大多数)CS研究人员的工具。
拉斐尔

7

使用运行时间渐近分析有两个严重的原因:

  • 提取不重要的细节。在许多需要非平凡算法的应用程序中,大多数时间都花在需要中等数量到大量操作的问题实例上,并且我们对总体趋势更感兴趣,而不是确切的操作数量。在这些应用程序中,小行为是无趣的。n

  • 允许数学上的易处理性。可能找到精确的运算计数表达式的情况是例外。研究渐近性开辟了更多的可能性(例如复杂函数的渐近逼近很方便)。

还有许多其他方面(例如机器独立性,有意义性,可比性...)。


n

好吧,我根本不认为这是规则。您丢弃的数据越多,您所做的陈述就越弱。渐近(更重要的是,“ big-oh”)透视图会创建类似“ Quicksort比Insertionsort更快”的语句,即使不是错误,也不太正确。(是的,我是说算法分析经常被教导错误,恕我直言。)
拉斐尔

6

如Raphael的回答所述,最坏情况下的运行时间的精确计算可能非常困难。由于RAM模型已经引入了近似值,因此也不需要进行精确的计算。例如,所有操作是否真的需要相等的时间?特定的实现(硬件,优化)可能会通过恒定的因素来加快算法的速度。我们想了解一种算法与这些因素无关的有效程度。这是使用渐近分析的主要动机。


3

因为渐近是“简单的”(总之,比对有限情况进行精确分析更简单)。

例如,比较一下Knuth的百科全书“计算机编程的艺术”,该书对所有重要算法(以及许多不太重要的算法)进行了详细的分析,并且采用了经验法则分析(通常足以得出渐近估计值)(或只是一个界限),就像大多数“算法”书中所实践的那样。

你当然是对的。如果问题足够重要,则很可能需要进行Knuth风格的分析(或者可能不太详细)。在许多情况下,暗示拟合到实验数据的渐进复杂度(也许是色散的平均值)就足够了。在大多数情况下,对竞争算法进行粗略分类,因为在第一轮淘汰赛中,比较渐近症状可能足够精确。如果没有竞争者,那么在细节上获得确切成本的坏消息只是受虐狂。


2
这仅是事实的一半:首先,似乎您在写时要牢记“ big-oh”(问题未提及)。其次,臭名昭著的“大哦”渐近线在选择算法时因“杂草回合”失败而臭名昭著:输入实际上是有限的。
拉斐尔

3

在这里,通过渐近分析,我假设我们的意思是算法的行为是随着输入的大小达到无穷大。

我们使用渐近分析的原因是, 它在预测实践中算法的行为方面很有用。这些预测使我们能够做出决策,例如,当针对一个问题使用不同的算法时,应该使用哪个算法?(有用并不意味着它总是正确的。)

关于现实世界的任何简化模型,可以提出相同的问题。为什么我们使用现实世界的简化数学模型?

想想物理学。在预测现实世界中,经典的牛顿物理学不如相对论物理学。但这对于建造汽车,摩天大楼,潜艇,飞机,桥梁等来说已经足够好了。在某些情况下,它还不够好,例如,如果我们要制造卫星或向冥王星发送太空探测器或预测运动,大型天体,例如恒星和行星,或超高速物体,例如电子。 知道模型的局限性很重要。

  1. 通常,它是逼真的世界的近似值。 在实践中,我们经常看到具有更好渐近分析的算法在实践中效果更好。很少有算法具有更好的渐近行为,因此,如果输入可以足够大,那么我们通常可以将渐进分析作为算法行为的第一个预测。如果我们知道输入将很小,则不是这样。根据我们想要的性能,我们可能需要进行更仔细的分析,例如,如果我们了解有关输入分布的信息,则将给出算法,我们可以进行更仔细的分析以实现我们拥有的目标(例如,快速完成99输入的百分比)。作为第一步,渐近分析是一个很好的起点。在实践中,我们还应该进行性能测试,但要记住,这也有其自身的问题。

  2. AAA具有更好的渐近复杂度。在所有投入中,没有哪个比另一个更好?然后,它变得更加棘手,并取决于我们在乎什么。我们关心大投入还是小投入?如果我们关心大型输入,那么算法具有渐近复杂度但在我们关心的大型输入上表现最差的情况并不常见。如果我们更关心小的输入,那么渐进分析可能没有那么有用。我们应该在我们关心的输入上比较算法的运行时间。在实践中,对于具有复杂需求的复杂任务,渐进分析可能没有用。对于算法教科书涵盖的简单基本问题,它非常有用。

简而言之,渐近复杂度相对容易地计算出用于简单基本任务的算法实际复杂度的近似值(算法教科书中的问题)。随着我们构建更复杂的程序,性能要求也随之变化,变得越来越复杂,渐近分析可能没有用。


最好将渐近分析与其他方法进行比较,以预测算法的性能并进行比较。一种常见的方法是针对随机或基准输入进行性能测试。当计算渐进复杂性困难或不可行时,例如在我们使用启发式算法(例如SAT解决方案)时,这是很常见的。另一种情况是,要求更加复杂,例如,程序的性能取决于外部因素,而我们的目标可能是在某个固定的时间限制(例如,考虑更新显示给用户的界面)上完成99%的操作。输入。

但是请记住,性能分析也存在问题。除非我们实际对将提供给算法的所有输入进行性能测试(通常在计算上不可行)(并且通常无法确定某些输入永远不会给出),否则它不能为性能提供数学上的授权。如果我们针对随机样本或基准进行测试,则隐含地假设了 有关算法性能的一些规律性,即算法将在性能测试以外的其他输入上执行类似的操作。

性能测试的第二个问题是它们取决于测试环境。也就是说,程序的性能不是仅由输入决定的,而是由外部因素(例如,机器类型,操作系统,编码算法的效率,CPU的利用率,内存访问时间等)决定的,其中某些因素可能在不同的运行期间有所不同。在同一台机器上进行测试。再次在这里,我们假设执行性能测试的特定环境与实际环境相似,除非我们在可以运行程序的所有环境上进行性能测试(以及我们如何预测某人可能在哪个计算机上运行排序) 10年内启用算法?)。

Θ(nlgn)Θ(n2)Θ(lgn)O(n)



我喜欢这个答案,现在可以投票了。有两个注意事项:1)我将在这里使用“成本”而不是“复杂性”。部分原因是出于宠物烦恼,但也有许多可以设想的成本衡量标准(使您提到的所有注意事项变得复杂)。2)您可能需要进行语言抛光。;)
拉斐尔

@Raphael,谢谢。我打算很快再做一次编辑。:)
卡夫

-2

O(n2)O(nlogn)O(n2) 与quicksort相比,它可以完成。

现在想象一下,代码中的等待重复次数与调用代码的次数相同。如何从数学上量化/证明快速排序算法的这一明显优势?(即,它的名称真的合理吗,还是仅仅是营销口号?)通过渐进复杂性度量。人们主观地看动画,觉得冒泡是某种较弱的算法,渐近复杂度分析可以定量地证明这一点。但请注意,渐进复杂性分析只是分析算法的工具包中的一种工具,而并非总是最终工具。

并且值得一看的并排代码。bubbleort在概念上似乎更简单,并且不使用递归。quicksort并不是立即理解的,尤其是“ 3的中位数”枢轴原理。bubbleort可能仅在没有子例程的循环中实现,而quicksort通常可能至少具有一个子例程。这显示了一种模式,即更多的代码复杂性有时可以牺牲代码的简单性来改善渐近复杂性。有时存在类似于降低边际收益(源自经济学的原始概念)的极端情况,在这种情况下,非常大量的代码复杂性(要求整篇论文有足够的经验和证据来证明)只需要对渐进复杂性进行很小的改进。这以esp为例显示矩阵乘法甚至可以用图形表示


在“看动画”和形式分析之间有很多领域,例如广泛的运行时基准测试。它们实际上是它们自己的有效区域,因为我们没有理论来解释所有影响运行时的因素。
拉斐尔

@raphael您在答案中涵盖了基准测试;这是一个很好的答案。但请注意,动画/可视化可能与基准测试密切相关。实际上,有很多关于影响运行时间的解释(在其他答案中发现),但是在某种程度上,它的“噪声”和渐近复杂性“使噪声平滑/平均”。多数民众赞成在另一个练习,看看它实际上是如何做到的。
vzn 2012年

但是,动画不会滤除噪音。另外,人眼容易被欺骗,并且观看动画以获取合理大小列表的合理大小样本(例如,数以百万计的1000个列表作​​为基准排序算法的基准)决定哪种算法更快是不可行的(平均来说)。
拉斐尔

nn

n
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.