为什么恒定总是从大O分析中掉线?

时间:2014-03-05 04:51:34

标签: algorithm big-o analysis

我正试图在PC上运行程序的过程中理解Big O分析的一个特定方面。

假设我有一个性能为O(n + 2)的算法。如果n变得非常大,那么2变得微不足道。在这种情况下,非常清楚真正的表现是O(n)。

但是,另一种算法的平均性能为O(n ^ 2/2)。我看到这个例子的书说实际表现是O(n ^ 2)。我不确定我明白为什么,我的意思是在这种情况下,2似乎并非完全无足轻重。所以我正在寻找书中一个很好的清晰解释。这本书以这种方式解释:

  

“考虑1/2意味着什么。检查每个值的实际时间   高度依赖于机器指令的代码   转换为CPU然后执行指令的速度。因此1/2并不意味着很多。“

我的反应是......嗯???我完全不知道那句话是什么,或者更确切地说,这句话与他们的结论有什么关系。请允许有人为我拼出来。

感谢您的帮助。

7 个答案:

答案 0 :(得分:66)

“这些常数是有意义的还是相关的?”之间存在区别?并且“大O符号关心他们吗?”第二个问题的答案是“不”,而第一个问题的答案是“绝对的!”

Big-O表示法并不关心常量,因为big-O表示法仅描述函数的长期增长率,而不是它们的绝对值。将函数乘以常数仅影响其生长速率恒定量,因此线性函数仍然线性增长,对数函数仍然呈对数增长,指数函数仍然呈指数增长等等。由于这些类别不受常数影响,因此它不会重要的是我们放弃常数。

那就是说,那些常数绝对重要!运行时为10 100 n的函数 way 比运行时为n的函数慢。运行时为n 2 / 2的函数将比运行时仅为n 2 的函数更快。前两个函数都是O(n)和后两个函数都是O(n 2 )这一事实并没有改变它们不会在相同的时间内运行的事实,因为这不是大O符号的设计目标。 O符号有助于确定长期一个函数是否会大于另一个函数。即使10 100 n对于任何n> 1来说都是巨大的值。 0,该函数是O(n),因此对于足够大的n,它最终会击败运行时为n 2 / 2的函数,因为该函数是O(n 2 )。

总之 - 由于大O只讨论增长率的相对类别,它忽略了常数因素。但是,这些常数绝对重要;它们与渐近分析无关。

希望这有帮助!

答案 1 :(得分:4)

Big-O表示法仅用数学函数描述算法的增长率,而不是某些机器上算法的实际运行时间。

数学上,令f(x)和g(x)对于x足够大是正的。 我们说f(x)和g(x)以与x趋于无穷大相同的速率增长,如果

enter image description here

现在让f(x)= x ^ 2并且g(x)= x ^ 2/2,然后lim(x-> infinity)f(x)/ g(x)= 2。所以x ^ 2和x ^ 2/2都有相同的增长率。所以我们可以说O(x ^ 2/2)= O(x ^ 2)。

正如templatetypedef所说,渐近符号中隐藏的常量是绝对重要的。例如:marge sort在O(nlogn)最坏情况下运行,插入排序在O(n ^ 2)最坏情况下运行。但是插入排序中隐藏的常数因子小于marge排序,实际上插入排序比许多计算机上的小问题大小的排序更快。

答案 2 :(得分:3)

你永远是对的,常数很重要。在针对同一问题比较许多不同的算法时,没有常数的O数字可以让您概览它们如何相互比较。如果你在同一个O类中有两个算法,你可以使用相关的常量来比较它们。

但即使对于不同的O类,常数也很重要。例如,对于多位数或大整数乘法,朴素算法为O(n ^ 2),Karatsuba为O(n ^ log_2(3)),Toom-Cook O(n ^ log_3(5))和Schönhage-Strassen O (N *的log(n)*日志(的log(n)))。但是,每个较快的算法都会在大常量中反映出越来越大的开销。因此,要获得近似交叉点,需要对这些常数进行有效估计。因此,作为SWAG,人们得到n = 16的天真乘法最快,直到n = 50 Karatsuba,从Toom-Cook到Schönhage-Strassen的交叉发生在n = 200。

