大O关于(n ^ 2 + n)/ 2增长率的算法的问题


16

我问这个问题是因为我对大O表示法的一个方面感到困惑。

我正在使用Frank Carrano的《Java数据结构和抽象》一书。在“算法效率”一章中,他显示了以下算法:

int sum = 0, i = 1, j = 1
for (i = 1 to n) {
    for (j = 1 to i)
        sum = sum + 1
}

他最初将这种算法描述为具有(n 2  + n) / 2的增长率。哪个看似直观。

但是,可以说,当n大时,(n 2  + n) / 2的行为类似于n 2。在同一段他说(N 2  + N) / 2也表现很像ñ 2 / 2。他使用此算法将上述算法分类为O(n 2

我得到的(N 2  + N) / 2 类似于ň 2 / 2,因为从百分比上看,ñ差别不大。我不明白的是为什么当n大时(n 2  + n) / 2n 2相似。

例如,如果n = 1,000,000

(n^2 + n) / 2 =  500000500000 (5.000005e+11)
(n^2) / 2     =  500000000000 (5e+11)
(n^2)         = 1000000000000 (1e+12)

最后一个根本不相似。实际上,很明显,它是中间值的两倍。那么,弗兰克·卡拉诺怎么能说他们相似呢?另外,该算法如何分类为O(n 2。看着那个内循环,我会说是n 2 + n / 2


如果您有兴趣,我为三个嵌套循环提供了执行树图检查的答案与嵌套循环有关的难题
Grijesh Chauhan 2015年



1
从根本上说,n随着函数的增长,函数'n ^ 2'和您的函数的行为类似,它们的增长率始终保持差异。如果您具有复杂的表达式,则增长更快的功能将占主导。
AK_ 2015年

1
@MichaelT:我不认为这是该问题的重复,因为其他问题仅是错误计数的问题。这是一个更微妙的问题,即为什么忽略较小的项(特别是常数乘数和低阶多项式)。此处的发问者显然已经理解了另一问题中提出的问题,并且足以解决该问题的答案将无法回答该问题。
sdenham

Answers:


38

在计算算法的Big-O复杂度时,所显示的是如果运行算法的元素数量增加,则对执行时间增加的贡献最大。

如果您的算法的复杂度为,(n^2 + n)/2并且元素数量2增加n了一倍,则该常数不会影响执行时间的增加,该术语会使执行时间增加一倍,而该术语n^2会使执行时间增加四倍。时间。
由于该n^2术语的贡献最大,因此Big-O的复杂度为O(n^2)


2
我喜欢,它变得更清晰了。
Andrew S

7
这很手工波浪。可能是正确的也可能是错误的。如果您可以进行少量数学运算,请参阅以下答案之一。
usr

3
这种推理太模糊了:这意味着我们可以得出结论O(n * log n) = O(n),这是不正确的。
cfh 2015年

它可能不是最精确的答案,也可能不是最语义上正确的答案,但是重要的是它使我开始理解中心点,并且我认为这是作者的目标。这是故意含糊的,因为细节通常会分散核心原则的注意力。重要的是要看到树木的树木。
安德鲁·S

巴特实际上是在谈论条件,而不是因素。了解这一点,我们不能得出结论O(n * log n) = O(n)。我认为这很好地解释了该定义背后的原理。
马克·福斯基

10

定义是

f(n) = O(g(n))

如果存在某个常数C> 0,使得对于所有大于某个n_0的n,我们有

|f(n)| <= C * |g(n)|

对于f(n)= n ^ 2和g(n)= 1/2 n ^ 2,这显然是正确的,其中常数C应该为2。也很容易看出,对于f(n)= n ^ 2和g(n)= 1/2(n ^ 2 + n)。


4
“如果存在某个常数C> 0使得所有n为n”,应为“如果存在一些常数C,n_0使得对于所有n> n_0,”
Taemyr

@Taemyr:只要函数g为非零,实际上就不需要此函数,因为您可以始终增加常数C来使有限的前n_0个值的语句为true。
cfh 2015年

不,只要我们关注函数,就不会有数量有限的潜在n_0值。
塔伊米尔2015年

@Taemyr:n_0是一个有限数。选择C = max {f(i)/ g(i):i = 1,...,n_0},然后该语句将始终保留前n_0个值,您可以轻松地检查一下。
cfh 2015年

在CS中,这无关紧要,因为n通常是输入大小,因此谨慎。在这种情况下,可以选择C使得n_0 = 1起作用。但是正式定义比任何阈值大n,这消除了应用定义时的很多挑剔。
塔米尔(Taemyr)

6

在讨论复杂性时,您仅对基于​​元素数(n)的时间因子变化感兴趣。

这样,您可以删除任何恒定因子(如此2处)。

这让您有了O(n^2 + n)

现在,对于相当大n的乘积,即n * n,将大大大于正义n,这也是允许您也跳过该部分的原因,这实际上使您的最终复杂度为O(n^2)

的确,对于较小的数字,会有很大的不同,但是随着您n变得越大,这种差异就越小。


要使差异变为边际,n必须有多大?另外,为什么将/ 2删除,将其值减半?
Andrew S

6
@AndrewS因为Big O Notation谈论增长。在基准和时间戳之外,除以2是无关紧要的,因为它最终不会改变增长率。但是,最大的组件可以做到,这就是您要做的一切。
尼尔

2
@尼尔,太清楚了。我希望这些书能这样写。有时,我认为作者知道太多了,他们忘记了凡人没有掌握其功能知识,因此没有明确指出要点,而是将其掩埋在某种形式化的数学描述中,或者认为它们是隐含的而将其全部省略。
Andrew S

我希望我可以不止一次赞成这个答案!@Neil,您应该写Big O的书。
Tersosauros

3

并不是说“(n²+ n)/ 2在n大时表现得像n²”,而是(n²+ n)/ 2 随着n的增加而像一样增长

例如,当n从1,000增加到1,000,000

(n² + n) / 2  increases from  500500 to  500000500000
(n²) / 2      increases from  500000 to  500000000000
(n²)          increases from 1000000 to 1000000000000

同样,随着n从1,000,000增加到1,000,000,000

(n² + n) / 2  increases from  500000500000 to  500000000500000000
(n²) / 2      increases from  500000000000 to  500000000000000000
(n²)          increases from 1000000000000 to 1000000000000000000

它们以相似的方式增长,这就是Big O符号的含义。

如果在Wolfram Alpha上绘制(n²+ n)/ 2和n²/ 2,它们是如此相似,以至于n = 100很难区分。如果在Wolfram Alpha上绘制所有三个,则将看到两条线,以常数2分隔。


很好,这让我很清楚。感谢回复。
Andrew S

2

看起来您需要进一步计算O标记。这种表示法多么方便,由于使用等号而极易产生误导,此处不使用等号来表示功能的相等性。

如您所知,该符号表示函数的渐近比较,写f = O(g)意味着f(n)的增长与n到无穷大时的g(n)一样快。一种简单的翻译方法是说函数f / g是有界的。但是,当然,我们必须照顾g为零的地方,最终我们得到了更强大的定义,您几乎可以在任何地方阅读

事实证明,这种表示法非常便于计算-这就是为什么它如此广泛的原因-但应谨慎处理,因为我们在那里看到的等号并不表示函数相等。这就像说2 = 5 mod 3并不意味着2 = 5一样,如果您热衷于代数,您实际上可以将大O表示理解为等式的等式。

现在,回到您的特定问题,计算几个数值并将它们进行比较完全没有用:但是,当数值为一百万时,它并不能说明渐近行为。绘制函数f(n)= n(n-1)/ 2g(n)=n²的比率会更有用–但是在这种特殊情况下,我们可以很容易地看到f(n)/ g(n)如果n> 0则小于1/2,这意味着f = O(g)

为了增进您对符号的理解,您应该

  • 使用纯净的定义,而不是基于相似的事物而产生的模糊印象–如您刚经历的那样,这种模糊印象不能很好地发挥作用。

  • 花一些时间来详细说明示例。如果您一周内只完成五个示例,就足以提高您的信心。这是绝对值得的努力。


代数边注如果A是所有函数N N的代数,并且C是有界函数的子代数,则在给定函数f的情况下,属于O(f)的函数集是AC-子模块,并且大的计算规则O符号仅描述A在这些子模块上运行方式。因此,我们看到的等式是AC-子模块的等式,这只是另一种模量。


1
在第一个小词之后,该维基百科的文章很难理解。它是由有成就的数学家为有成就的数学家编写的,而不是我希望从百科全书中找到的介绍性文字。非常感谢您的见解。
Andrew S

您高估了Wikipedia文本中的级别!:)肯定不是那么好写。Graham,Knuth和Patashnik为CS的学生写了一本可爱的书《具体数学》。您也可以尝试“计算机程序设计的艺术”或50年代写的数字理论书(Hardy&Wright,Rose),因为它们通常针对高中学生。您无需阅读整本书,如果您选择其中一本,则只需阅读有关渐近的部分即可!但是在您需要决定需要了解多少之前。:)
Michael Le BarbierGrünewald2015年

