需要帮助证明递归函数的时间复杂性。 据说它是2 ^ n。我需要证明情况就是这样。
def F(n):
if n == 0:
return 0
else:
result = 0
for i in range(n):
result += F(i)
return n*result+n`
这是另一个做同样事情的版本。赋值表示使用数组存储值以试图减少时间复杂度,所以我所做的就是这个
def F2(n,array):
if n < len(array):
answer = array[n]
elif n == 0:
answer = 0
array.append(answer)
else:
result = 0
for i in range(n):
result += F2(i,array)
answer = n*result+n
array.append(answer)
return answer
我正在寻找的是解释如何找到两段代码的复杂性,而不是只知道答案。 所有和任何帮助非常感谢。
PS:由于某种原因,我无法让“def F2”留在代码块中...对不起答案 0 :(得分:4)
好吧,你写的第一个函数是Exhaustive Search
的一个例子,你正在探索每个可能的分支,这些分支可以由一组整数组成,直到n
(你已经在参数,你正在使用for
循环)。为了向您解释时间复杂度,我将把递归堆栈视为树(表示递归函数调用堆栈,您可以使用堆栈或使用n-ary树)
让我们先给你打电话F1
:
F1(3),现在将为集合S中的每个数字形成三个分支(set是整数到n)。我已经采取了n = 3,因为我很容易为它制作图表。您可以尝试其他更大的数字并观察递归调用堆栈。
3
/| \
0 1 2 ----> the leftmost node is returns 0 coz (n==0) it's the base case
| /\
0 0 1
|
0 ----> returns 0
所以在这里你已经探索了每一种可能性分支。如果你试图为上述问题编写递归方程,那么:
T(n) = 1; n is 0
= T(n-1) + T(n-2) + T(n-3) + ... + T(1); otherwise
在这里,
T(n-1) = T(n-2) + T(n-3) + ... T(1).
所以,T(n-1) + T(n-2) + T(n-3) + ... + T(1) = T(n-1) + T(n-1)
因此,递归方程变为:
T(n) = 1; n is 0
= 2*T(n-1); otherwise
现在您可以轻松解决此递归关系(或者使用Masters定理可以快速解决)。您将时间复杂度定为O(2^n)
。
解决递归关系:
T(n) = 2T(n-1)
= 2(2T(n-1-1) = 4T(n-2)
= 4(2T(n-3)
= 8T(n-3)
= 2^k T(n-k), for some integer `k` ----> equation 1
现在我们看到n
为0
的基本情况,所以让我们,
n-k = 0 , i.e. k = n;
将k = n
放入equation 1
,
T(n) = 2^n * T(n-n)
= 2^n * T(0)
= 2^n * 1; // as T(0) is 1
= 2^n
所以, T.C = O(2 ^ n)
这就是你如何为第一个功能获得时间复杂度的方法。接下来,如果您观察到上面形成的递归树(树中的每个节点都是主要问题的子问题),您将看到节点正在重复(即子问题正在重复)。因此,您在第二个函数F2
中使用了一个内存来存储已经计算的值,并且每当子问题再次发生时(即重复子问题),您使用的是预先计算的值(这样可以节省计算时间次要问题一次又一次)。该方法也称为动态编程。
现在让我们看看第二个函数,在这里你要返回answer
。但是,如果你看到你的函数,你正在程序中构建一个名为array
的数组。主要的时间复杂性就在那里。计算它的时间复杂度很简单,因为总是只涉及一个递归级别(或随便说你不能说递归),因为数字i
范围内的每个数字n
总是会出现小于n
,因此第一个if
条件被执行,控件从F2
返回。因此,每次调用都不能超过调用堆栈中的2级。
所以,
Time complexity of second function = time taken to build the array;
= 1 comparisions + 1 comparisions + 2 comparisions + ... + (n-1) comparisions
= 1 + 2 + 3 + ... + n-1
= O(n^2).
让我给你一个简单的方法来更深入地观察这种递归。您可以在控制台上打印递归堆栈,并观察函数调用的方式。下面我写了你打印函数调用的代码。
代码:
def indent(n):
for i in xrange(n):
print ' '*i,
# second argument rec_cnt is just taken to print the indented function properly
def F(n, rec_cnt):
indent(rec_cnt)
print 'F(' + str(n) + ')'
if n == 0:
return 0
else:
result = 0
for i in range(n):
result += F(i, rec_cnt+1)
return n*result+n
# third argument is just taken to print the indented function properly
def F2(n, array, rec_cnt):
indent(rec_cnt)
print 'F2(' + str(n) + ')'
if n < len(array):
answer = array[n]
elif n == 0:
answer = 0
array.append(answer)
else:
result = 0
for i in range(n):
result += F2(i, array, rec_cnt+1)
answer = n*result+n
array.append(answer)
return answer
print F(4, 1)
lis = []
print F2(4, lis, 1)
现在观察输出:
F(4)
F(0)
F(1)
F(0)
F(2)
F(0)
F(1)
F(0)
F(3)
F(0)
F(1)
F(0)
F(2)
F(0)
F(1)
F(0)
96
F2(4)
F2(0)
F2(1)
F2(0)
F2(2)
F2(0)
F2(1)
F2(3)
F2(0)
F2(1)
F2(2)
96
在第一个函数调用堆栈即F1
中,您会看到每个调用都被探索到0
,即我们正在探索每个可能的分支到0
(基本情况)所以,我们称之为穷举搜索。
在第二个函数调用堆栈中,您可以看到函数调用仅获得两个级别,即它们使用预先计算的值来解决重复的子问题。因此,它的时间复杂度小于F1
。