我欠你一个道歉。我第一次看错了你的代码,所以我给出的最初答案是不正确的。这是一个更正的答案,以及与解释我的分析出错的原始答案的比较。我希望你觉得这很有趣 - 我认为这会产生一些非常酷的数学!
您发布的代码如下所示:
for (int i=1; i<=n; i++)
for (int j=1; j<=n*n/i; j+=i)
printf(“*”);
要确定这段代码的运行时间,让我们看看内循环在所有迭代中做了多少工作。当 i = 1 时,循环计数到 n2 个,所以它确实 n2 工作。当 i = 2 时,循环计数最多为 n2 / 2,因此它确实 n2 / 4 工作。当 i = 3 时,循环计数到 n2 / 3 三个,所以它确实 n2 / 9 工作。更一般地,第 k 次迭代执行 n2 / k2 工作,因为它以大小为 k 的步长计数到 n2 / k。
如果我们总结这里完成的工作 i 范围从 1 到 n,包括在内,我们看到运行时是
n2 + n2 / 4 + n2 / 9 + n2 / 16 + 。 .. + n2 / n2
= n2 (1 + 1/4 + 1/9 + 1/16 + 1/25 + ... + 1/n2)。
这里的求和 (1 + 1/4 + 1/9 + 1/16 + ...) 具有(令人惊讶的!)属性,在极限情况下,it's exactly equal to π2 / 6. 换句话说,您的代码的运行时渐近接近 n2 π / 6,因此运行时间为 O(n2)。您可以通过编写一个将实际步数与 n2 π / 6 进行比较并查看结果的程序来了解这一点。
我第一次弄错了,因为我误读了你的代码,好像它是这样写的
for (int i=1; i<=n; i++)
for (int j=1; j<=n*n/i; j+=1)
printf(“*”);
换句话说,我认为内部循环在每次迭代中采用大小为 1 的步数,而不是大小为 i 的步数。在这种情况下,循环的第 k 次迭代所做的工作是 n2 / k,而不是 n2 / k2,即运行时间为
n2 + n2/2 + n2/3 + n2/4 + 。 ..n2/n
= n2(1 + 1/2 + 1/3 + 1/4 + ... + 1/n)
在这里,我们可以使用 1 + 1/2 + 1/3 + ... + 1/n 是众所周知的求和。第 n 个harmonic number 定义为 Hn = 1 + 1/2 + 1/3 + ... + 1/n 已知调和数服从 Hn sub> = Θ(log n),所以这个版本的代码运行时间为 O(n2 log n)。有趣的是,这种变化如何如此显着地改变了代码的运行时间!
作为一个有趣的概括,假设您更改了内部循环,使得对于某些 ε > 0 的步长为 iε(并假设您向上取整)。在这种情况下,通过内循环的第 k 次迭代次数将为 n2 / k1 + ε,因为循环的上限为 n2 / k 并且您正在采取大小为 kε 的步数。通过与我们之前看到的类似的分析,运行时将是
n2 + n2 / 21+ε + n2 / 31 +ε + n2 / 31+ε + ... + n2 / n1+ε
= n2(1 + 1/21+ε + 1/31+ε + 1/4 1+ε + ... + 1/n1+ε)
如果您参加过微积分课程,您可能会认识到该系列
1 + 1/21+ε + 1/31+ε + 1/41+ε + ... + 1/n1+ε
收敛到任何 ε > 0 的某个固定限制,这意味着如果步长是 any i 的正幂,则整体运行时间将为 O(n2 )。这意味着以下所有代码段的运行时间都为 O(n2):
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(“*”);