您如何证明一种算法比另一种算法更有效?

时间:2010-01-08 15:00:09

标签: algorithm math comparison performance

我不是专业程序员,我也不研究它。我是一名航空航天学生,为我的毕业论文做了一个数字方法,并编写了一个程序来证明它是有效的。

我做了几种方法并实现了几种算法,并试图证明为什么不同的情况需要他们自己的算法来解决任务。

我用数学方法做了这个证明,但是某些算法是如此具体,以至于我确实知道他们做了什么并且他们做得对,但是很难找到一个数学函数或某些东西来显示有多少次迭代或循环它必须做到完成。

所以,我想知道你是如何进行这种比较的。你是否也提出了一个数学函数,或者你只是对这两种算法进行了最快速的测试,如果你以数学方式进行,你是如何做到的?你在大学学习期间学到了什么,或者怎么做?

提前谢谢你,Andreas

11 个答案:

答案 0 :(得分:16)

比较不同算法的标准方法是使用Big O符号比较它们的复杂性。在实践中,您当然也会对算法进行基准测试。

作为一个例子,排序算法冒泡排序和堆排序分别具有复杂度O(n 2 )和O(n log n)。

作为最后一点,很难构建具有代表性的基准,请参阅Chris {3}}有关该主题的有趣帖子。

答案 1 :(得分:6)

首先,需要定义更有效的手段,更快意味着,使用更少的系统资源(如内存)等......(这些因素有时是相互排斥的)

就效率的标准定义而言,人们通常会使用Big-0 Notation,但在学术界以外的“现实世界”中,人们通常会对这两个方程进行分析/基准测试,然后比较结果

通常很难对Big-0表示法做出一般假设,因为这主要与循环有关,并假设循环中代码的固定成本,因此基准测试将是更好的方法

需要注意的一点是,有时根据您正在使用的数据集大小,结果会有很大差异 - 对于循环中的小N,有时会发现差异不大

答案 2 :(得分:5)

虽然big-O表示法可以为您提供区分糟糕算法和合理算法的方法,但它只会告诉您有关计算复杂性的特定定义。在现实世界中,这实际上不允许您在两种算法之间进行选择,因为:

1)具有相同复杂度的两种算法,我们称之为fg,两者都具有O(N^2)复杂度,在运行时可能会有几个数量级的差异。 Big-O表示法不会衡量与每次迭代相关的各个步骤的数量,因此f可能需要100步,g需要10步。

此外,不同的编译器或编程语言可能会为算法的每次迭代生成更多或更少的指令,并且算法描述中的细微选择可能会使缓存或CPU硬件执行10到1000倍的更差,而不会更改大O订单,或步数!

2)O(N)算法可能胜过O(log(N))算法

Big-O表示法不衡量与每次迭代相关的单个步骤的数量,因此如果O(N)需要100步,但O(log(N))每次迭代需要1000步,那么对于数据集最多一定大小O(N)会更好。

同样的问题也适用于上面的编译器。


解决方案是对Big-O表示法进行初步数学分析,然后使用基准驱动的性能调整周期,使用时间和硬件性能计数器数据,以及良好的经验。

答案 3 :(得分:1)

跑步速度测试不会像数学那样为您提供高质量的答案。我认为你的大纲方法是正确的 - 但也许你的经验和广泛的知识让你在分析你的算法时失望。我推荐Knuth和其他人的“混凝土数学”一书,但是有很多其他好的(甚至更好的)书籍涵盖了分析算法的主题。是的,我在大学学习期间学到了这一点。

编写完所有内容后,大多数算法复杂度都是根据最坏情况执行时间(所谓的big-O)进行分析的,并且您的数据集可能不会接近最坏情况,在这种情况下速度会测试您运行可能会照亮您的实际性能而不是算法的理论性能。所以测试并非没有价值。不过,我会说,这个价值是数学的第二,这不应该引起任何不必要的麻烦。

答案 4 :(得分:1)

当最坏情况或预期情况的渐近Big-O复杂度类别存在显着差异时,您可能会很容易。即使这样,你也需要证明隐藏的常数因子不会使合理大小的输入的“更好”(从渐近视角)算法变慢。

如果差异不大,那么考虑到当今计算机的复杂性,使用各种数据集进行基准测试是唯一正确的方法。您甚至无法开始考虑分支预测准确性,数据和代码缓存命中率,锁争用等所产生的所有复杂的相互作用。

答案 5 :(得分:0)

通常用big O notation表示。基本上你选择一个简单的函数(比如n 2 ,其中n是元素的数量),它支配着实际的迭代次数。

答案 6 :(得分:0)

这取决于。在大学里,你确实学会通过计算它执行的操作数来比较算法,具体取决于它的参数的大小/值。 (比较analysis of algorithmsbig O notation)。我要求每个体面的程序员至少要了解其基础知识。

然而在实践中,这仅适用于小算法或较大算法的一小部分。例如,对于XML文档的解析算法,您将无法计算它。但是,了解基础知识往往会使你免于犯错误 - 例如,请参阅Joel Spolskys有趣的博客条目"Back to the Basics"

