循环真的比递归快吗?

Are loops really faster than recursion?

本文关键字:递归 真的 循环      更新时间:2023-10-16

根据我的教授的说法,循环比使用递归更快,更缺乏,但我想出了这个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: %.2fsn", (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: %.2fsn", (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: %.2fsn", (double)(clock() - tStart) / CLOCKS_PER_SEC);
        cout << " : Fib(" << i << ")" << endl;
    }
    std::cin.ignore();
    return 0;
}

你通过传统的编程方法循环更快。但是有一类语言称为函数式编程语言,它不包含循环。我是函数式编程的忠实粉丝,也是Haskell的狂热用户。Haskell是一种函数式编程语言。在此而不是循环中,您使用递归。为了实现快速递归,有一种称为尾递归的东西。基本上,为了防止系统堆栈中有很多额外的信息,您编写函数的方式是将所有计算存储为函数参数,以便除了函数调用指针之外,不需要在堆栈上存储任何内容。因此,一旦调用了最终的递归调用,程序只需要转到第一个函数调用堆栈条目,而不是展开堆栈。函数式编程语言编译器具有内置设计来处理此问题。现在,即使是非函数式编程语言也在实现尾递归。

例如,考虑查找用于查找正数阶乘的递归解。C 语言中的基本实现是

int fact(int n)
{
  if(n == 1 || n== 0)
       return 1
   return n*fact(n-1);
}

在上述方法中,每次调用堆栈时,n都存储在堆栈中,以便可以将其与fact(n-1(的结果相乘。这基本上发生在堆栈展开过程中。现在查看以下实现。

int fact(int n,int result)
{
   if(n == 1 || n== 0)
       return result
       return fact(n-1,n*result);
}

在这种方法中,我们将计算结果传递到变量结果中。所以最后我们直接在变量结果中得到答案。在这种情况下,您唯一需要做的就是在初始调用中为结果传递值 1。堆栈可以直接展开到其第一个条目。当然,我不确定 C 或 C++ 是否允许尾递归检测,但函数式编程语言允许

您的"递归修改"版本根本没有递归。

事实上,唯一启用非递归版本来填充数组的一个新条目的是主函数中的 for 循环——所以它实际上也是一个使用迭代的解决方案(对 imibis 和 HeartFarm 的道具,用于注意到这一点(。

但是您的版本甚至没有正确执行此操作。 相反,由于它总是用i == 0调用,所以它非法读取F[-1]F[-2]。 你很幸运(?1 程序没有崩溃。

您获得正确结果的原因是整个F数组都由正确的版本预填充。

无论如何,您尝试计算 Fib(2000....( 都没有成功,因为您溢出了double。 你甚至尝试过运行该代码吗?

这是一个正常工作的版本(无论如何,精度为 double(并且不使用全局数组(它实际上是迭代与递归,而不是迭代与记忆(。

#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] = %fn", n, result);
    std::printf("Time taken: %.2f usn", (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] = %fn", n, result);
    std::printf("Time taken: %.2f usn", (std::clock() - tStart) / 1.0 / CLOCKS_PER_SEC);
    return 0;
}

--

1可以说,任何隐藏错误的东西实际上都是不吉利的。

我认为这不是一个好问题。但也许答案为什么有点有趣。

首先让我说,一般来说,这种说法可能是正确的。但。。。

关于 C++ 程序性能的问题非常本地化。永远不可能给出一个好的笼统答案。每个示例都应单独进行分析。它涉及许多技术细节。允许 C++ 编译器按照他们的意愿实际修改程序,只要它们不产生明显的副作用(无论确切的含义是什么(。因此,只要您的计算给出相同的结果就可以了。这在技术上允许将程序的一个版本转换为等效版本,甚至从递归版本转换为基于循环的版本,反之亦然。因此,这取决于编译器优化和编译器工作。

此外,要将一个版本与另一个版本进行比较,您需要证明您比较的版本实际上是等效的。

如果更容易针对编译器进行优化,则算法的递归实现也可能以某种方式比基于循环的算法更快。通常迭代版本更复杂,通常代码越简单,编译器就越容易优化,因为它可以对不变量等做出假设。