【问题标题】:Example of Big O of 2^n2^n 的大 O 示例
【发布时间】:2016-04-27 05:35:40
【问题描述】:

所以我可以想象一个复杂度为 n^c 的算法是什么,只是嵌套 for 循环的数量。

for (var i = 0; i < dataset.len; i++ {
    for (var j = 0; j < dataset.len; j++) {
        //do stuff with i and j
    }
}

日志是每次将数据集分成两半的东西,二进制搜索就是这样做的(不完全确定它的代码是什么样的)。

但是什么是 c^n 或更具体地说是 2^n 的算法的简单示例。 O(2^n) 是否基于数据循环?或者数据是如何分割的?还是完全不同的东西?

【问题讨论】:

  • n^c??你的意思是 n^2 因为这就是你的例子所显示的......(这是关于你问题中的第一句话)
  • @JohnD c 只是一个表示嵌套循环数的变量。 n^2 将是我给出的示例,n^3 将是 3 个嵌套循环,等等。

标签: algorithm big-o time-complexity


【解决方案1】:

想想例如遍历集合的所有可能子集。例如,这种算法用于广义背包问题。

如果您很难理解对子集的迭代如何转换为 O(2^n),请想象一组 n 个开关,每个开关对应于一组中的一个元素。现在,每个开关都可以打开或关闭。将“on”视为子集中。注意,有多少种组合是可能的:2^n。

如果您想查看代码中的示例,通常在这里考虑递归更容易,但我现在想不出任何其他好的且不稳定的示例。

【讨论】:

  • 这实际上是O(n * 2^n) 复杂度。
  • @SanketMakani 如何迭代所有位长度为n 的二进制数与O(n * 2^n) 相关?除非您当然假设将 n 位数字递增为 O(n)(恕我直言,这是完全正确的,但 许多人会不同意)这有点类似于说,迭代 n 数字需要O(n log n) 也就是说,如果您计算单个位操作,则正确,但通常会做出一些假设。
  • 当您遍历所有可能的2^n 数字时,您需要检查数字的每一位以检查子集中是否存在元素。我们认为检查是否设置了某个位需要O(1) 时间,但您仍然需要遍历所有n 位,因此对于每个2^n 数字都需要n 迭代。所以总复杂度是O(n * 2^n)
  • @SanketMakani 您基本上是在重复我写的内容:“当然,除非您假设将 n 位数递增为 O(n)”。尽管如此,关于迭代 n 值取 O(n log n) 的论点仍然成立。
  • 不,我没有考虑过增加n 会导致另一个O(n)overhead。增量在O(1) 操作中完成。现在考虑您给出的示例,您将检查每个2^n 数字的ith 灯泡是on 还是off,它需要一个线性循环来检查每个灯泡的状态。你需要一个线性循环,它会导致每个数字的开销为O(n)。这使得这种复杂性O(n * 2^n).
【解决方案2】:
  int Fibonacci(int number)
 {
  if (number <= 1) return number;

  return Fibonacci(number - 2) + Fibonacci(number - 1);
 }

每增加一个输入数据集,增长就会翻倍。 O(2N) 函数的增长曲线是指数型的 - 从非常浅的开始,然后迅速上升。 我的大 O(2^n) 示例,但更好的是:

public void solve(int n, String start, String auxiliary, String end) {
   if (n == 1) {
       System.out.println(start + " -> " + end);
   } else {
       solve(n - 1, start, end, auxiliary);
       System.out.println(start + " -> " + end);
       solve(n - 1, auxiliary, start, end);
   }

在此方法中,程序会打印解决“河内塔”问题的所有动作。 这两个示例都使用递归来解决问题,并且运行时间为 O(2^n)。

【讨论】:

  • 您应该解释为什么它具有指数复杂性 - 这并不明显。此外,这是一个不好的例子,因为您可以轻松地“修复”该算法以使其具有线性复杂性 - 就好像您想故意浪费处理能力一样。一个更好的例子是显示一个算法,该算法计算一些很难/不可能快速完成的事情。
【解决方案3】:

运行时间为 O(2^N) 的算法通常是递归算法,通过递归解决两个大小为 N-1 的较小问题来解决大小为 N 的问题。

例如,该程序以伪代码打印出解决 N 个磁盘的著名“河内塔”问题所需的所有移动

void solve_hanoi(int N, string from_peg, string to_peg, string spare_peg)
{
    if (N<1) {
        return;
    }
    if (N>1) {
        solve_hanoi(N-1, from_peg, spare_peg, to_peg);
    }
    print "move from " + from_peg + " to " + to_peg;
    if (N>1) {
        solve_hanoi(N-1, spare_peg, to_peg, from_peg);
    }
}

令 T(N) 为 N 个磁盘所需的时间。

我们有:

T(1) = O(1)
and
T(N) = O(1) + 2*T(N-1) when N>1

如果你反复展开最后一个词,你会得到:

T(N) = 3*O(1) + 4*T(N-2)
T(N) = 7*O(1) + 8*T(N-3)
...
T(N) = (2^(N-1)-1)*O(1) + (2^(N-1))*T(1)
T(N) = (2^N - 1)*O(1)
T(N) = O(2^N)

要真正弄清楚这一点,您只需要知道递归关系中的某些模式会导致指数结果。通常T(N) = ... + C*T(N-1)C &gt; 1意味着 O(x^N)。见:

https://en.wikipedia.org/wiki/Recurrence_relation

【讨论】:

  • 计算第 N 个斐波那契数的朴素递归函数是另一个经典示例。
  • 我仍然不会查看该代码并能够推导出 2^n,但这确实有很大帮助。
  • 我添加了一个可能有帮助的解释
  • @EsotericScreenName O(2^n) 不是天真地计算第 n 个斐波那契数的时间复杂度的严格限制。它是 O(phi^n),其中 phi 是黄金比例。所以我认为这不是一个很好的问题答案,它隐含地要求使用 Theta(2^n) 的算法。
  • O(2^n) 通常可以用 DP 变成 O(n)
【解决方案4】:

c^N = 来自c 大小的字母表中n 元素的所有组合。

更具体地说,2^N 是所有可以用 N 位表示的数字。

常见的情况是递归实现的,比如:

vector<int> bits;
int N
void find_solution(int pos) {
   if (pos == N) {
     check_solution();
     return;
   }
   bits[pos] = 0;
   find_solution(pos + 1);
   bits[pos] = 1;
   find_solution(pos + 1);
}

【讨论】:

    【解决方案5】:

    这是一个代码片段,它计算货物数组中每个值组合的值总和(value 是一个全局数组变量):

    fun boom(idx: Int, pre: Int, include: Boolean) {
        if (idx < 0) return
        boom(idx - 1, pre + if (include) values[idx] else 0, true)
        boom(idx - 1, pre + if (include) values[idx] else 0, false)
        println(pre + if (include) values[idx] else 0)
    }
    

    如您所见,它是递归的。我们可以插入循环来获得Polynomial的复杂度,并使用递归来获得Exponential的复杂度。

    【讨论】:

      【解决方案6】:

      这里有两个在 Python 中使用 Big O/Landau (2^N) 的简单示例:

      #fibonacci 
      def fib(num):    
          if num==0 or num==1:
              return num
          else:
              return fib(num-1)+fib(num-2)
      
      num=10
      for i in range(0,num):
          print(fib(i))
      
      
      #tower of Hanoi
      def move(disk , from, to, aux):
          if disk >= 1:
              # from twoer , auxilart 
              move(disk-1, from, aux, to)
              print ("Move disk", disk, "from rod", from_rod, "to rod", to_rod)
              move(disk-1, aux, to, from)
      
      n = 3
      move(n, 'A', 'B', 'C')
      

      【讨论】:

        【解决方案7】:

        假设您想猜测智能手机的 PIN,此 PIN 是一个 4 位整数。您知道保存 4 位数字的最大位数是 14 位。所以,你必须从 2^14 = 16384 个可能的值中猜测这个 PIN 的值,比如 14 位正确组合!!

        唯一的方法是蛮力。所以,为了简单起见,考虑这个你想猜对的简单的 2 位字,每个位都有 2 个可能的值,0 或 1。所以,所有的可能性是:

        00
        01
        10
        11
        

        我们从逻辑电路设计中知道,一个 n 位字的所有可能性都是 2^n 种可能的组合。所以,2^2 是我们之前看到的 4 种可能的组合。

        这同样适用于 14 位整数 PIN,因此猜测 PIN 需要您解决 2^14 个可能的结果难题,因此算法的时间复杂度为 O(2^n)。

        因此,那些类型的问题,其中集合 S 中的元素组合不同,并且您必须尝试通过尝试所有可能的组合来尝试解决问题,将具有 O(2^n) 时间复杂度。但是,取幂的底数不一定是 2。在上面的例子中,它的底数是 2,因为每个元素,每个位,都有两个可能的值,这在其他问题中不会出现。

        O(2^n) 算法的另一个很好的例子是递归背包。您必须尝试不同的组合以最大化值,其中集合中的每个元素都有两个可能的值,无论我们是否接受。

        Edit Distance 问题的时间复杂度为 O(3^n),因为对于 n 个字符串中的每一个,您有 3 个决策可供选择:删除、插入或替换。

        【讨论】:

          【解决方案8】:

          假设一个集合是它自己的一个子集,那么一个包含 n 个元素的集合有 2ⁿ 个可能的子集。

          这样想。为了制作一个子集,让我们取一个元素。此元素在您正在创建的子集中有两种可能性:存在或不存在。这同样适用于集合中的所有其他元素。将所有这些可能性相乘,您将得到 2ⁿ。

          【讨论】:

          • 这里有一个细微差别,列出所有子集需要超过 2^n 时间,因为写出这些子集的成本很高。实际时间限制为 Theta(n * 2^n)。
          猜你喜欢
          • 2013-06-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-04-26
          • 1970-01-01
          • 2012-11-22
          • 2020-12-29
          • 2022-11-10
          相关资源
          最近更新 更多