热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

寻找斐波那契数列最优解(C++)

目录斐波那契数列简介算法部分一、原版递归二、尾递归(存值版递归)三、双指针缓存(存值版非递归)四、二阶矩阵因为在刷《剑指offer》的时候又又又又遇到了这个题,脑子里响起了“塔塔开

目录
  • 斐波那契数列简介
  • 算法部分
    • 一、原版递归
    • 二、尾递归(存值版递归)
    • 三、双指针缓存(存值版非递归)
    • 四、二阶矩阵


因为在刷《剑指offer》的时候又又又又遇到了这个题,脑子里响起了“塔塔开,不塔塔开就无法胜利啊!”,于是我准备好好把斐波那契数列弄明白,然后此文就诞生了。



斐波那契数列简介


斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……


在数学上,斐波那契数列以如下被以递推的方法定义:

F(0)=0

F(1)=1

F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N)**


算法部分


一、原版递归

long long fibonacci(unsigned int n) {
long long f[2] = {0, 1};
if(n<2)return f[n];

return fibonacci(n-1) + fibonacci(n-2);
}

n = 5

image-20211113161524232

n = 40(尝试了一下n=100,结果渣机在两分钟之内没跑出来)

#include
using namespace std;
int main(void) {
long long out = fibonacci(40);
cout < return 0;
}

image-20211113161842071

消耗时间:0.9454s

当n比较小的时候,运算还是比较愉快的~

但是当n比较大的时候事请就变得严重了~

\[time(40)\approx 11*time(5)
\]

那么,为什么呢?


其实斐波那契数列的递归过程可以看作一棵完全二叉树的建立(以n=5为例):

QQ截图20211113160746

最后f[5] = f[1] + f[0] + f[1] + f[1] + f[0] + f[1] + f[0] + f[1] = 1 + 0 + 1 + 1 + 0 + 1 + 0 + 1 = 5


我们发现,有好多没必要的重复计算出现。

比方说f(2)已经求出来了,但是我没有储存这个结果,于是计算机就得再求一遍,那么时间就会增加,这好吗?这不好!

这个例子f(2)求了3遍 f(3)求了2遍,而求f(5)只需要分别求1遍f(4),f(3),f(2)。

这样递归相当于浪费了一半的时间!


于是捏,我们就可以把已经求过了的f给存起来,给计算机减负。


二、尾递归(存值版递归)

long long fibonacci(int f0,int f1,unsigned int n) {

if(n > 0) {
return fibonacci(f0+f1,f0,n-1);
}

return f0;
}

n = 40

#include
using namespace std;
int main(void) {
long long out = fibonacci(0,1,5);//当然f0=0,f1=1咯,带进去就好了
cout < return 0;
}

image-20211113171235839

消耗的时间:0.2618s


三、双指针缓存(存值版非递归)

long long fibonacci(unsigned int n) {
long long f[100000] = {0, 1};
if(n<2)return f[n];

for(size_t i=2;i<=n;i++){
f[i] = f[i-1] + f[i-2];//每一次循环记录下新的f的值,不重复计算
}

return f[n];
}

n = 40

#include
using namespace std;
int main(void) {
long long out = fibonacci(40);
cout < return 0;
}

image-20211113172004974

消耗时间:0.1321s


存下值后再进行计算是不是快了很多!!!

但是,俗话说得好,活到老,学到老。


所以有没有更快、更强的方法呢

别急,马上安排上!


四、二阶矩阵

这个算法的时间复杂度为log(n),但前提是你要有一点点线代的知识,什么,你说没有?那我现场教你!


在数学中,矩阵(Matrix)是一个按照长方阵列排列的复数或实数集合,最早来自于方程组的系数及常数所构成的方阵。这一概念由19世纪英国数学家凯利首先提出。

image-20211113173545363

image-20211113173614403

image-20211113173625505

1749451-20190826113709947-2107024256

矩阵求值:

MommyTalk1636797045268


好了教完了,以上知识均来自于百度,但是我为什么要教你呢?因为你如果不懂这些的话大概率不会自学,不自学就不会往下看,不往下看我就不能装13,装不了13我就会很难受,所以为了我能装13,我必须教大家这个。


首先有这样一个这个公式:{f(n), f(n-1), f(n-1), f(n-2)} ={1, 1, 1,0}n-1

MommyTalk1636796822909

不知道也很简单,自己证明一下,上面的知识加上你高中数学知识足够证明出来。

有了这个公式,要求f(n),我们只需要求矩阵{1, 1, 1,0}的(n-1)次方。

但是如果我们从0开始循环,n次方仍然需要n次运算,和存值版没啥大的区别。


但是!他是乘方啊,乘方就可以分成两半,这样我们就不需要n的时间复杂度了,而是二分法所带来的趋于log(n)的时间复杂度!!!!

