斐波那契数求解引发的思考

定义:

斐波那契数列是指这样一个数列:0,1,1,2,3,5,8,13,21,34,55......这个数列从第3项开始 ,每一项都等于前两项之和。

  • 表达式:F(n) = F(n-1) + F(n-2)

  • 基例:F(0) = 0, F(1) = 1

求解

方法 1. 递归法

代码

使用递归的方法,编写代码如下

go 复制代码
/**
 * @ 方法 1:递归的方法
 * @ num - 用于求解的斐波那契数参数
 * @ 返回结果
 * */ 
int fibonacci_1(int num)
{
    if (num < 2)
        return num;
    else 
        return (fibonacci_1(num-1) + fibonacci_1(num-2));
}
分析
  • 因为重复计算了很多相同的斐波那契数,以上程序实现效率非常低。

  • 在嵌入式系统中使用,由于栈空间的限制,递归调用可能会导致栈溢出。

  • 递归会产生额外的函数调用开销(保存和恢复寄存器状态)。

方法 2. 记忆法

代码

在递归的基础上,记忆已计算过的斐波拉契数,避免重复计算。编写代码如下

go 复制代码
/**
 * @ 方法 2:递归 + 记忆的方法
 * @ num - 用于求解的斐波那契数参数     res - 记录求过的斐波那契数
 * @ 返回结果
 * */ 
int fibonacci_2(int num, int *res)
{
    int result = 0;

    if (num < 2) {
        result = num;
    } elseif (res[num] != -1) {    /* 判断是否求解过 */
        result = res[num];
    } else {
        result = (fibonacci_2(num-1, res) + fibonacci_2(num-2, res));
        res[num] = result;        /* 记录斐波那契数的值 */
    }

    return result;
}
分析
  • 通过记录求得的斐波拉契数值,避免重复迭代计算。

  • 因为需要引入数组记录数据,所以会有一定的额外空间开销。

  • 注意:申请数组空间需要大于所求的斐波拉契数值。

方法 3. 迭代法

代码

使用循环的方式求解,减少递归带来的栈开销。编写代码如下

go 复制代码
/**
 * @ 方法 3:迭代的方法
 * @ num - 用于求解的斐波那契数参数
 * @ 返回结果
 * */ 
int fibonacci_3(int num)
{
    int a = 0, b = 1, tmp;

    if (num > 0) {
        while (num--) {
            tmp = a;
            a = b;
            b = tmp + b;
        }     
    }
    return a;
}
分析
  • 简单高效,适合大多数应用场景。

  • 减少递归带来的栈空间开销。

  • 没有递归代码容易理解。

方法 4. 动态规划

代码

预先计算并存储所有中间结果,以便快速查询。编写代码如下

go 复制代码
/**
 * @ 方法 4:动态规划的方法
 * @ num - 用于求解的斐波那契数参数
 * @ 返回结果
 * */ 
int fibonacci_4(int num)
{
    int i, res[MAX_NUM];

    res[0] = 0;
    res[1] = 1;
    if (num >= 2) {
        for (i=2; i<=num; i++) {
            res[i] = res[i-1] + res[i-2];
        }
    }
    return res[num];
}
分析
  • 系统化记录结果,适合多次查询。

  • 需要额外的内存空间。

完整代码

go 复制代码
/**
 * @Filename : fibonacci.c
 * @Revision : $Revision: 1.00 $
 * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅)
 * @Description : 计算斐波那契数 f(n) = f(n-1) + f(n-2)  f(0) = 0, f(1) = 1
**/

#include <stdio.h>
#include <string.h>

#define MAX_NUM     100  

/**
 * @ 方法 1:递归的方法
 * @ num - 用于求解的斐波那契数参数
 * @ 返回结果
 * */
int fibonacci_1(int num)
{
    if (num < 2)
        return num;
    else
        return (fibonacci_1(num-1) + fibonacci_1(num-2));
}

/**
 * @ 方法 2:递归 + 记忆的方法
 * @ num - 用于求解的斐波那契数参数     res - 记录求过的斐波那契数
 * @ 返回结果
 * */
int fibonacci_2(int num, int *res)
{
    int result = 0;

    if (num < 2) {
        result = num;
    } elseif (res[num] != -1) {    /* 判断是否求解过 */
        result = res[num];
    } else {
        result = (fibonacci_2(num-1, res) + fibonacci_2(num-2, res));
        res[num] = result;        /* 记录斐波那契数的值 */
    }

    return result;
}

/**
 * @ 方法 3:迭代的方法
 * @ num - 用于求解的斐波那契数参数
 * @ 返回结果
 * */
int fibonacci_3(int num)
{
    int a = 0, b = 1, tmp;

    if (num > 0) {
        while (num--) {
            tmp = a;
            a = b;
            b = tmp + b;
        }     
    }
    return a;
}

/**
 * @ 方法 4:动态规划的方法
 * @ num - 用于求解的斐波那契数参数
 * @ 返回结果
 * */
int fibonacci_4(int num)
{
    int i, res[MAX_NUM];

    res[0] = 0;
    res[1] = 1;
    if (num >= 2) {
        for (i=2; i<=num; i++) {
            res[i] = res[i-1] + res[i-2];
        }
    }
    return res[num];
}

/**
 * @ 主函数
 * */
int main(void)
{
    int num = 10, i, res[MAX_NUM] = { 0 };

    printf ("-------方法1 递归法------\n");
    printf ("fibonacci_1(%d) = %d\n", num, fibonacci_1(num));

    printf ("-------方法2 记忆法------\n");
    memset(res, -1, sizeof(res));
    res[0] = 0;
    res[1] = 1;
    printf ("fibonacci_2(%d) = %d\n", num, fibonacci_2(num, res));

    printf ("-------方法3 迭代法------\n");
    printf ("fibonacci_3(%d) = %d\n", num, fibonacci_3(num));

    printf ("-------方法4 动态规划------\n");
    printf ("fibonacci_4(%d) = %d\n", num, fibonacci_4(num));
    
    return0;
}

运行结果

总结

以上 4 种求斐波那契数的方法,各有优缺点。

  • 递归法:效率最低,代码最简洁。

  • 记忆法:作为递归法的进阶,减少重复计算,大大提高了效率。适用于需要多次计算不同斐波那契数的情况。

  • 迭代法:高效并节省了内存。适合大多数应用场景。

  • 动态规划:预先计算并存储所有中间结果,适合需要多次查询斐波那契数的场景。

相关推荐
小梁不秃捏2 小时前
深入浅出Java虚拟机(JVM)核心原理
java·开发语言·jvm
我不是程序猿儿3 小时前
【C】识别一份嵌入式工程文件
c语言·开发语言
Dizzy.5173 小时前
数据结构(查找)
数据结构·学习·算法
软件开发技术局4 小时前
撕碎QT面具(8):对控件采用自动增加函数(转到槽)的方式,发现函数不能被调用的解决方案
开发语言·qt
周杰伦fans5 小时前
C#中修饰符
开发语言·c#
yngsqq5 小时前
c# —— StringBuilder 类
java·开发语言
赔罪6 小时前
Python 高级特性-切片
开发语言·python
星星点点洲6 小时前
【操作幂等和数据一致性】保障业务在MySQL和COS对象存储的一致
java·mysql
xiaolingting6 小时前
JVM层面的JAVA类和实例(Klass-OOP)
java·jvm·oop·klass·instanceklass·class对象
分别努力读书6 小时前
acm培训 part 7
算法·图论