【问题标题】:What would you call the time complexity of an algorithm of this sort?你会称这种算法的时间复杂度是多少?
【发布时间】:2017-02-11 08:17:29
【问题描述】:

我使用的是 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 的平方根时间进行运算,这只是@987654324 的一小部分@?

示例 2

您如何对下面算法的时间复杂度进行分类?

public static long Do(long n)
{
    while (n > 1)
    {
        n = (long)Math.Sqrt(n);

        // do something
    }

    return result;
}

在最坏的情况下,这是否会被称为在 对数时间 内完成的操作,即使我们再次将每次迭代次数减半,而是将它们减少了一个数量级幅度超过一半。

【问题讨论】:

  • 如果你在做 sqrt(n) 操作,那将是 O(sqrt(n)) 复杂度,为什么是线性的?
  • 第一个是O(n^(1/2))。第二个有点棘手。
  • O(sqrt(n)) 有术语吗?
  • O(sqrt(n)) 是 O(sqrt(n)) 的“术语”......不知道还有什么。此外,在此示例中,请注意“输入大小”。因为你实际上没有 n 个元素,而是一个整数 n,所以时间复杂度通常以输入整数的大小(以位为单位)给出(因为图灵机)。将输入大小增加一位会使 n 加倍,使其花费 sqrt(2) 更多时间——因此按照这种方法,这将是指数级的,或者 O(sqrt(2) ^ n)(这个“n”与您的变量 n) 不同
  • 在维基百科 (en.wikipedia.org/wiki/Time_complexity) 中它被命名为“分数幂”

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


【解决方案1】:

第一个代码 sn-p 只包含一个循环和该循环之外的恒定数量的操作。如果此循环迭代k 次而每次迭代花费t 时间,则其复杂度为O(kt)。这里,k 等于 sqrt(n),这意味着如果循环不包含非常量时间操作(例如,如果它不包含嵌套循环或循环函数等),那么这个 sn-p 时间复杂度是相等的到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 的不同值构建一个操作数表,并尝试猜测复杂性规则。例如,对于问题中的代码 sn-p #2:

N 操作日志 n 日志日志 n 1 1 0 - 2 2 1 0 4 3 2 1 16 4 4 2 256 5 8 3 65536 6 16 4

正确答案通常从表格中显而易见

【讨论】:

    【解决方案2】:

    复杂性是根据问题实例的编码长度来衡量的。
    两个sn-ps中的问题完全由参数n描述,所以一个实例是一个字符串,它编码了n。 我们可以使用任何非退化编码,文献中的一个常见选择是在任何数字位置系统中编写n
    例如,如果 n 是 13,我们可以使用“13”或“1101”或“15”或“D”作为

    那么很容易看出的长度,这里记为|n|,是log(n) .
    不完全是 log(n),我们需要知道底数才能给出准确的长度,但是我们不需要准确的长度函数 L( n) 给定 n 准确地返回 |n|,我们只是一个 O(L(n)) ,因为我们将使用无论如何,大 O 符号。
    我们可以通过乘以一个常数将基数更改为任何对数,因此使用 L(n) = log(n) 就可以了。
    但是,如果我们在 |n| 的函数中表达 n 时使用不指定基数,我们只能说 n = 2O(|n|) 会使公式过于拥挤。
    由于我们通过指定一个基数并没有失去一般性,我们可以选择基数二,因此 |n| = log2(n)n = 2|n| 在限制中(如 n 增长)。

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

    片段 1

    为了保持分析非常简单,我们假设每次迭代都需要恒定的时间。
    那么总次数就是迭代次数,即n1/2
    由于 n1/2 = 2|n|/2 我们有时间复杂度是

    2O(|n|),指数

    片段 2

    在迭代 k 时,n 的原始值已减少到 n1/(2k) .然后我们想找到 k 使得

    n1/2k ≤ 1

    这样循环就结束了。
    但是对于 k 的任何有限值,永远无法满足该条件(证明这一点作为练习)。
    然而,部分结果被转换为整数,无需恢复到 floor 函数,我们可以只要求 n1/2k ≤ 2 因为一次n1/2k 是 2,在下一次迭代中它将被强制转换为 1。

    n1/2k ≤ 2 => n ≤ 22k => log2(log2(n)) ≤ k

    因为 log2(n)log2(2|n|) = |n| 时间复杂度为

    O(log2(|n|)), 对数


    可以做出更强有力的陈述,就大问题而言,但这留给读者。

    【讨论】:

      【解决方案3】:

      线性时间: 表示对于大小为 n 的输入,时间复杂度将与 n 本身成正比。换句话说,大小为 n = k * n 的问题的时间复杂度(猜猜二次时间是什么意思?对于大小为 n 的输入, 运行时间与 n2... 的时间复杂度成正比 = c * n2)

      • 对于您的第一个 sn-p,对于大小为 n 的输入,运行时间将与 sqrt(n) 成正比。因此运行时间可以写成 * sqrt(n),它与 ​​k * n 的函数不同。用来描述这种比例性的词只是平方根关系
      • 对于您的第二个 sn-p,您继续取 n 的平方根,直到它减少到 1。其递归可写为:
        • T(n)= T(sqrt(n)) + 1
        • 要解决这种重复,请取 n = 22i。现在,i 实际上将是代码 sn-p 将运行的次数,导致 n 或 Log(Log(n)) 作为运行时间的双 log对于这个 sn-p。

      为了解决你的第二次sn-p,我居然问了this question on Math SE

      【讨论】:

        【解决方案4】:

        第一个 sn-p 的复杂度为O(sqrt(n))

        第二个 sn-p 的复杂度为O(log(log(n))。只需要一点 10 年级的代数就可以解出方程 n^1/(2^i) &lt; 2i,这是第二个 sn-p 的复杂度。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-03-27
          • 1970-01-01
          • 2015-06-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多