比方说,斐波那契系列的迭代和递归版本。它们具有相同的时间复杂度吗?
答案 0 :(得分:20)
答案很大程度上取决于您的实施。对于您给出的示例,有几种可能的解决方案,我想说实现解决方案的天真方式在迭代实现时具有更好的复杂性。以下是两种实现:
int iterative_fib(int n) {
if (n <= 2) {
return 1;
}
int a = 1, b = 1, c;
for (int i = 0; i < n - 2; ++i) {
c = a + b;
b = a;
a = c;
}
return a;
}
int recursive_fib(int n) {
if (n <= 2) {
return 1;
}
return recursive_fib(n - 1) + recursive_fib(n-2);
}
在两种实现中,我假设输入正确,即n> = 1.第一个代码要长得多,但其复杂度为O(n)即线性,而第二个实现较短但具有指数复杂度O(fib(n) ))= O(φ^ n)(φ = (1+√5)/2
)因此慢得多。
可以通过引入memoization来改进递归版本(即记住你已经计算过的函数的返回值)。这通常通过引入存储值的数组来完成。这是一个例子:
int mem[1000]; // initialize this array with some invalid value. Usually 0 or -1
// as memset can be used for that: memset(mem, -1, sizeof(mem));
int mem_fib(int n) {
if (n <= 2) {
return mem[n] = 1;
}
if (mem[n-1] == -1) {
solve(n-1);
}
if (mem[n-2] == -1) {
solve(n-2);
}
return mem[n] = mem[n-1] + mem[n-2];
}
这里递归算法的复杂性就像迭代解决方案一样是线性的。我上面介绍的解决方案是自上而下的方法,用于解决问题的动态编程。自下而上的方法将导致与我作为迭代引入的解决方案非常相似的东西。 关于动态编程的文章很多,包括wikipedia
根据我在经验中遇到的问题,有些问题更难用自下而上的方法解决(即迭代解决方案),而其他方法则难以用自上而下的方法解决。 然而,该理论指出,具有迭代解的每个问题都具有相同计算复杂度的递归(反之亦然)。
希望这个答案有所帮助。
答案 1 :(得分:5)
计算fibanocci系列的特定递归算法效率较低。 考虑以下通过递归算法找到fib(4)的情况
int fib(n) :
if( n==0 || n==1 )
return n;
else
return fib(n-1) + fib(n-2)
现在,当上述算法执行n = 4
时 fib(4)
fib(3) fib(2)
fib(2) fib(1) fib(1) fib(0)
fib(1) fib(0)
这是一棵树。它说,为了计算fib(4),你需要计算fib(3)和fib(2)等等。
请注意,即使是4的小值,也计算两次fib(2)并计算三次fib(1)。这个数量的增加量增加了很多。
有一种猜想认为计算fib(n)所需的加法数是
fib(n+1) -1
因此,这种重复是导致该特定算法性能下降的原因。
斐波纳契数列的迭代算法速度要快得多,因为它不涉及计算冗余事物。
虽然所有算法的情况可能并非相同。
答案 2 :(得分:2)
如果你采用一些递归算法,你可以通过将所有函数局部变量存储在一个数组中来将其转换为迭代,从而有效地模拟堆上的堆栈。如果像这样完成迭代和递归之间没有区别。
请注意,(至少)有两个递归的Fibonacci算法,因此为了准确地说明您需要指定您正在讨论的递归算法。
答案 3 :(得分:1)
是的,每个迭代算法都可以转换为递归版本,反之亦然。通过实现堆栈结构传递continuation和另一种方法。这样做不会增加时间复杂度。
如果你可以优化尾递归,那么每个迭代算法都可以转换为递归算法,而不会增加渐近内存的复杂性。
答案 4 :(得分:0)
是的,如果您使用与算法完全相同的想法,则无关紧要。但是,递归通常易于使用迭代。例如,编写河内塔的递归版本非常容易。将递归版本转换为相应的迭代版本很困难且容易出错,即使可以完成。实际上,有定理表明每个递归算法都可以转换为等效的迭代算法(这样做需要使用一个或多个堆栈数据结构迭代地模拟递归以保存传递给递归调用的参数)。