你会把这种算法的时间复杂度称为什么?

时间:2016-10-03 11:39:13

标签: c# algorithm time-complexity big-o computer-science

我使用的是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;
}

在最坏的情况下,这会被称为在对数时间中完成的操作吗,即使我们再一次不是每次都将迭代次数减半,而是按顺序减少它们幅度超过一半。

4 个答案:

答案 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
    }
}

在此示例中,i0转到n,因此我们花费O(n)时间来增加i。同样,j0变为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 成长)。

如果上述任何内容对您来说是新的,您可以参考任何关于复杂性理论的介绍性书籍。

Snippet 1

为了使分析变得非常简单,我们假设每次迭代都需要恒定的时间 然后总时间是 n 1/2 的迭代次数。
由于 n 1/2 = 2 | n | / 2 ,我们认为时间复杂度

  

2 O (| n |),指数

Snippet 2

在迭代 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

  • 对于您的第一个代码段,对于大小为 n 的输入,运行时间将与sqrt( n )成比例。因此,运行时间可以写为* sqrt( n ),它与k * n 的功能不同。用于描述这种比例性的词只是平方根关系
  • 对于您的第二个代码段,您将继续使用 n 的平方根,直到它减少为1.可以将其重现为:
    • T(n)= T(sqrt(n))+ 1
    • 要解决这种情况,请使用 n = 2 2 i 。现在, i 实际上是代码片段运行的次数,导致 n 或Log(Log(n))的双重日志作为此运行时间片段。

要解决您的第二个片段,我实际上问了this question on Math SE

答案 3 :(得分:0)

第一个代码段的复杂度为O(sqrt(n))

第二个代码段的复杂度为O(log(log(n))。只需要一个小的10年级代数来解决n^1/(2^i) < 2的等式i,这是第二个片段的复杂性。