热门标签 | 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~

算你勉强合格吧!


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




推荐阅读
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • 开发笔记:计网局域网:NAT 是如何工作的?
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了计网-局域网:NAT是如何工作的?相关的知识,希望对你有一定的参考价值。 ... [详细]
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社区 版权所有