所以这些是我必须找到时间复杂度的for循环,但我并不清楚如何计算。
for (int i = n; i > 1; i /= 3) {
for (int j = 0; j < n; j += 2) {
... ...
}
for (int k = 2; k < n; k = (k * k) {
...
}
对于第一行,(int i = n; i&gt; 1; i / = 3),保持潜水i为3,如果i小于1则循环停在那里,对吧?
但那时间复杂度是多少?我认为这是n,但我不太确定。 我认为它是n的原因是,如果我假设n是30然后我会像30,10,3,1那么循环停止。它运行n次,不是吗?
对于最后一个for循环,我认为它的时间复杂度也是n,因为它的作用是
k从2开始并保持自身相乘直到k大于n。
因此,如果n为20,则k将为2,4,16然后停止。它也运行了n次。
我真的不认为我理解这类问题,因为时间复杂度可以是log(n)或n ^ 2等,但我看到的只有n。
我不知道什么时候记录或正方形。或其他任何事情。
我认为每个for循环运行n次。如何记录日志或方块?
任何人都可以帮我理解这个吗?请。
答案 0 :(得分:2)
<强> TL;博士;我首先描述了几个例子,我在本文的底部分析了所述OP问题的复杂性
简而言之,大O表示法会告诉您如果缩放输入,程序将如何执行。
想象一个计数为100的程序(P0)。无论你多久运行一次程序,它每次都会快速计数到100(给予或接受)。显然对吗?
现在假设一个程序(P1)计数到一个可变的数字,即它将一个数字作为它所计数的输入。我们将此变量称为n
。现在每次P1运行时,P1的性能取决于n
的大小。如果我们将n
设为100,则P1将非常快速地运行。如果我们将n
等同于googleplex,那么它会花费更长的时间。
基本上,P1的性能取决于n
的大小,这就是我们说P1具有时间复杂度O(n)
的含义。
现在想象一个程序(P2),我们计算n
的平方根,而不是它自己。显然,P2的性能将比P1差,因为它们的数量差别很大(特别是对于较大的n
(=缩放))。如果P1的复杂度等于O(n^2)
,你会直觉地知道P2的时间复杂度等于O(n)
。
现在考虑一个如下所示的程序(P3):
var length= input.length;
for(var i = 0; i < length; i++) {
for (var j = 0; j < length; j++) {
Console.WriteLine($"Product is {input[i] * input[j]}");
}
}
此处没有n
,但您可能会发现,此程序仍取决于此处名为input
的输入。仅仅因为程序依赖于某种输入,如果我们谈论时间复杂性,我们将此输入声明为n
。如果一个程序需要多个输入,我们只需调用那些不同的名称,这样时间复杂度就可以表示为O(n * n2 + m * n3)
,这个假设的程序将需要4个输入。
对于P3,我们可以通过首先分析不同输入的数量,然后通过分析它的性能取决于输入的方式来发现它的时间复杂度。
P3有3个变量,名为length
,i
和j
。第一行代码执行一个简单的赋值,即&#39;性能不依赖于任何输入,这意味着该行代码的时间复杂度等于O(1)
意味着恒定的时间。
第二行代码是一个for
循环,暗示我们要做一些可能取决于某事物长度的事情。确实,我们可以告诉第一个for
循环(以及其中的所有内容)将执行length
次。如果我们增加length
的大小,这行代码将线性地更多,因此这行代码的时间复杂度为O(length)
(称为线性时间)。
下一行代码将按照与之前相同的逻辑再次O(length)
次,但由于我们每次执行此操作都会执行此循环,因此时间复杂度将成倍增加由它:导致for
。
第二个O(length) * O(length) = O(length^2)
循环的内部不依赖于输入的大小(即使输入是必要的),因为如果我们增加输入(对于数组!!)的索引将不会变慢输入的大小。这意味着内部将是恒定时间= for
。由于它在另一个O(1)
循环的一侧运行,我们再次必须将它相乘以获得嵌套代码行的总时间复杂度:`for for循环*当前代码块= O(长度^ 2 )* O(1)= O(长度^ 2)。
该计划的总时间复杂度只是我们计算的所有内容的总和:for
。第一行代码为O(1) + O(length^2) = O(length^2) = O(n^2)
,O(1)
循环分析为for
。你会注意到两件事:
O(length^2)
重命名为length
:我们这样做是因为我们表达了
基于通用参数的时间复杂度而不是基于通用参数的时间复杂度
碰巧住在程序中。n
。我们这样做是因为我们只是
对最大的术语感兴趣(=增长最快)。自O(1)
以来
是更大的方式&#39;比O(n^2)
,时间复杂度定义为等于
它(这只适用于术语(例如,由O(1)
分割),而不是
因素(例如按+
分割)。现在我们可以考虑你的程序(P4)有点棘手,因为程序中的变量定义比我的示例中的变量稍微多一点。
*
如果我们分析,我们可以这样说:
for (int i = n; i > 1; i /= 3) {
for (int j = 0; j < n; j += 2) {
... ...
}
for (int k = 2; k < n; k = (k * k) {
...
}
}
次,O(cbrt(3))
是其输入的立方根。由于cbrt
每个循环除以3,因此i
的立方根是n
小于或等于1之前循环需要执行的次数。i
循环在时间上是线性的,因为执行了for
j
次,因为它增加了2而不是1
会是正常的#39;我们可以说,因为我们知道O(n / 2)
这个for循环执行O(n/2) = O(n)
次(第一个O(cbrt(3)) * O(n) = O(n * cbrt(n))
*嵌套for
)。for
也嵌套在第一个for
中,但由于它没有嵌套在第二个for
中,我们不会将它乘以第二个for
一个(显然是因为它只在每次执行第一个for
时执行)。在这里,k
受n
的约束,但由于它每次都增加了一个因子,我们不能说它是线性的,即它的增加是由变量而不是变量来定义的。一个常数。由于我们将k
增加了一个因子(我们将其平方),它将在 n
2
步骤中达到log(n)
。如果您了解log
的工作方式,那么推断这一点很简单,如果您不了解这一点,则首先需要了解这一点。在任何情况下,由于我们分析此for循环将运行O(
2
log(n))
时间,因此第三个for
的总复杂度为O(cbrt(3)) * O(
2
log(n))
= O(cbrt(n) *
2
log(n))
O(n * cbrt(n)) + O(cbrt(n) *
2
log(n))
正如我们之前看到的,如果我们谈论大O符号,我们只关心增长最快的术语,所以我们说程序的时间复杂度等于O(n * cbrt(n))
。
答案 1 :(得分:2)
由于所有三个循环彼此独立,我们可以单独分析它们并在结尾处将结果相乘。
<强> 1。 i
循环
经典的对数循环。 SO上有无数的例子,this是类似的例子。使用该页面上给出的结果并替换除法常量:
此循环执行的确切次数为
ceil(log3(n))
。
<强> 2。 j
循环
正如您所理解的那样,这会运行O(n / 2)
次;
确切的数字是
floor(n / 2)
。
第3。 k
循环
另一个经典的已知结果 - log-log 循环。代码恰好是this SO帖子的完全复制品;
确切的数字是
ceil(log2(log2(n)))
结合上述步骤,总时间复杂度由
给出请注意,j
- 循环遮盖了k
- 循环。
确认的数字测试
JavaScript代码:
T = function(n) {
var m = 0;
for (var i = n; i > 1; i /= 3) {
for (var j = 0; j < n; j += 2)
m++;
for (var k = 2; k < n; k = k * k)
m++;
}
return m;
}
M = function(n) {
return ceil(log(n)/log(3)) * (floor(n/2) + ceil(log2(log2(n))));
}
M(n)
是数学预测T(n)
将完全是(内循环执行次数):
n T(n) M(n)
-----------------------
100000 550055 550055
105000 577555 577555
110000 605055 605055
115000 632555 632555
120000 660055 660055
125000 687555 687555
130000 715055 715055
135000 742555 742555
140000 770055 770055
145000 797555 797555
150000 825055 825055
M(n)
完全按预期匹配T(n)
。 T(n)
对n log n
的图(预测时间复杂度):
我认为这是一条令人信服的直线。