1

我认为您会误解大O符号的含义。

当您看到O(N ^ 2)时,基本上意味着:当问题变得大10倍时,解决该问题的时间将是:10 ^ 2 =大100倍。

让我们在方程式中打1000和10000:1000:(1000 ^ 2 + 1000)/ 2 = 500500 10000:(10000 ^ 2 + 10000)/ 2 = 50005000

50005000/500500 = 99,91

因此,尽管N的大小是10倍,但解决方案的大小却是100倍。因此它表现为:O(N ^ 2)


1

如果n是一个1,000,000

(n^2 + n) / 2  =  500000500000  (5.00001E+11)
(n^2) / 2      =  500000000000  (5E+11)
(n^2)          = 1000000000000  (1E+12)

1000000000000.00什么?

尽管复杂度为我们提供了一种预测实际成本的方式(秒或字节,取决于我们在谈论时间复杂度还是空间复杂度),但它却没有给我们几秒钟或任何其他特定单位。

它给了我们一定程度的比例。

如果算法必须执行n²次,那么对于某个c值(即每次迭代需要多长时间),它将花费n²×c。

如果一个算法必须做n²÷2次,那么对于某个c值,它的取值是n²×c,是每次迭代所需时间的两倍。

无论哪种方式,花费的时间仍与n²成正比。