实际上,交叉点不仅取决于常量,还取决于处理器缓存和其他与硬件相关的问题。

答案 3 :(得分:1)

没有常数的大O足以进行算法分析。

首先,实际时间不仅取决于指令的数量,还取决于每条指令的时间,这些指令与代码运行的平台紧密相关。它不仅仅是理论分析。所以大多数情况下都不需要常数。

其次,Big O主要用于衡量运行时间随着问题变大而增加的程度,以及随着硬件性能的提高,运行时间如何减少。

第三,对于高性能优化的情况,也会考虑常数。

答案 4 :(得分:1)

Big O表示法最常用于描述算法的运行时间。在这种情况下,我认为特定的常数值实际上是没有意义的。想象一下下面的对话:

Alice:您的算法的运行时间是多少?

鲍勃:7n 2

爱丽丝:7n 2 是什么意思?

  • 什么是单位?微秒?毫秒?纳秒?
  • 您在哪个CPU上运行?英特尔i9-9900K?高通骁龙845? (或者您使用的是GPU,FPGA还是其他硬件?)
  • 您正在使用哪种类型的RAM?
  • 您使用哪种编程语言实现该算法?什么是源代码?
  • 您使用的是什么编译器/ VM?您将哪些标志传递给编译器/ VM?
  • 什么是操作系统?

因此,如您所见,任何表示特定常数值的尝试本质上都是问题。但是,一旦我们抛弃了常数因子,我们就能清楚地描述算法的运行时间。 Big O表示法为我们提供了一种算法耗时的健壮且有用的描述,同时抽象了其实现和执​​行的技术特征。

现在可以在描述操作数(适当定义)或算法执行的CPU指令,排序算法执行的比较次数等时指定​​常数因子。但是通常,我们真正感兴趣的是运行时间。

这些都不意味着暗示算法的实际性能特征不重要。例如,如果需要矩阵乘法的算法,则不建议使用Coppersmith-Winograd算法。确实,该算法需要O(n 2.376 )时间,而最强的竞争对手Strassen算法却需要O(n 2.808 )时间。但是,根据Wikipedia所述,Coppersmith-Winograd在实践中速度较慢,并且“它仅对大型矩阵提供了优势,以至于无法通过现代硬件进行处理。”通常可以这样解释,即Coppersmith-Winograd的常数因子非常大。但是要重申一下,如果我们谈论的是Coppersmith-Winograd的运行时间,那么给定常数作为常数是没有意义的。

尽管有其局限性,但是大的O表示法可以很好地衡量运行时间。而且在许多情况下,它甚至告诉我们在编写一行代码之前,就告诉我们哪种算法对于足够大的输入大小而言最快。

答案 5 :(得分:0)

现在,在计算机上执行特定任务所需的时间不需要很长时间,除非输入的值非常大。

假设我们要将2个大小为10 * 10的矩阵相乘,我们就不会有问题除非我们想要执行此操作多次然后的角色>渐近符号变得普遍当n的值变得非常大时,常数对答案没有任何影响,几乎可以忽略不计所以我们倾向于在计算时留下它们复杂性。

答案 6 :(得分:0)

O(n+n)的时间复杂度降低为O(2n)。现在2是一个常数。因此,时间复杂度将主要取决于n
因此,O(2n)的时间复杂度等于O(n)
另外,如果有类似O(2n + 3)的东西,它将仍然是O(n),因为时间基本上取决于n的大小。
现在假设有一个代码O(n^2 + n),它将是O(n^2),因为当n的值增加时,与n^2的作用相比,n的作用将变得不那么重要。
例如:

n = 2  => 4 + 2 = 6
n = 100 => 10000 + 100 => 10100
n = 10000 => 100000000 + 10000 => 100010000 

您可以看到,第二个表达式的效果随着n的值不断增加而减小。因此,时间复杂度评估为O(n^2)