我对这两个代码感到困惑。
代码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),空间复杂度为O(n)
代码2是O(N)
我不知道这两个代码之间有什么不同。看起来两者都是相同的二叉树
答案 0 :(得分:5)
首先,了解两种情况下的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 通常表示输入的大小,而输入取决于您的问题。它可以只是一个数字(比如你的第一个问题),一维数组,二叉树(比如你的第二个问题),甚至是一个矩阵(尽管在后一种情况下你可能会看到像&这样的显式语句#34;大小为M * N&#34;的矩阵。
所以你的困惑可能来自N&#34;的定义。这两个问题有所不同。换句话说,我可能会说n2 = 2^n1
。
答案 1 :(得分:4)
那是错误的,因为第一个片段在O(2 ^ n)中运行而不是O(n ^ 2)。
解释是:
在每个步骤中,我们递减n
但是创建两次调用次数,因此对于n,我们用f(n-1)调用两次,并且对于n-1的每一次调用,我们将调用n = 1。用f(n-2)呼叫两次 - 这是4次呼叫,如果我们再次降低级别,我们用f(n-3)呼叫8次:所以呼叫次数为: 2 ^ 1,然后是2 ^ 2,然后是2 ^ 3,2 ^ 4,...,2 ^ n。
第二个片段在二叉树上进行一次传递,并且恰好到达每个节点一次,所以它是O(n)。
答案 2 :(得分:1)
第一个代码确实是O(2^n)
。
但第二个代码不能是O(n)
,因为那里没有n
。这是许多人忘记的事情,通常他们假设 n
是什么而不澄清它。
事实上,您可以根据任何事情估算任何事物的增长速度。有时它的输入大小(在第一个代码中是O(1)
或O(log n)
取决于大数字的用法),有时只是在参数上,如果它是数字的。
因此,当我们开始考虑第二个代码中的时间和内存依赖时,我们可以得到这些东西:
time=O(number_of_nodes_in_tree)
time=O(2^height_of_tree)
additional_space=O(height_of_tree)
additional_space=O(log(number_of_nodes))
(如果树是平衡的)所有这些都是同时正确的 - 它们只是将某些东西与不同的东西联系起来。
答案 3 :(得分:0)
代码1:
if()
语句根据传入参数的内容运行n
次,但函数调用自身n-1
次。简化:
n * (n-1) = n^2 - n = O(n^2 - n) = O(n^2)
代码2:
搜索只遍历树的每个元素一次,函数本身没有任何for()
。由于有n
个项目,并且只访问过一次,因此它是O(n)
。
答案 4 :(得分:0)
对于代码2,要确定函数的大O,我们是否必须考虑重复的成本以及重复运行的次数?
如果我们使用两种方法来估计使用递归树和主定理的Big O:
递归树: 每个级别的总成本对于每个级别将是cn,因为递归调用的数量和输入的分数是相等的,并且树的级别是lg(n),因为它是平衡的二叉搜索树。那么运行时间应该是nlg(n)?
主定理: 这应该是情况2,因为f(n)= n ^ logbase a(b)。那么根据主定理,它应该是nlg(n)运行时间?
答案 5 :(得分:0)
您对这两种情况的“ N”感到困惑。在第一种情况下,N表示给定的输入。因此,例如,如果N = 4,则调用的函数数为2 ^ 4 = 16。您可以绘制递归图进行说明。因此,O(2 ^ N)。
在第二种情况下,N表示二叉树中的节点数。因此,此N与输入无关,但与二叉树中已经存在的节点数无关。因此,当用户调用该函数时,它会访问每个节点一次。因此,O(N)。