MommyTalk1636798608942

别懵,我来举个例子。

比方说你要求:26

普通人:2 * 2 = 4

​ 4 * 2 = 8

​ 8 * 2 = 16

​ 16 * 2 = 32

​ 32 * 2 = 64

二分人:22 = 2 * 2 = 4

​ 23 = 22 * 2 = 8

​ 26 = 23 * 23 = 8 * 8 = 64


普通人算了五次,而二分人只算了三次

了解了这些之后我们直接上代码!

struct Matrix2By2//2x2矩阵
{
Matrix2By2(long long m00 = 0, long long m01 = 0, long long m10 = 0, long long m11 = 0):m_00(m00), m_01(m01), m_10(m10), m_11(m11){}//初始化二元矩阵的四个元素
long long m_00;
long long m_01;
long long m_10;
long long m_11;
};
Matrix2By2 MatrixMultiply(const Matrix2By2& matrix1, const Matrix2By2& matrix2){
return Matrix2By2(matrix1.m_00 * matrix2.m_00 + matrix1.m_01 * matrix2.m_10,matrix1.m_00 * matrix2.m_01 + matrix1.m_01 * matrix2.m_11,matrix1.m_10 * matrix2.m_00 + matrix1.m_11 * matrix2.m_10,matrix1.m_10 * matrix2.m_01 + matrix1.m_11 * matrix2.m_11);
}//矩阵乘法
Matrix2By2 MatrixPower(unsigned int n)
{
Matrix2By2 matrix;
if(n == 1)
{
matrix = Matrix2By2(1, 1, 1, 0);
}
else if(n % 2 == 0)
{
matrix = MatrixPower(n / 2);
matrix = MatrixMultiply(matrix, matrix);
}
else if(n % 2 == 1)
{
matrix = MatrixPower((n - 1) / 2);
matrix = MatrixMultiply(matrix, matrix);
matrix = MatrixMultiply(matrix, Matrix2By2(1, 1, 1, 0));
}
return matrix;
}//矩阵乘方
int fibonacci(unsigned int n) {
int result[2] = {0, 1};
if(n <= 2)return result[n];
Matrix2By2 PowerNMinus2 = MatrixPower(n-1);
return PowerNMinus2.m_00;//返回的就是f[n],记不得可以回去看下公式
}

n=40

int main(void) {
long long out = fibonacci(40);
cout < return 0;
}

image-20211113182632305

消耗时间:0.09125s

太帅了!

试试之前那哥们儿算不出来的n=100

int main(void) {
unsigned long long out = fibonacci(100);//不加unsigned就溢出啦,可想而知斐波那契数列的威力巨大
cout < return 0;
}

image-20211113182905523

消耗时间:0.1228s

emmmm~

算你勉强合格吧!


大家要好好吃饭,每天都要开开心心的!




推荐阅读
  • 本文由编程笔记#小编为大家整理,主要介绍了logistic回归(线性和非线性)相关的知识,包括线性logistic回归的代码和数据集的分布情况。希望对你有一定的参考价值。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 达人评测 酷睿i5 12450h和锐龙r7 5800h选哪个好 i512450h和r75800h对比
    本文介绍了达人评测酷睿i5 12450h和锐龙r7 5800h选哪个好的相关知识,包括两者的基本配置和重要考虑点。希望对你在选择时提供一定的参考价值。 ... [详细]
  • IB 物理真题解析:比潜热、理想气体的应用
    本文是对2017年IB物理试卷paper 2中一道涉及比潜热、理想气体和功率的大题进行解析。题目涉及液氧蒸发成氧气的过程,讲解了液氧和氧气分子的结构以及蒸发后分子之间的作用力变化。同时,文章也给出了解题技巧,建议根据得分点的数量来合理分配答题时间。最后,文章提供了答案解析,标注了每个得分点的位置。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • 本文介绍了多因子选股模型在实际中的构建步骤,包括风险源分析、因子筛选和体系构建,并进行了模拟实证回测。在风险源分析中,从宏观、行业、公司和特殊因素四个角度分析了影响资产价格的因素。具体包括宏观经济运行和宏经济政策对证券市场的影响,以及行业类型、行业生命周期和行业政策对股票价格的影响。 ... [详细]
  • 本文介绍了开关稳压器设计中PCB布局布线的重要性,并提供了相应的准则。开关稳压器作为一种高效的电源,逐渐取代了线性稳压器。开关模式电源的工作原理是通过一定的开启时间和关闭时间来实现电压转换。开关频率并不是影响系统的最大因素,而开关转换的速度才是关键。在系统噪声方面,开关频率或其谐波可能会对系统产生影响。严格遵守PCB布局布线的准则,可以将开关模式电源的相关问题降到最小。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
author-avatar
山中幽水_418
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有