Java 为什么计算复杂度是O(n^4)? int和=0; 对于(int i=1;i

Java 为什么计算复杂度是O(n^4)? int和=0; 对于(int i=1;i,java,big-o,Java,Big O,我不明白当j=I,2i,3i。。。的最后一个循环运行n次。我想我只是不明白我们是如何根据if语句得出这个结论的 编辑:我知道如何计算所有循环的复杂度,除了为什么最后一个循环基于mod运算符执行I次。。。我只是不明白这是怎么回事。基本上,为什么j%i不能上升到i*i而不是i? 第一个循环消耗n迭代 第二个循环消耗n*n迭代。想象一下当i=n,然后j=n*n时的情况 第三个循环消耗n迭代,因为它只执行i次,其中i在最坏的情况下被限制为n 因此,代码复杂度为O(n×n×n×n) 我希望这有助于您理解

我不明白当j=I,2i,3i。。。的最后一个
循环运行n次。我想我只是不明白我们是如何根据
if
语句得出这个结论的


编辑:我知道如何计算所有循环的复杂度,除了为什么最后一个循环基于mod运算符执行I次。。。我只是不明白这是怎么回事。基本上,为什么j%i不能上升到i*i而不是i?

  • 第一个循环消耗
    n
    迭代
  • 第二个循环消耗
    n*n
    迭代。想象一下当
    i=n
    ,然后
    j=n*n
    时的情况
  • 第三个循环消耗
    n
    迭代,因为它只执行
    i
    次,其中
    i
    在最坏的情况下被限制为
    n
因此,代码复杂度为O(n×n×n×n)


我希望这有助于您理解。

让我们看看前两个循环

第一个很简单,从1循环到n。第二个更有趣。从1到i的平方。让我们看一些例子:

e.g. n = 4    
i = 1  
j loops from 1 to 1^2  
i = 2  
j loops from 1 to 2^2  
i = 3  
j loops from 1 to 3^2  
总之,
i和j循环
组合起来有
1^2+2^2+3^2

有一个计算前n个平方和的公式,
n*(n+1)*(2n+1)/6
,大致是
O(n^3)

最后有一个
k循环
,它从0循环到
j
当且仅当
j%i==0
。由于
j
从1变为
i^2
j%i==0
对于
i
次为真。由于
i循环
n
上迭代,因此您有一个额外的
O(n)


因此,您有来自
i和j循环的
O(n^3)
,还有来自
k循环的
O(n)
,总共
O(n^4)

让我们标记循环a、B和C:

int和=0;
//环路A
对于(int i=1;i
  • 循环A迭代O(n)次
  • 循环B在A的每次迭代中迭代O(i2)次。对于每个迭代:
    • 计算
      j%i==0
      ,需要O(1)个时间
    • 在这些迭代的1/i上,循环C迭代j次,每次迭代做O(1)个工作。因为j的平均值是O(i2),并且这只在循环B的1/i迭代中进行,所以平均成本是O(i2/i)=O(i)
将所有这些相加,我们得到O(n×i2×(1+i))=O(n×i3)。因为i的平均值是O(n),所以这是O(n4)


这其中的棘手部分是说,
if
条件只有1/i的时间是真的:

基本上,为什么j%i不能上升到i*i而不是i

事实上,
j
确实上升到
j
,而不仅仅是上升到
j
。但是当且仅当
j
i
的倍数时,条件
j%i==0
为真


范围内的
i
倍数为
i
2*i
3*i
,…,
(i-1)*i
。其中有
i-1
,因此循环C达到
i-1
次,尽管循环B迭代
i*i-1
次。

所有其他答案都是正确的,我只想修正以下内容。 我想看看,减少内部k循环的执行是否足以降低
O(n)以下的实际复杂性⁴).所以我写了以下内容:

for(int n=1;n<363;++n){
整数和=0;
对于(int i=1;i
执行此操作后,很明显,复杂性实际上是
n⁴。输出的最后几行如下所示:

n = 356: iterations = 1989000035, n³ = 45118016, n⁴ = 16062013696, rel = 0.12383254507467704
n = 357: iterations = 2011495675, n³ = 45499293, n⁴ = 16243247601, rel = 0.12383580700180696
n = 358: iterations = 2034181597, n³ = 45882712, n⁴ = 16426010896, rel = 0.12383905075183874
n = 359: iterations = 2057058871, n³ = 46268279, n⁴ = 16610312161, rel = 0.12384227647628734
n = 360: iterations = 2080128570, n³ = 46656000, n⁴ = 16796160000, rel = 0.12384548432498857
n = 361: iterations = 2103391770, n³ = 47045881, n⁴ = 16983563041, rel = 0.12384867444612208
n = 362: iterations = 2126849550, n³ = 47437928, n⁴ = 17172529936, rel = 0.1238518469862343
这表明,实际
n之间的实际相对差异⁴
这个代码段的复杂度是一个因子,它的值大约是
0.124…
(实际上是0.125)。虽然它没有给出准确的值,但我们可以推断如下:

时间复杂度为
n⁴/8~f(n)
其中
f
是您的函数/方法

  • 关于大O表示法的维基百科页面在“Bachmann–Landau表示法家族”的表格中指出,
    ~
    定义了两个操作数边的限制相等。或者: f渐近地等于g

(我选择363作为排除上限,因为
n=362
是我们得到合理结果的最后一个值。之后,我们超出了长空间,相对值变为负值。)

用户kaya3得出以下结论:

顺便说一下,渐近常数正好是1/8=0.125

删除
if
和模,而不改变复杂性 以下是原始方法:

public static long f(int n) {
    int sum = 0;
    for (int i = 1; i < n; i++) {
        for (int j = 1; j < i * i; j++) {
            if (j % i == 0) {
                for (int k = 0; k < j; k++) {
                    sum++;
                }
            }
        }
    }
    return sum;
}
为了更容易地计算复杂性,您可以引入一个中间变量
j2
,以便每个循环变量在每次迭代时递增1:

public static long f3(int n) {
    int sum = 0;
    for (int i = 1; i < n; i++) {
        for (int j2 = 1; j2 < i; j2++) {
            int j = j2 * i;
            for (int k = 0; k < j; k++) {
                sum++;
            }
        }
    }
    return sum;
}
它显然与原始代码的复杂性不同,但它返回的值是相同的

如果你在谷歌上搜索第一个术语,你会发现
0211
public static long f3(int n) {
    int sum = 0;
    for (int i = 1; i < n; i++) {
        for (int j2 = 1; j2 < i; j2++) {
            int j = j2 * i;
            for (int k = 0; k < j; k++) {
                sum++;
            }
        }
    }
    return sum;
}
public static long f4(int n) {
    return (n - 1) * n * (n - 2) * (3 * n - 1) / 24;
}