【问题标题】:What is good measure to compare algorithms?比较算法的好方法是什么?
【发布时间】:2014-10-06 22:11:49
【问题描述】:

嗯,我正在阅读一篇关于通过首先分析它们来比较两种算法的文章。

我的老师教我,你可以通过直接使用算法的步数来分析算法。

例如:

algo printArray(arr[n]){
    for(int i=0;i<n;i++){
    write arr[i];
    }
}

将具有O(N) 的复杂度,其中N 是数组的大小。它会重复 for 循环 N 次。

同时

algo printMatrix(arr,m,n){
    for(i=0;i<m;i++){
        for(j=0;j<n;j++){
        write arr[i][j];
        }
    }
}

M=N 时,复杂度为O(MXN) ~ O(N^2)for 中的语句被执行 MXN 次。

类似O(log N)。如果它将输入分成两等份。等等。

但是根据那篇文章:

措施Execution TimeNumber of statements 不利于分析算法。

因为:

Execution Time 将依赖于系统,并且,

Number of statements会因所使用的编程语言而异。

它指出

理想解决方案将算法的运行时间表示为输入大小 Nf(n) 的函数。

这让我有点困惑,如果你认为执行时间不是很好的衡量标准,你如何计算运行时间?

这里的专家可以详细说明一下吗?

提前致谢。

【问题讨论】:

  • 您已经在 O(N)、O(N^2) 等分析中使用“理想解决方案”。执行时间很难判断,除非您在单个平台上比较多种算法。至于行,不正确的分析是:“PrintArray 在其算法中有 2 行”和“PrintMatrix 有 3”。 O(N) 和 O(N*M) 是理想的方法。
  • 算法是数学过程:它们的实现可能因语言而略有不同,但数学运算保持不变。您所指的复杂性根据它执行的操作量为您提供了运行时的近似值。选择算法有更多方面,例如它所需要的内存以及您是否想要摊销时间(而不是复杂性,这是最坏情况下的时间),但通常您描述的方法主要用于确定算法的质量。
  • 您犯了一个常见的错误,即认为写入 2D 数组的每个元素都是 O(N^2) 操作。它是O(N),其中N 是数组中的元素数。 N 可能是另一个数字的平方这一事实与此类分析无关。如果您不相信这项研究,并了解到无法通过更改计数方式将算法从一种复杂性类别转换为另一种复杂性类别N。如果我们能做到这一点,我们将永远不会对二维数组使用简单的扫描,因为O(N^2)O(N) 差得多;我们只需将数组保留为 1D。
  • 嗯,我只是很困惑我的老师是正确的还是那篇文章的作者或两者兼而有之?以及如何?
  • @VedantTerkar 你的老师和作者说要使用相同的方法(时间复杂度)。唯一的区别是他们使用不同的术语。

标签: c algorithm analysis


【解决方案1】:

当您说“O(N) 的复杂性”时,它被称为“Big-O 表示法”,与您在帖子中提到的“理想解决方案”相同。这是一种将运行时间表示为输入大小函数的方式。

我认为您是否感到困惑的是,当它说“表示运行时间”时 - 它并不是指用数值表示它(这就是执行时间),它意味着用 Big-O 表示法表示它。我想你只是被术语绊倒了。

【讨论】:

  • 是的。确切地。所以你的意思是Running timeexecution time 不一样?
  • 通常当人们使用“运行时间”这个词时,他们的意思是“时间复杂度”。执行时间是指代码完成执行所需的时间长度。如此正确,它们不一样。
【解决方案2】:

执行时间确实取决于系统,但也取决于算法执行的指令数

此外,鉴于算法被分析为与语言无关,并且没有关注各种语言所暗示的任何特征和句法糖,我不明白步骤的数量是如何无关紧要的。

自从我开始分析算法以来,我一直遇到的一种算法分析衡量标准是执行指令的数量,我看不出这个衡量标准是如何无关紧要的。

同时,复杂度等级是指算法的快慢程度的“数量级”指示。它们取决于执行指令的数量,并且独立运行算法的系统,因为根据定义,一个基本操作(例如两个数字相加)应该花费恒定时间,无论这个时间是大是小“恒定”在实践中意味着,因此复杂性等级不会改变。精确复杂度函数表达式中的常数确实可能因系统而异,但与算法比较实际相关的是复杂度类,因为只有通过比较它们才能发现算法在越来越大时的行为输入(渐近)与另一种算法相比。

