【问题标题】:Time complexity of a recursive algorithm递归算法的时间复杂度
【发布时间】:2011-02-12 03:20:48
【问题描述】:

如何计算递归算法的时间复杂度?

int pow1(int x,int n) {
    if(n==0){
        return 1;
    }
    else{
        return x * pow1(x, n-1);
    }
}

int pow2(int x,int n) {
    if(n==0){
        return 1;
    }
    else if(n&1){
        int p = pow2(x, (n-1)/2)
        return x * p * p;
    }
    else {
        int p = pow2(x, n/2)
        return p * p;
    }
}

【问题讨论】:

    标签: c algorithm time-complexity


    【解决方案1】:

    分析递归函数(甚至评估它们)是一项不平凡的任务。 (在我看来)很好的介绍可以在 Don Knuths Concrete Mathematics 中找到。

    不过,现在让我们分析一下这些例子:

    我们定义了一个函数,它为我们提供了函数所需的时间。假设t(n)表示pow(x,n)所需的时间,即n的函数。

    那么我们可以得出结论,t(0)=c,因为如果我们调用pow(x,0),我们必须检查是否(n==0),然后返回1,这可以在常数时间内完成(因此常数@987654329 @)。

    现在我们考虑另一种情况:n>0。这里我们获得t(n) = d + t(n-1)。那是因为我们必须再次检查n==1,计算pow(x, n-1,因此是(t(n-1)),然后将结果乘以x。校验和乘法可以在常数时间内完成(常数d),递归计算pow需要t(n-1)

    现在我们可以“扩展”术语t(n)

    t(n) =
    d + t(n-1) = 
    d + (d + t(n-2)) = 
    d + d + t(n-2) = 
    d + d + d + t(n-3) =
    ... =
    d + d + d + ... + t(1) =
    d + d + d + ... + c
    

    那么,我们需要多长时间才能到达t(1)?因为我们从t(n)开始,每一步减1,所以需要n-1步才能达到t(n-(n-1)) = t(1)。另一方面,这意味着我们得到n-1 乘以常数d,而t(1) 被评估为c

    所以我们得到:

    t(n) =
    ...
    d + d + d + ... + c =
    (n-1) * d + c
    

    所以我们得到t(n)=(n-1) * d + c,它是 O(n) 的元素。

    pow2 可以使用Masters theorem 完成。因为我们可以假设算法的时间函数是单调递增的。所以现在我们有了计算pow2(x,n)所需的时间t(n)

    t(0) = c (since constant time needed for computation of pow(x,0))
    

    对于n>0,我们得到

            / t((n-1)/2) + d if n is odd  (d is constant cost)
    t(n) = <
            \ t(n/2) + d     if n is even (d is constant cost)
    

    以上可以“简化”为:

    t(n) = floor(t(n/2)) + d <= t(n/2) + d (since t is monotonically increasing)
    

    所以我们得到t(n) &lt;= t(n/2) + d,可以使用t(n) = O(log n)的masters theorem来解决(参见维基百科链接中流行算法的应用部分,例如“二进制搜索”)。

    【讨论】:

      【解决方案2】:

      忽略递归的两个函数的复杂度是 O(1)

      对于第一个算法,pow1(x, n) 复杂度为 O(n),因为递归深度与 n 呈线性相关。

      第二个复杂度是 O(log n)。这里我们递归大约 log2(n) 次。抛出 2 我们得到 log n。

      【讨论】:

      • 两个函数的复杂度都是 O(1) — 什么?
      • 忽略递归调用是 O(1),但可以用不同的方式表达。关键是总复杂度仅取决于递归深度。
      【解决方案3】:

      让我们从 pow1 开始,因为这是最简单的。

      你有一个在 O(1) 中完成单次运行的函数。 (条件检查、返回和乘法都是常数时间。)

      你剩下的就是你的递归。您需要做的是分析函数最终调用自身的频率。在 pow1 中,它会发生 N 次。 N*O(1)=O(N)。

      对于 pow2,原理相同 - 函数的单次运行在 O(1) 中运行。但是,这一次你每次都将 N 减半。这意味着它将运行 log2(N) 次 - 每比特有效一次。 log2(N)*O(1)=O(log(N))。

      可能对您有所帮助的是利用递归始终可以表示为迭代这一事实(并不总是非常简单,但它是可能的。我们可以将 pow1 表示为

      result = 1;
      while(n != 0)
      {
        result = result*n;
        n = n - 1;
      }
      

      现在您有了一个迭代算法,您可能会发现以这种方式分析它更容易。

      【讨论】:

        【解决方案4】:

        所以我猜你正在将 x 提高到 n 次方。 pow1 需要 O(n)。

        你永远不会改变 x 的值,但你每次从 n 中取 1 直到它变为 1(然后你就返回)这意味着你将进行 n 次递归调用。

        【讨论】:

          【解决方案5】:

          这可能有点复杂,但我认为通常的方法是使用Master's theorem

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-07-15
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多