【问题标题】:Time complexity of 3 nested loops with a condition具有条件的 3 个嵌套循环的时间复杂度
【发布时间】:2021-12-15 15:09:09
【问题描述】:

这个函数的时间复杂度(大 O)是多少?以及如何计算?

我认为是 O(N^3) 但不确定。

int DAA(int n){
    int i, j, k, x = 0;
    for(i=1; i <= n; i++){
        for(j=1; j <= i*i; j++){
            if(j % i == 0){
                for(k=1; k <= j; k++){
                    x += 10;
                }
            }
        }
    }
    return x;
}

【问题讨论】:

  • 请同时添加您认为的内容。以及为什么。
  • 我认为是 O(N^3)
  • 我认为@appleapple 寻找的不仅仅是一个疯狂的猜测。您认为复杂度为 O(N^3) 的背后有什么原因吗?

标签: c++ algorithm time-complexity big-o


【解决方案1】:

复杂度是O(n^4)

但不是因为你盲目地放弃未使用的迭代。

这是因为当您考虑所有指令时,O(n + n^3 + n^4) = O(n^4)

int DAA(int n){
   int x = 0;
   for(int i=1; i <= n; i++) // O(n)
      for(int j=1; j <= i*i; j++) // O(1+2+...n^2) = O(n^3)
         if(j % i == 0) // O(n^3) same as loop j
            for(int k=1; k <= j; k++) // O(n^4), see below
               x += 10; // O(n^4) same as loop k

   return x;
}

条件内循环的复杂性

循环k仅在j%i==0时执行,即{i, i*2, i*3 ... i*i}

所以对于最内层循环执行的情况,算法是有效的

int DAA(int n){
   int x = 0;
   for(int i=1; i <= n; i++) // O(n)
      for(int t=1; t <= i; t++) // O(1+2+...+n) = O(n^2)
         for(int k=1; k <= t*i; k++) // O(n^4)
               x += 10;
   return x;
}


为什么简单地放弃未使用的迭代不起作用?

假设是现在

int DAA(int n){
   int x = 0;
   for(int i=1; i <= n; i++) // O(n)
      for(int j=1; j <= i*i; j++) // O(1+2+...+n^2) = O(n^3)
         if(j == i) 
            for(int k=1; k <= j; k++)
               x += 10; // oops! this only run O(n^2) time

   return x;
}
// if(j==i*log(n)) also cause loop k becomes O((n^2)log(n))
// or, well, if(false) :P

虽然最里面的指令只运行O(n^2)时间。该程序实际上做了if(j==i)(和j++,j&lt;=i*i)O(n^3)时间,这使得整个算法O(n^3)

【讨论】:

    【解决方案2】:

    如果您摆脱无所事事的迭代,时间复杂度会更容易计算。除非ji 的倍数,否则中间循环不会做任何事情。所以我们可以强制ji 的倍数,并去掉if 语句,这样代码更容易分析。

    int DAA(int n){
        int x = 0;
        for(int i=1; i <= n; i++){
            for(int m=1; m <= i; m++){  // New variable to avoid the if statement
                int j = m*i;            // The values for which the inner loop executes
                for(int k=1; k <= j; k++){
                    x += 10;
                }
            }
        }
        return x;
    }
    

    外循环迭代n 次。到目前为止 O(n)。

    中间循环迭代1 次,然后2 次,然后... n 次。人们可能会从 O(n2) 排序算法中识别出这种设置。循环执行n次,迭代次数增加到n,导致O(n×n)复杂度。

    内部循环按n×n次的顺序执行(中间循环的复杂度)。每次执行的迭代次数增加到 n×n(最大值j)。类似于中间循环如何将其执行次数和最大迭代次数相乘以获得其复杂度,内部循环的复杂度——因此整个代码的复杂度——应该变为 O(n4) ,但我会把精确的证明留作练习。


    上面确实假设时间复杂度代表x += 10;被执行的次数。也就是说,它假设最内层循环的主要工作压倒了其余的工作。这通常是人们感兴趣的,但也有一些注意事项。

    第一个警告是,添加10 并没有比增加更多的工作量。如果x += 10; 行不是“做工作”的方便替代,那么时间复杂度可能应该包括所有迭代,即使是那些不工作的迭代。

    第二个警告是if 语句中的条件相对于最内层循环来说是廉价的。在某些情况下,条件可能很昂贵,因此时间复杂度应该包括if 语句的执行次数。消除if 语句确实会干扰这一点。

    如果您碰巧遇到这些警告之一,您需要计算遗漏的内容。修改后的代码在其每个n 执行中省略了中间循环的 i2-i 次迭代。因此,省略的迭代将对整体复杂度贡献 n 倍 n2-n,或 O(n3)。

    因此,原代码的复杂度为O(n4 + n3),与O(n4相同>)。

    【讨论】:

    • 我认为不管条件如何都忽略迭代是不好的。说如果它是if(j==1) 那么它会改变结果的复杂性。
    • @appleapple 我想我确实让一些假设溜走了而没有承认它们。这些假设通常在过度复杂或人为的代码 sn-ps 之外是有效的,但是,当然,应该为未来的访问者提及它们。另外,这是一个快速的小附加计算。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多