【讨论】:

  • 如果指令不相关则编号。考虑从 1 到 n 的循环与从 1 到 n^2 的循环。相同数量的指令,不同的算法复杂度。
  • @mclaassen 告诉我您如何定义指令,我将相应地编辑我的答案。就我的回答而言,for 循环不是指令。我应该改用“步骤”这个词吗?
  • 最好使用短语“执行的指令数
  • @webuster 编译程序时生成的汇编指令中的指令。
  • 好的,编辑了我的答案以解释“指令”的定义。当然,如果指令意味着单个汇编指令,那么您是对的,因为这与机器实际执行的内容无关。我希望现在你明白我的意思(如果你一开始没有的话)并反对我。
【解决方案3】:

Big-O 表示法消除了常数(固定成本和常数乘数)。因此,无论kc,任何需要kn+c 操作来完成的函数都是(根据定义!)O(n)。这就是为什么最好使用真实数据对算法进行实际测量(分析),看看它们的效率有多快。

但显然,执行时间会因数据集而异——如果您试图提出一个通用的性能衡量标准,不是基于特定的使用场景,那么执行时间价值较低(除非您在相同条件下比较所有算法,即使这样也不一定公平,除非您对大多数可能的场景进行建模,而不仅仅是一个)。

随着您转向更大的数据集,Big-O 表示法变得更有价值。它让您大致了解算法的性能,假设kc 的值合理。如果您有一百万个数字要排序,那么可以肯定地说您要远离任何O(n^2) 算法,并尝试找到更好的O(n lg n) 算法。如果您要对三个数字进行排序,那么理论上的复杂性界限就不再重要了,因为常量支配了所占用的资源。

另请注意,虽然给定算法可以表达的语句数量在编程语言之间差异很大,但需要执行的恒定时间步骤的数量(在目标架构的机器级别,通常是一个其中整数算术和内存访问需要固定的时间,或者更准确地说是有界固定的时间)。这是 big-O 测量的算法所需的最大固定成本步骤数的界限,它与给定输入的实际运行时间没有直接关系,但仍大致描述了工作 随着数据集大小的增长,必须针对给定的数据集执行此操作。

【讨论】:

    【解决方案4】:

    在比较算法时,执行速度很重要,其他人也提到过,但内存空间等其他因素也很重要。

    内存空间也使用复杂度表示法。

    代码可以使用冒泡排序就地对数组进行排序,只需要少量的额外内存 O(1)。其他方法虽然更快,但可能需要 O(ln N) 内存。

    其他更深奥的措施包括代码复杂性,例如 Cyclomatic complexityReadability

    【讨论】:

      【解决方案5】:

      传统上,计算机科学使用“大 O 表示法”通过比较次数或有时数据访问次数来衡量算法的有效性(速度)。之所以如此,是因为比较(和/或数据访问)的数量是描述某些算法效率的一个很好的数学模型,特别是搜索和排序算法,其中 O(log n) 被认为是理论上最快的。

      尽管如此,这个理论模型总是存在一些缺陷。它假设比较(和/或数据访问)是需要时间的,并且执行函数调用和分支/循环之类的时间可以忽略不计。这在现实世界中当然是无稽之谈。

      在现实世界中,例如,与使用普通 for 循环实现的快速且肮脏的线性搜索相比,递归二进制搜索算法可能非常慢,因为在给定系统上,函数调用开销是最耗时的,而不是比较。

      影响性能的因素有很多。随着 CPU 的发展,更多这样的东西被发明出来。如今,您可能不得不考虑数据对齐、指令流水线、分支预测、数据高速缓存、多个 CPU 内核等。所有这些技术都使传统的算法理论变得无关紧要。

      要编写最有效的代码,您需要有一个特定的系统,并且您需要对该系统有深入的了解。幸运的是,编译器也有了很大的发展,所以很多深入的系统知识可以留给为特定系统实现编译器端口的人。

      一般来说,我认为今天的许多程序员花费了太多时间来思考程序速度并想出“聪明的东西”来获得更好的性能。在 CPU 很慢、编译器很糟糕的时代,这些事情非常重要。但是今天,一个优秀的现代程序员专注于使代码无错误、可读、可维护、可重用、安全、可移植等。无论你的程序有多快,如果它是一堆不可读的废话.因此,在需要时处理性能。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-10-31
        • 1970-01-01
        • 1970-01-01
        • 2015-08-02
        • 2011-01-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多