我使用的是C#语法,但这个问题并不仅限于C#。
示例1
public static long Do(long n)
{
var sqrt = (long)Math.Sqrt(n);
for(long i = 0; i < sqrt; i++)
// do something
return result;
}
这仍然是线性时间即使在最坏的情况下,我们只对n
的平方根时间进行操作,这只是一小部分n
?
示例2
您如何对下面算法的时间复杂度进行分类?
public static long Do(long n)
{
while (n > 1)
{
n = (long)Math.Sqrt(n);
// do something
}
return result;
}
在最坏的情况下,这会被称为在对数时间中完成的操作吗,即使我们再一次不是每次都将迭代次数减半,而是按顺序减少它们幅度超过一半。
答案 0 :(得分:0)
第一个代码片段只包含一个循环和此循环之外的常量操作数。如果此循环在每次迭代花费k
时间时迭代t
次,则其复杂度为O(kt)
。这里,k
等于sqrt(n)
,这意味着如果循环不包含非常量时间操作(例如,如果它不包含嵌套循环或循环函数等),则此片段时间复杂度等于O(sqrt(n))
,也写为O(√n)
。
这里有一个循环的事实并不意味着复杂性。例如,以下代码具有两个嵌套循环,具有线性复杂性:
int j = 0;
for (int i = 0; i < n; ++i)
{
for (; j < n; ++j)
{
// A loop with constant-time operations and eventual breaks
}
}
在此示例中,i
从0
转到n
,因此我们花费O(n)
时间来增加i
。同样,j
从0
变为n
,我们O(n)
增量j
变量以及内循环的O(n)
次迭代身体。由于此代码中没有其他操作,因此总复杂度为O(n) + O(n) + O(n) = O(n)
。
为了处理第二个例子,我以递归的方式重写它:
int Do(int n)
{
// Do something with constant-time compexity
return n > 1 ? Do(sqrt(n)) : result;
}
我们将此示例的时间复杂度称为T(n)
。我们可以看到T(n) = 1 + T(sqrt(n))
,其中计算该函数的第一部分的时间(其是常数)被视为时间单位。求解这个递归方程给出了T(n) = log log n
(这里的对数是二进制)。确实,1 + log log(sqrt(n)) = 1 + log ((log n) / 2) = 1 + log log n - 1 = log log n
。对于渐近表达式,使用对数的哪个基数并不重要,因为log_a x = log_a b * log_b x = O(log_b x)
,这就是为什么通常省略对数基数。
因此,复杂性为:O(√n)
和O(log log n)
。
UP :要非严格估算您的复杂程度,可以使用Excel或任何其他软件工具进行计算。您只需要为n
的不同值构建一个操作数量表,并尝试猜测复杂性规则。例如,对于问题的代码段#2:
N Operations log n log log n 1 1 0 - 2 2 1 0 4 3 2 1 16 4 4 2 256 5 8 3 65536 6 16 4
表中通常可以明显看出正确的答案
答案 1 :(得分:0)
复杂性是根据问题实例的编码长度来衡量的
两个片段中的问题完全由参数 n 描述,因此实例是一个字符串&lt; n&gt; ,它编码 n 。
我们可以使用任何非退化编码,文献中常见的选择是在任何数字位置系统中编写 n 。
例如,如果 n 为13,我们可以使用&#34; 13&#34;或&#34; 1101&#34;或&#34; 15&#34;或&#34; D&#34; as &lt; n&gt; 。
很容易看到&lt; n&gt; 的长度,这里表示为 | n | ,是 log(n)。
它不完全 log(n),我们需要知道基数以给出确切的长度,但是我们不需要精确的长度函数给出 n 的L(n)完全返回 | n | ,我们只是一个 O (L(n))无论如何都会使用big-O表示法
我们可以通过乘以常数将base更改为任何日志,因此使用 L(n)= log(n)可以。
但是,如果我们在 | n | 的功能中使用“ n ”来指定基础,我们只能说 n = 2 < sup> O (| n |) 会使公式过于拥挤。
由于我们通过指定基数没有失去一般性,我们可以选择基数2,从而 | n | = log 2 (n)和 n = 2 | n | 的限制(作为 n 成长)。
如果上述任何内容对您来说是新的,您可以参考任何关于复杂性理论的介绍性书籍。
为了使分析变得非常简单,我们假设每次迭代都需要恒定的时间
然后总时间是 n 1/2 的迭代次数。
由于 n 1/2 = 2 | n | / 2 ,我们认为时间复杂度
2 O (| n |),指数
在迭代 k 时, n 的原始值已减少为 n 1 /(2k) 。然后我们想要找到 k 这样
n 1/2 k ≤1
以便循环结束。
但是对于 k 的任何有限值,这个条件永远不会得到满足(证明这是一个练习)
然而,部分结果被转换为整数,而不回复到floor函数,我们只需要 n 1/2 k ≤2 n 1/2 k 是两个,在下一次迭代中它将被转换为一个。
n 1/2 k ≤2 =&gt; n≤2 2 k =&gt; log 2 (log 2 (n))≤k
由于 log 2 (n)是 log 2 (2 | n | )= | n | 时间复杂度
O (log 2 (| n |)),对数
就大暴行而言,可以做出更强烈的陈述,但这留给了读者。
答案 2 :(得分:0)
线性时间:表示对于大小为 n 的输入,时间复杂度将与 n 本身成比例。换句话说,大小 n = k * n 问题的时间复杂度(猜猜二次时间是什么意思?对于大小 n 的输入,运行时间与 n 2 成正比...时间复杂度= c * n 2 )
要解决您的第二个片段,我实际上问了this question on Math SE。
答案 3 :(得分:0)
第一个代码段的复杂度为O(sqrt(n))
。
第二个代码段的复杂度为O(log(log(n))
。只需要一个小的10年级代数来解决n^1/(2^i) < 2
的等式i
,这是第二个片段的复杂性。