运行时功能

时间:2016-02-13 18:32:51

标签: c runtime big-o time-complexity

让函数f()为:

void f(int n)
{
   for (int i=1; i<=n; i++)
     for (int j=1; j<=n*n/i; j+=i)
       printf(“*”);
}

根据我的计算,Big O方法的运行时间应为O(n 2 log n)。
答案是O(n 2 )。那是为什么?

3 个答案:

答案 0 :(得分:3)

是的,我欠你一个道歉。我第一次误读你的代码,所以我给出的初步答案是错误的。这是一个更正的答案,并与原始答案进行比较,解释了我的分析出错的地方。我希望你发现这很有意思 - 我认为有一些非常酷的数学来自于此!

您发布的代码如下所示:

for (int i=1; i<=n; i++)
  for (int j=1; j<=n*n/i; j+=i)
     printf(“*”);

要确定此代码的运行时间,让我们看看内部循环在所有迭代中的工作量。当i = 1时,循环计数到n 2 为1,因此它的n 2 工作。当i = 2时,循环计数达到n 2 / 2两倍,所以n 2 / 4工作。当i = 3时,循环计数达到n 2 / 3三分之一,因此n 2 / 9工作。更一般地,第k次迭代进行n 2 / k 2 工作,因为它以大小为k的步长计数到n 2 / k。

如果我们总结一下这里完成的工作,范围从1到n,包括在内,我们看到运行时是

  

n 2 + n 2 / 4 + n 2 / 9 + n 2 / 16 +。 .. + n 2 / n 2

     

= n 2 (1 + 1/4 + 1/9 + 1/16 + 1/25 + ... + 1 / n 2 )。< / p>

此处的总和(1 + 1/4 + 1/9 + 1/16 + ...)具有(令人惊讶的!)属性,在限制中,it's exactly equal to π2 / 6.换句话说,运行时你的代码渐近逼近n 2 π/ 6,因此运行时为O(n 2 )。您可以通过编写一个程序来比较n 2 π/ 6的实际步数,并查看结果。

我第一次出错了,因为我误读了你的代码,好像它被写成

for (int i=1; i<=n; i++)
  for (int j=1; j<=n*n/i; j+=1)
     printf(“*”);

换句话说,我认为内循环在每次迭代时采用大小为1的步骤而不是大小为i的步长。在这种情况下,循环的第k次迭代完成的工作是n 2 / k,而不是n 2 / k 2 ,其中给出了

的运行时间
  

n 2 + n 2 / 2 + n 2 / 3 + n 2 / 4 +。 ...... N 2 / N

     

= n 2 (1 + 1/2 + 1/3 + 1/4 + ... + 1 / n)

这里,我们可以使用1 + 1/2 + 1/3 + ... + 1 / n是众所周知的求和的事实。第n harmonic number定义为H n = 1 + 1/2 + 1/3 + ... + 1 / n,并且已知谐波数遵循H n =Θ(log n),因此该版本的代码在时间O(n 2 log n)中运行。有趣的是,这种变化如此显着地改变了代码的运行时间!

作为一个有趣的概括,让我们假设您改变内循环,使得步长为i ε,对于某些ε&gt; 0(并假设你向上舍入)。在这种情况下,通过内环的第k次的迭代次数将是n 2 / k 1 +ε,因为循环的上限是n < sup> 2 / k你正在采取大小为k ε的步骤。通过与我们之前看到的类似的分析,运行时将是

  

n 2 + n 2 / 2 1 +ε + n 2 / 3 1 +ε + n 2 / 3 1 +ε + ... + n 2 / n 1 +ε功能

     

= n 2 (1 + 1/2 1 +ε + 1/3 1 +ε + 1/4 1 +ε + ... + 1 / n 1 +ε

如果您参加过微积分课程,您可能会认识到该系列课程

  

1 + 1/2 1 +ε + 1/3 1 +ε + 1/4 1 +ε + ... + 1 / n 1 +ε

对于任何ε> p,

收敛到某个固定极限。 0,表示如果步长是 i的任何正幂,则整个运行时间将为O(n 2 )。这意味着以下所有代码都具有运行时O(n 2 ):

for (int i=1; i<=n; i++)
  for (int j=1; j<=n*n/i; j+=i)
     printf(“*”);

for (int i=1; i<=n; i++)
  for (int j=1; j<=n*n/i; j+=i*i)
     printf(“*”);

for (int i=1; i<=n; i++)
  for (int j=1; j<=n*n/i; j+=i*(sqrt(i) + 1))
     printf(“*”);

答案 1 :(得分:1)

第一个循环的运行时间为n,第二个循环的运行时间为(n/i)^2(不是n^2/i),因为我们有j+=i(不是j++) 。所以总时间如下:

∑{i=1to n}(n/i)^2 = n^2∑{i=1to n}(1/i)^2 < 2*n^2

  

所以时间复杂度为O(n^2)

答案 2 :(得分:-1)

从我从理论中学到的是,i不会对复杂性产生太大影响。由于您具有指数函数,因此将忽略log n。因此,只考虑大O(n 2 )而不是预期的O(n 2 log n)。

  

回想一下,当我们使用big-O表示法时,我们会删除常量和低阶项。这是因为当问题规模足够大时,这些术语并不重要。但是,这意味着两个算法可以具有相同的大O时间复杂度,即使一个总是比另一个快。例如,假设算法1需要N 2 时间,算法2需要10 * N 2 + N时间。对于这两种算法,时间是O(N 2 ),但算法1总是比算法2快。在这种情况下,常数和低阶项在哪个算法方面都很重要。实际上更快。

     

然而,重要的是要注意,常数在算法&#34;如何扩展&#34;的问题方面无关紧要。 (即,当问题规模加倍时算法的时间如何变化)。虽然需要N 2 时间的算法总是比需要10 * N 2 时间的算法更快,但对于两种算法,如果问题大小加倍,实际时间将翻两番。

     

当两种算法具有不同的大O时间复杂度时,常数和低阶项仅在问题大小较小时才有效。例如,即使涉及大的常数,线性时间算法最终总是比二次时算法快。下表说明了这一点,该表显示了100 * N(N中为线性的时间)的值和N 2 / 100的值(N中为二次方的时间)一些N值。对于N小于104的值,二次时间小于线性时间。但是,对于N大于104的所有值,线性时间都较小。

请查看this文章了解详情。