递归算法的基本情况和时间复杂度

时间:2017-07-25 17:25:49

标签: algorithm performance recursion big-o sicp

我想对O(N)函数做一些澄清。我正在使用SICP

考虑书中的阶乘函数,它以伪代码生成递归过程:

function factorial1(n) {
    if (n == 1) {
        return 1;
    }
    return n*factorial1(n-1);
}

我不知道如何衡量步数。也就是说,我不知道" step"是定义的,所以我使用书中的陈述来定义一个步骤:

  

因此,我们可以计算n!通过计算(n-1)!并乘以   结果由n。

我认为这就是他们所说的一步。举一个具体的例子,如果我们追踪(阶乘5),

  • factorial(1)= 1 = 1步(基本情况 - 恒定时间)
  • 阶乘(2)= 2 *阶乘(1)= 2阶段
  • 阶乘(3)= 3 *阶梯(2)= 3阶段
  • 阶乘(4)= 4 *阶梯(3)= 4阶段
  • 阶乘(5)= 5 *阶乘(4)= 5阶段

我认为这确实是线性的(步数与n成正比)。

另一方面,这是我一直看到的另一个因子函数,它具有稍微不同的基本情况。

function factorial2(n) {
    if (n == 0) {
        return 1;
    }
    return n*factorial2(n-1);
}

除了添加另一个计算(步骤)之外,这与第一个完全相同:

  • 阶乘(0)= 1 = 1步(基本情况 - 恒定时间)
  • 阶乘(1)= 1 *阶乘(0)= 2阶段
  • ...

现在我相信这仍然是O(N),但如果我说factorial2更像是O(n + 1)(其中1是基本情况)而不是factorial1正好是O(N)我是否正确(包括基本情况)?

3 个答案:

答案 0 :(得分:2)

需要注意的一点是,factorial1 n = 0不正确,可能会发生下溢并最终导致典型实现中的堆栈溢出。 factorial2的{​​{1}}正确无误。

除此之外,你的直觉是正确的。 n = 0是O(n),factorial1是O(n + 1)。但是,由于factorial2的影响优于常数因子(n),因此通常会将其简化为O(n)来简化它。关于Big O Notation的维基百科文章描述了这一点:

  

...出现在O(...)中的函数g(x)通常选择尽可能简单,省略常数因子和低阶项。

从另一个角度来看,更准确地说这些函数在pseudo-polynomial time中执行。这意味着它相对于+ 1的数值是多项式的,但是相对于表示n的值所需的位数是指数的。有一个很好的在先答案描述了这种区别。

What is pseudopolynomial time? How does it differ from polynomial time?

答案 1 :(得分:2)

您的伪代码对其执行的具体细节仍然非常模糊。更明确的可能是

function factorial1(n) {
    r1 = (n == 1);        // one step
    if r1: { return 1; }  // second step ... will stop only if n==1
    r2 = factorial1(n-1)  // third step ... in addition to however much steps
                          //                it takes to compute the factorial1(n-1)
    r3 = n * r2;          // fourth step
    return r3;
}

因此,我们发现计算factorial1(n)比计算factorial1(n-1)多四步,计算factorial1(1)需要两个步骤:

T(1) = 2
T(n) = 4 + T(n-1)

这大致转换为 4n 操作,其中 O(n)。一步或多或少,或任何常数步骤(即独立于 n ),不会改变任何东西。

答案 2 :(得分:1)

我认为,说这不是你说的不对。

如果某些内容为O(N),则定义为O(N+1)以及O(2n+3)以及O(6N + -e)O(.67777N - e^67)。我们使用最简单的表格来表示O(N),但是我们必须意识到,第一个函数也是O(N+1),第二个函数同样是O(n) as it was。{{1} }为O(n + 1)`。

我证明了这一点。如果你花一些时间来定义big-O,那么证明这一点并不难。     g(n)= O(f(n)),f(n)= O(k(n)) - 示例 - > g(n)= O(k(n))

(不相信我?只是google传递属性的大O符号)。从上面可以很容易地看出以下含义。

n = O(n+1), factorial1 = O(n) --implies--> factorial1 = O(n+1)

所以说一个函数是O(N)或O(N + 1)之间没有区别。你刚才两次说了同样的话。它是等距,同等,等同。选择你喜欢的词。它们是同一个东西的不同名称。

如果你看一下Θ函数,你可以把它们想象成一堆充满函数的数学集,其中该集合中的所有函数都具有相同的增长率。一些常见的集合是:

Θ(1)        # Constant 
Θ(log(n))   # Logarithmic
Θ(n)        # Linear
Θ(n^2)      # Qudratic
Θ(n^3)      # Cubic
Θ(2^n)      # Exponential (Base 2)
Θ(n!)       # Factorial

一个函数将落入一个且恰好一个Θ集。如果一个函数分为2组,那么根据定义,两组中的所有函数都可以证明属于两组,而你实际上只有一组。在一天结束时,Θ为我们提供了所有可能功能的完美分割,成为可数无限的唯一集合。

大O集中的函数意味着它存在于某个Θ集合中,其增长率不大于big-O函数。

这就是为什么我会说你错了,或者至少被误导说它是“更多O(N+1)”。 O(N)实际上只是一种表示“增长率等于或小于线性增长的所有函数的集合”的方法。所以说:

a function is more O(N+1) and less `O(N)`

等同于说

a function is more "a member of the set of all functions that have linear
growth rate or less growth rate" and less "a member of the set of all 
functions that have linear or less growth rate"

这是非常荒谬的,并不是一个正确的说法。