为啥常数总是从大 O 分析中删除?
Posted
技术标签:
【中文标题】为啥常数总是从大 O 分析中删除?【英文标题】:Why is the constant always dropped from big O analysis?为什么常数总是从大 O 分析中删除? 【发布时间】:2014-04-06 23:39:08 【问题描述】:我试图在 PC 上运行程序的背景下了解 Big O 分析的特定方面。
假设我有一个性能为 O(n + 2) 的算法。在这里,如果 n 变得非常大,则 2 变得微不足道。在这种情况下,很明显真正的性能是 O(n)。
但是,假设另一种算法的平均性能为 O(n2 / 2)。我看到这个例子的书说真正的性能是 O(n2)。我不确定我明白为什么,我的意思是这种情况下的 2 似乎并非完全无关紧要。所以我一直在从书中寻找一个很好的清晰解释。这本书是这样解释的:
“考虑一下 1/2 的含义。检查每个值的实际时间 高度依赖于代码的机器指令 转换为然后转换为 CPU 执行指令的速度。因此 1/2 的意义不大。”
而我的反应是……嗯?我真的不知道那是什么意思,或者更确切地说,那句话与他们的结论有什么关系。有人可以为我拼写出来吗?
感谢您的帮助。
【问题讨论】:
实际上我从概念上理解大 O,以及为什么 1/2 因子在增长率方面可能微不足道。我明白这一点。我不明白的是这本书对为什么 1/2 因子微不足道的解释。没有人关注我关于教科书报价的核心问题。我想具体理解这一点。他们想说什么?有什么线索吗? 当您描述算法的运行时间时,您通常不知道或选择忽略它将运行的系统。引用仅仅意味着因为程序将运行的系统将在运行时引入一个未知的常数因子,所以完全担心常数因子或多或少是没有意义的。但这只是在您分析 算法 时。当然,对于特定系统的特定实现,您通常非常关心常量因素。 啊哈哈哈哈!这是有道理的,这是一个很好的解释。感谢您解决我的核心问题。 【参考方案1】:“这些常量是否有意义或相关?”之间存在区别。和“大 O 符号是否关心它们?”第二个问题的答案是“不”,而第一个问题的答案是“绝对!”
Big-O 表示法不关心常数,因为 big-O 表示法只描述函数的长期增长率,而不是它们的绝对大小。一个函数乘以一个常数只会影响它的增长率,所以线性函数仍然线性增长,对数函数仍然对数增长,指数函数仍然指数增长,等等。由于这些类别不受常数的影响,它不会不管我们删除常量。
也就是说,这些常量绝对很重要!运行时间为 10100n 的函数将比运行时间仅为 n 的函数慢方式。运行时间为 n2 / 2 的函数将比运行时间仅为 n2 的函数快。前两个函数都是 O(n) 而后两个函数是 O(n2) 的事实并没有改变它们运行时间不同的事实,因为这不是 big-O 符号的设计目的。 O 表示法有助于确定从长远来看一个函数是否会大于另一个函数。即使 10100n 对于任何 n > 0 来说都是一个巨大的值,但该函数是 O(n),因此对于足够大的 n,最终它将击败运行时间为 n2 的函数 / 2 因为那个函数是 O(n2)。
总而言之 - 由于 big-O 只讨论相对类别的增长率,它忽略了常数因素。然而,这些常数是绝对重要的。它们只是与渐近分析无关。
希望这会有所帮助!
【讨论】:
你不知道 O(2n) 函数会比 O(n) 函数慢。 O(n) 函数计算每次迭代的时间可能是 O(2n) 函数的两倍。 是的,当然。毕竟,O(2n) 的字面意思与 O(n) 相同。但是如果我们从 non-big-O 的角度讨论总运行时间,如果真正的运行时间是 2n 而真正的运行时间是 n,那么第一个算法将比第二个算法慢。跨度> 【参考方案2】:你完全正确,常量很重要。在比较同一问题的许多不同算法时,没有常数的 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(log(n)))。然而,每个更快的算法都有越来越大的开销,反映在大常数上。因此,要获得近似的交叉点,需要对这些常数进行有效估计。因此,作为 SWAG,在 n=16 时,朴素乘法最快,在 n=50 时,Karatsuba 和从 Toom-Cook 到 Schönhage-Strassen 的交叉发生在 n=200 时。
实际上,交叉点不仅取决于常量,还取决于处理器缓存和其他与硬件相关的问题。
【讨论】:
【参考方案3】:Big-O 表示法仅以数学函数的形式描述算法的增长率,而不是算法在某些机器上的实际运行时间。
在数学上,当 x 足够大时,让 f(x) 和 g(x) 为正。 我们说 f(x) 和 g(x) 以相同的速率增长,因为 x 趋于无穷大,如果
现在让 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 排序在 O(nlogn) 最坏情况时间运行,插入排序在 O(n^2) 最坏情况时间运行。但是作为插入排序中的隐藏常数因子小于边缘排序,实际上对于许多机器上的小问题,插入排序比边缘排序更快。
【讨论】:
这是一个很好的解释,非常有意义。谢谢。但我仍在努力理解这本书的意义所在。有什么线索吗?? @user2722568:请澄清您的问题。 如果你回到我在顶部的原始帖子,我引用了一本教科书的引述,它对 1/ O(N^2/2) 中的 2 个因子。理想情况下,我想了解他们想要表达的观点。现在对我来说毫无意义。 @user2722568:你想了解隐藏在渐近符号中的常数因子的含义吗? @user2722568:我认为它试图解释在分析算法时隐藏在渐近符号中的常数因子的重要性。这个常数实际上取决于语句成本。不同机器的语句成本可能不同.但在某些特殊情况下,如示例所示,此常量可能很有用。【参考方案4】:大 O 符号最常用于描述算法的运行时间。在这种情况下,我认为特定的常数值本质上是没有意义的。想象以下对话:
Alice:你的算法的运行时间是多少?
鲍勃:7n2
Alice:你说的 7n2 是什么意思?
单位是什么?微秒?毫秒?纳秒? 您在哪个 CPU 上运行它?英特尔 i9-9900K?高通骁龙845? (或者您使用的是 GPU、FPGA 还是其他硬件?) 您使用的是什么类型的 RAM? 您用什么编程语言实现了该算法?源代码是什么? 您使用的是什么编译器/VM?您将哪些标志传递给编译器/VM? 什么是操作系统? 等如您所见,任何表示特定常量值的尝试都存在固有的问题。但是一旦我们把常数因素放在一边,我们就能清楚地描述一个算法的运行时间。大 O 表示法为我们提供了关于算法需要多长时间的可靠且有用的描述,同时从其实现和执行的技术特征中抽象出来。
现在可以在描述算法执行的操作数(适当定义)或 CPU 指令数、排序算法执行的比较数等时指定常数因子。但通常情况下,我们真正感兴趣的是运行时间。
这并不是说算法的实际性能特征不重要。例如,如果您需要一个矩阵乘法算法,Coppersmith-Winograd 算法是不可取的。确实,该算法需要 O(n2.376) 时间,而其最强竞争对手 Strassen 算法需要 O(n2.808) 时间。然而,根据 Wikipedia 的说法,Coppersmith-Winograd 在实践中的速度很慢,而且“它只为大到现代硬件无法处理的矩阵提供了优势。”这通常被解释为 Coppersmith-Winograd 的常数因子非常大。但重申一下,如果我们谈论的是 Coppersmith-Winograd 的运行时间,那么为常数因子给出一个具体数字是没有意义的。
尽管有其局限性,但大 O 表示法是一个很好的运行时间度量。在许多情况下,它甚至在我们编写一行代码之前就告诉我们哪些算法对于足够大的输入大小是最快的。
【讨论】:
【参考方案5】:没有常数的大 O 足以进行算法分析。
首先,实际时间不仅取决于有多少条指令,还取决于每条指令的时间,这与代码运行的平台密切相关。它不仅仅是理论分析。所以大多数情况下不需要这个常数。
其次,Big O 主要用于衡量运行时间如何随着问题的变大而增加,或者运行时间如何随着硬件性能的提高而减少。
第三,对于高性能优化的情况,也会考虑常数。
【讨论】:
我认为说在算法分析中完全忽略常量是可以的,这有点太强了。由于隐藏在 O 符号中的巨大常数因子,许多看起来高效的算法(例如 O(n))在实践中效率极低。确定使用哪种算法通常与常数因子有关,因为它与渐近运行时间有关,尤其是在已知输入很小的情况下。【参考方案6】:现在一天在计算机中完成一项特定任务所需的时间并不需要很多时间,除非输入的值非常大。
假设我们想将 2 个大小为 10*10 的矩阵相乘,我们不会有问题除非我们想要执行此操作多次然后的角色>渐近符号变得普遍,当n的值变得非常大时,常数对答案没有任何影响,几乎可以忽略不计所以我们倾向于在计算时留下它们复杂性。
【讨论】:
【参考方案7】: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)
。
【讨论】:
以上是关于为啥常数总是从大 O 分析中删除?的主要内容,如果未能解决你的问题,请参考以下文章