如果你有一个更大的系统,你通常会通过有根据的猜测来比较算法,进行时间测量,或者使用profiling tool找到系统中的麻烦点。根据我的经验,这很少有重要意义 - 为降低系统的复杂性而进行的斗争有助于提高实力。

答案 7 :(得分:0)

正如其他人正确指出的那样,一种常见的方法是使用Big O-notation。

但是,只要您考虑处理明确定义和范围有限的算法(例如冒泡排序),Big O就是好的。

当其他硬件资源或并行运行的其他运行软件发挥作用时,称为工程的部分开始发挥作用。硬件有其约束。内存和磁盘资源有限。磁盘性能甚至取决于所涉及的机制。

操作系统调度程序将例如区分I / O绑定和CPU绑定资源,以提高给定应用程序的总体性能。 DBMS将考虑磁盘读写,内存和CPU使用情况,甚至是集群情况下的网络。

这些事情很难用数学方法证明,但通常很容易根据一组使用模式进行基准测试。

所以我想答案是开发人员都使用Big O等理论方法和基准测试来确定算法及其实现的速度。

答案 8 :(得分:0)

假设速度(不是内存)是您最关心的问题,并且假设您需要经验(非理论)方法来比较算法,我建议您准备几个大小不同的数据集,例如3个数量级。然后针对每个数据集运行每个算法,为它们计时,并绘制结果。每个算法的时间与数据集大小曲线的形状将很好地了解其大O性能。

现在,如果数据集在实践中的大小众所周知,那么具有更好的大O性能的算法不一定更快。要确定哪种算法对于给定的数据集大小更快,您需要调整每个算法的性能,直到它“尽可能快”,然后查看哪一个获胜。性能调优需要在指令级进行概要分析或单步执行,或者我最喜欢的技术stackshots

答案 9 :(得分:0)

回答你的问题:“你是否也提出了数学函数,或者你只是对两种算法进行速度测试。”

对两者都是 - 让我们总结一下。

上面讨论的“大O”方法是指如上所述的Mark的最坏情况性能。你提到的“速度最快”将是一种估算“平均案例表现”的方法。实际上,最差案例表现与平均案例表现之间可能存在巨大差异。这就是为什么你的问题很有趣和有用。

最糟糕的案例表现是定义和分类算法性能的经典方法。最近,研究越来越关注平均案例表现或更精确的性能界限:99%的问题需要少于N次操作。你可以想象为什么第二种情况对大多数问题更加实用。

根据应用程序的不同,您可能会有不同的要求。一个应用程序可能要求响应时间在95%的时间内小于3秒 - 这将导致定义性能界限。另一个可能要求性能不超过5秒 - 这将导致分析最坏情况的性能。

在这两种情况下,这都是在大学或研究生阶段教授的。任何开发用于实时应用程序的新算法的人都应该了解平均和最差情况下的性能之间的差异,并且还应该准备开发模拟和算法性能分析,作为实现过程的一部分。

希望这会有所帮助。

答案 10 :(得分:0)

Big O表示法在最坏的情况下为您提供了算法的复杂性,并且主要用于了解当必须处理的数据量增长时,算法将如何在执行时间内增长。例如(C风格的语法,这并不重要):

List<int> ls = new List<int>();           (1) O(1)
for (int i = 0; i < ls.count; i++)        (2) O(1)                                     
   foo(i);                                (3) O(log n) (efficient function)

Cost analysis:
    (1)  cost: O(1), constant cost
    (2)  cost: O(1), (int i = 0;)
               O(1), (i < ls.count)
               O(1), (i++)
               ----  total: O(1) (constant cost), but it repeats n times (ls.count)
    (3)  cost: O(log n) (assume it, just an example), 
                        but it repeats n times as it is inside the loop

因此,在渐近符号中,它的成本为:O(n log n)(效率不高)在这个例子中是一个合理的结果,但是举个例子:

List<int> ls = new List<int>();           (1) O(1)
for (int i = 0; i < ls.count; i++)        (2) O(1)                                     
  if ( (i mod 2) == 0) )                  (*) O(1)  (constant cost)
    foo(i);                               (3) O(log n)

相同的算法,但带有条件的新线条。在这种情况下,渐近符号将选择最坏的情况,并将得出与上述O(n log n)相同的结果,当很容易检测到(3)步骤只执行一半时。

数据只是示例,可能不准确,只是试图说明Big O表示法的行为。当数据长大时,它主要给你算法的行为(你的算法将是线性的,指数的,对数的......),但这不是每个人都知道的“效率”,或者差不多,这不是唯一的“效率“意义。

然而,这个methot可以检测到“不可能的过程”(对不起,不知道确切的英文单词)算法,这就是需要在其早期步骤中处理大量时间的算法(想想例如,或者非常大的matix)。

如果你想进行真实世界的效率研究,你可能会优先追踪一些现实世界的数据,并用这些数据做一个真正的世界基准测试。它不是一种数学风格,但在大多数情况下会更精确(但在最坏的情况下不会!))。

希望这有帮助。