现在,这些常数因素不是我们不能忽视的。确实,您会遇到一种情况,即O(n²)复杂度的算法比O(n)复杂度的算法要好,因为如果我们处理少量项目,那么常数因素的影响会更大,并且可能使其他问题无法解决。(实际上,对于足够低的n值,即使O(n!)与O(1)相同)。

但是它们并不是复杂性告诉我们的。

实际上,有几种不同的方法可以改善算法的性能:

  1. 提高每次迭代的效率:O(n²)仍以n²×c秒运行,但是c较小。
  2. 减少出现的情况数量:O(n²)仍以n²×c秒运行,但是n较小。
  3. 用具有相同结果但较低复杂度的算法替换该算法:例如,如果我们可以将O(n²)替换为O(n log n),从而从n²×c₀秒更改为(n log n)×c₁秒。

或者换一种方式来看,我们有 f(n)×c换几秒钟的时间,您可以通过减少c,减少n或减少f给定的收益来提高性能n

首先,我们可以通过在循环内使用一些微光学器件或使用更好的硬件来实现。它总是会有所改善。

第二,我们可以通过确定一种情况来确定,在这种情况下,我们可以在检查所有内容之前将其短路,或者过滤掉一些不重要的数据。如果这样做的代价大于收益,则不会有任何改善,但通常会比第一种情况更大,尤其是n较大时。

第三,我们可以通过完全使用不同的算法来完成。一个经典的例子是用快速排序代替气泡排序。如果元素数量少,我们可能会使情况变得更糟(如果c₁大于c₀),但通常可以带来最大的收益,尤其是当n很大时。

在实际使用中,复杂度度量使我们能够准确地推断出算法之间的差异,因为它们忽略了减少n或c的帮助程度的问题,从而专注于检查 f()


“对于足够低的n值,O(n!)与O(1)相同”是错误的。必须有一种更好的方法来解释“当n保持足够低时,Big-O无关紧要”。
Ben Voigt 2015年

@BenVoigt我还没有遇到与我初读时一样的修辞影响;它最初不是我的,我是从埃里克·利珀特(Eric Lippert)那里偷来的,埃里克·利珀特(Eric Lippert)可能起源于它,也可能是从别人那里获得的。当然,它引用了诸如“对于较小的π值等于3而对于较大的值π等于3”之类的笑话,该笑话仍旧存在。
乔恩·汉纳

0

常数因子

大O表示法的意义在于,您可以选择任意大的常数因子,以使O(function(n))始终大于C * function(n)。如果算法A比算法B慢十亿倍,则它们具有相同的O复杂度,只要当n任意增大时该差不会增大即可。

让我们假设一个常数因子1000000来说明这个概念-它比必要的大100倍,但这说明它们被认为无关紧要。

(n ^ 2 + n)/ 2个“适合” O(n ^ 2),因为对于任何n,无论大小如何,(n ^ 2 + n)/ 2 <1000000 * n ^ 2。

(n ^ 2 + n)/ 2“不适合”较小的集合,例如O(n),因为对于某些值(n ^ 2 + n)/ 2> 1000000 * n。

常数因子可以任意大-运行时间为n年的算法的O(n)复杂度比运行时间为n * log(n)微秒的算法“更好”。


0

Big-O就是关于算法的“复杂程度”。如果您有两种算法,一种算法n^2*k运行几秒钟,另一种算法运行n^2*j几秒钟,那么您可以争论哪种算法更好,并且您可以进行一些有趣的优化来尝试影响kj,但是两者n*m与运行所需的算法相比,这些算法非常慢。不管常量k或多么小j,对于足够大的输入n*m,即使算法很大,算法也总是会获胜m

因此,我们称之为前两种算法O(n^2),而我们称之为第二种O(n)。它将世界很好地划分为算法类别。这就是big-O的全部含义。就像将车辆分为汽车,卡车和公共汽车等...汽车之间有很多差异,您可以整日争论普锐斯是否比雪佛兰Volt好,但最终还是要需要将12个人合二为一,那么这是一个毫无意义的论点。:)

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.