理解大 O 符号 - 破解编码面试示例 9

Understanding Big O notation - Cracking the coding interview example 9

我被这两个代码卡住了。

代码 1

int f(int n){
    if (n <= 1){
         return 1;
    }
    return f(n-1) + f(n-1);
}

代码2(平衡二叉查找树)

int sum(Node node){
    if(node == null){
        return 0;
    }
    return sum(node.left) + node.value + sum(node.right);
}

作者说代码1的运行时间是O(2^n),space复杂度是O(n)

而代码 2 是 O(N)

我不知道这两个代码有什么不同。看起来两者都是相同的二叉树

代码 1:

根据传入参数的内容,if() 语句运行 n 次,但函数调用自身 n-1 次。简化:

n * (n-1) = n^2 - n = O(n^2 - n) = O(n^2)

代码 2:

搜索只遍历树的每个元素一次,函数本身没有任何for()。由于有 n 项并且只访问了一次,因此是 O(n).

嗯,这是一个错误,因为第一个片段的运行时间复杂度为 O(2^n) 而不是 O(n^2)。

解释是: 在每一步中,我们减少 n 但创建两倍的调用次数,因此对于 n 我们将使用 f(n-1) 调用两次,对于 n-1 的每个调用,我们将调用两次f(n-2) - 这是 4 次调用,如果我们再往下一层,我们将使用 f(n-3) 调用 8 次:所以调用次数是:2^1,然后是 2^2 , 然后 2^3, 2^4, ..., 2^n.

第二个代码片段在二叉树上执行一次传递并恰好到达每个节点一次,所以它是 O(n)。

第一个密码确实是O(2^n).

但是第二个代码不能是O(n),因为那里没有n。这是许多人忘记的事情,通常他们 假设 什么是 n 而没有澄清它。

事实上,你可以根据任何事物来估计任何事物的增长速度。有时它是输入的大小(在第一个代码中是 O(1)O(log n) 取决于大数字的使用),有时只是在参数上,如果它是数字。

所以当我们开始思考第二个代码中时间和内存依赖什么时,我们可以得到这些东西:

  1. time=O(number_of_nodes_in_tree)
  2. time=O(2^height_of_tree)
  3. additional_space=O(height_of_tree)
  4. additional_space=O(log(number_of_nodes))(如果树是平衡的)

所有这些同时都是正确的 - 它们只是将某些事物与不同事物联系起来。

首先,了解两种情况下的 N 是什么很重要。 在第一个示例中,它非常明显,因为您可以直接在代码中看到它。对于第一种情况,如果构建 f(i) 调用树,您将看到它包含 O(2^N) 元素。的确,

            f(N)             // 1 node
        /          \ 
   f(N-1)         f(N-1)     // 2 nodes
  /      \      /      \
f(N-2) f(N-2)  f(N-2) f(N-2) // 2^2 nodes
...
f(1) ........ f(1)           // 2^(N-1) nodes

在第二种情况下,N(很可能)是树中的一些元素。正如您从代码中看到的那样,我们只遍历每个元素一次 - 您可能会意识到 node.value 为每个树节点调用一次。因此 O(N).

请注意,在此类任务中,N 通常表示 输入的大小 ,而 input 取决于你的问题。它可以只是一个数字(就像你的第一个问题)、一维数组、二叉树(就像你的第二个问题),甚至是一个矩阵(尽管在后一种情况下你可能希望看到像 "a matrix with a size M*N").

所以您的困惑可能来自于 "definition of N" 这两个问题之间的不同这一事实。换句话说,我可能会说 n2 = 2^n1.

对于代码2,判断一个函数的大O,不是要考虑递归的代价和递归的次数吗运行?

如果我们使用两种方法使用递归树和主定理来估计大 O:

递归树: 每个级别的总成本将为每个级别的 cn,因为递归调用的次数和输入的分数相等,树的级别为 lg(n),因为它是一个平衡的二叉搜索树。所以 运行 时间应该是 nlg(n)?

主定理: 这应该是情况 2,因为 f(n) = n^logbase a (b)。那么根据高手定理,应该是nlg(n)运行ning次?

你对这两种情况的“N”感到困惑。在第一种情况下,N 指的是给定的输入。例如,如果 N=4,则调用的函数数为 2^4=16。你可以画出递归图来说明。因此 O(2^N)。

在第二种情况下,N是指二叉树中的节点数。所以这个 N 与输入无关,而是与二叉树中已经存在的节点数量有关。因此,当用户调用该函数时,它只访问每个节点一次。因此 O(N)。

我们可以认为是O(2^Depth)

第一个例子中:深度为N,正好是书上提到的问题的输入

在第二个例子中:它是一棵平衡的二叉搜索树,因此,它有Log(N)层(深度)。注:N是树中的元素个数。

=> 让我们应用 O(2^Depth).. O(2^(Log(N)) = O(N) 留下 O(N) 复杂性。

提醒:

  • 在计算机科学中,我们通常将 Log2(n) 称为 Log(n)
  • 以 b 为底的 x 的对数是你对 b 求得 x 结果的指数。

在上述复杂性中:O(2^(Log(N),我们将基数 2 提高到 Log2(N),这给了我们 N。(检查两个提醒)

这个link可能会有用。