根据我的教授,循环比使用递归更快更缺陷但我想出了这个使用递归和循环计算Fibonacci系列的c ++代码,结果证明它们非常相似。因此,我对可能的输入进行了最大化,以查看性能是否存在差异,并且出于某种原因,递归比使用循环更好。谁知道为什么?谢谢你提前。
以下是代码:
#include "stdafx.h"
#include "iostream"
#include <time.h>
using namespace std;
double F[200000000];
//double F[5];
/*int Fib(int num)
{
if (num == 0)
{
return 0;
}
if (num == 1)
{
return 1;
}
return Fib(num - 1) + Fib(num - 2);
}*/
double FiboNR(int n) // array of size n
{
for (int i = 2; i <= n; i++)
{
F[i] = F[i - 1] + F[i - 2];
}
return (F[n]);
}
double FibMod(int i,int n) // array of size n
{
if (i==n)
{
return F[i];
}
F[i] = F[i - 1] + F[i - 2];
return (F[n]);
}
int _tmain(int argc, _TCHAR* argv[])
{
/*cout << "----------------Recursion--------------"<<endl;
for (int i = 0; i < 36; i=i+5)
{
clock_t tStart = clock();
cout << Fib(i);
printf("Time taken: %.2fs\n", (double)(clock() - tStart) / CLOCKS_PER_SEC);
cout << " : Fib(" << i << ")" << endl;
}*/
cout << "----------------Linear--------------"<<endl;
for (int i = 0; i < 200000000; i = i + 20000000)
//for (int i = 0; i < 50; i = i + 5)
{
clock_t tStart = clock();
F[0] = 0; F[1] = 1;
cout << FiboNR(i);
printf("Time taken: %.2fs\n", (double)(clock() - tStart) / CLOCKS_PER_SEC);
cout << " : Fib(" << i << ")" << endl;
}
cout << "----------------Recursion Modified--------------" << endl;
for (int i = 0; i < 200000000; i = i + 20000000)
//for (int i = 0; i < 50; i = i + 5)
{
clock_t tStart = clock();
F[0] = 0; F[1] = 1;
cout << FibMod(0,i);
printf("Time taken: %.2fs\n", (double)(clock() - tStart) / CLOCKS_PER_SEC);
cout << " : Fib(" << i << ")" << endl;
}
std::cin.ignore();
return 0;
}
答案 0 :(得分:4)
你通过传统的编程方法循环更快。但是有一类称为函数式编程语言的语言不包含循环。我是函数式编程的忠实粉丝,我是一个狂热的Haskell用户。 Haskell是一种函数式编程语言。在这个而不是循环中,您使用递归。要实现快速递归,有一种称为尾递归的东西。基本上为了防止向系统堆栈提供大量额外信息,您可以编写函数,使所有计算都存储为函数参数,这样除了函数调用指针之外,不需要将任何内容存储在堆栈中。因此,一旦调用了最终的递归调用,程序只需要转到第一个函数调用堆栈条目,而不是展开堆栈。函数式编程语言编译器有一个内置的设计来处理这个问题。现在即使是非函数式编程语言也在实现尾递归。
例如,考虑找到用于查找正数的阶乘的递归解决方案。 C中的基本实现是
int fact(int n)
{
if(n == 1 || n== 0)
return 1
return n*fact(n-1);
}
在上述方法中,每次调用堆栈时,n都存储在堆栈中,以便可以将其与事实(n-1)的结果相乘。这基本上发生在堆栈展开期间。现在查看以下实现。
int fact(int n,int result)
{
if(n == 1 || n== 0)
return result
return fact(n-1,n*result);
}
在这种方法中,我们将计算结果传递给变量结果。所以最后我们直接得到变量结果的答案。您唯一需要做的就是在初始调用中,在这种情况下为结果传递值1。堆栈可以直接解开到第一个条目。当然,我不确定C或C ++是否允许尾递归检测,但函数式编程语言确实如此。
答案 1 :(得分:1)
您的“递归修改”版本根本没有递归。
事实上,启用非递归版本的唯一一件事就是填充数组的一个新条目是主函数中的for循环 - 所以它实际上也是一个使用迭代的解决方案(支持immibis和BlastFurnace注意到了。)
但你的版本甚至没有正确地做到这一点。相反,因为它始终使用i == 0
调用,所以它会非法读取F[-1]
和F[-2]
。你很幸运(?) 1 程序没有崩溃。
您获得正确结果的原因是整个F
数组预先填充了正确的版本。
你计算Fib(2000 ....)的尝试无论如何都没有成功,因为你溢出double
。你有没有试过运行那段代码?
这是一个正常工作的版本(无论如何都是double
的精度)并且不使用全局数组(它实际上是迭代与递归而不是迭代与memoization)。
#include <cstdio>
#include <ctime>
#include <utility>
double FiboIterative(int n)
{
double a = 0.0, b = 1.0;
if (n <= 0) return a;
for (int i = 2; i <= n; i++)
{
b += a;
a = b - a;
}
return b;
}
std::pair<double,double> FiboRecursive(int n)
{
if (n <= 0) return {};
if (n == 1) return {0, 1};
auto rec = FiboRecursive(n-1);
return {rec.second, rec.first + rec.second};
}
int main(void)
{
const int repetitions = 1000000;
const int n = 100;
volatile double result;
std::puts("----------------Iterative--------------");
std::clock_t tStart = std::clock();
for( int i = 0; i < repetitions; ++i )
result = FiboIterative(n);
std::printf("[%d] = %f\n", n, result);
std::printf("Time taken: %.2f us\n", (std::clock() - tStart) / 1.0 / CLOCKS_PER_SEC);
std::puts("----------------Recursive--------------");
tStart = std::clock();
for( int i = 0; i < repetitions; ++i )
result = FiboRecursive(n).second;
std::printf("[%d] = %f\n", n, result);
std::printf("Time taken: %.2f us\n", (std::clock() - tStart) / 1.0 / CLOCKS_PER_SEC);
return 0;
}
-
1 可以说任何隐藏错误的东西实际上都是不吉利的。
答案 2 :(得分:-1)
我认为这不是一个好问题。但也许答案为什么有些有趣。
首先让我说一般来说这个说法可能是正确的。但是......
有关c ++程序性能的问题非常本地化。永远不可能给出一个好的一般答案。应对每个示例进行分析,并单独进行分析。它涉及许多技术细节。允许c ++编译器按照他们的意愿实际修改程序,只要它们不产生可见的副作用(无论这意味着什么)。所以只要你的计算给出相同的结果就好了。这在技术上允许将程序的一个版本转换为等效的,甚至从递归版本转换为基于循环的版本,反之亦然。所以它取决于编译器优化和编译器工作。
此外,要将一个版本与另一个版本进行比较,您需要证明您比较的版本实际上是等效的。
如果更容易针对编译器进行优化,也可能会发生以某种方式递归实现算法比基于循环的算法更快。通常迭代版本更复杂,通常代码越简单,编译器就越容易优化,因为它可以对不变量等做出假设。