【问题标题】:Time Complexity of recursive of function函数递归的时间复杂度
【发布时间】:2018-08-18 15:49:28
【问题描述】:

需要帮助证明递归函数的时间复杂度。 据说是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

我再次寻找的是关于如何找到两个 sn-ps 代码的复杂性的解释,而不是仅仅知道答案。 非常感谢所有和任何帮助。

PS:由于某种原因,我无法让“def F2”留在代码块中......对此感到抱歉

【问题讨论】:

  • 如果您简要说明您的功能应该做什么,这将很有帮助。特别是当他们的名字像FF2...
  • 你可以通过induction证明2^n的事情。
  • 这实际上是一道比较复杂的数学题,如果你没有得到答案可能是因为它超出了本站更多的技术范围(例如如何实现算法,为什么要实现不工作,如何用语言 y 做 x 等)。如果你没有得到答案,那就是为什么。我建议查看您的课程为您确定递归方法的大 O 的方法。例如,您知道T(n)=T(n-1)+T(n-2)+...+T(1)+T(0)+O(1) 从那里开始。

标签: python algorithm recursion time-complexity dynamic-programming


【解决方案1】:

好的,您编写的第一个函数是 Exhaustive Search 的示例,您正在探索可以从一组整数到 n 形成的每个可能的分支(您已在参数中传递,并且您是使用for 循环)。为了向您解释时间复杂度,我将递归堆栈视为一棵树(为了表示递归函数调用堆栈,您可以使用堆栈或使用 n-ary Tree)

我们称你为第一个函数F1

F1(3),现在集合 S 中的每个数都会形成三个分支(集合是直到 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 theorem 来快速解决)。您将得到时间复杂度为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

现在我们得到了n0 的基本情况,所以让我们,

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 的数组。主要的时间复杂度在那里。计算其时间复杂度很简单,因为总是只涉及一级递归(或者随便你可以说不涉及递归),因为在数字 n 范围内的每个数字 i 总是小于数字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

【讨论】:

  • 非常感谢您花时间解释
  • 你写的是 T(n-1) + T(n-2) + T(n-3) + ... + T(1) 而不是 " n(T(n-1) ) + T(n-2) + T(n-3) + ... + T(1))+n" (不考虑 n 的乘法和 n 的加法),因为它们是常数,可以忽略当谈到渐近复杂性时?
  • 另外,当应用主定理时,我看不到任何会产生 O(2^n) 的情况,你能告诉我你用来确定 T(n) = 1 的步骤吗? n 为 0 = 2*T(n-1);复杂度 O(2^n)
  • n*result+n是一个常数时间运算,算法中的主要时间是由于递归树的形成。当你说T(n) = n( T(n-1)+T(n-2)+....+T(0))+n 时你错了,因为当n 作为输入传递给算法时,T(n) 表示函数的时间复杂度。你写的表达式没有意义,为什么要把时间和n相乘?
  • 是的,我很抱歉。现在你这样说没有意义。我刚开始算法分析,我仍然觉得它很混乱,尤其是在使用递归时,我的错
猜你喜欢
  • 2018-05-29
  • 1970-01-01
  • 2021-11-11
  • 2023-03-14
相关资源
最近更新 更多