青蛙跳台阶问题暨斐波那契数列
1.问题描述
一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
2.问题分析
设 f(n) 表示青蛙跳上 n 级台阶的跳法数。当只有一个台阶时,
即 n = 1 时, 只有 1 中跳法;
当 n = 2 时,有 2 种跳法;
当 n = 3 时,有 3 种跳法;
当 n 很大时,青蛙在最后一步跳到第 n 级台阶时,有两种情况:
一种是青蛙在第 n-1 个台阶跳一个台阶,那么青蛙完成前面 n-1 个台阶,就有 f(n-1) 种跳法,这是一个子问题。
另一种是青蛙在第 n-2 个台阶跳两个台阶到第 n 个台阶,那么青蛙完成前面 n-2 个台阶,就有 f(n-2) 种情况,这又是另外一个子问题。
两个子问题构成了最终问题的解,所以当 n>=3 时,青蛙就有 f(n) = f(n-1) + f(n-2) 种跳法。上面的分析过程,其实我们用到了动态规划的方法,找到了状态转移方程,用数学方程表达如下:
仔细一看,这不就是传说中的著名的斐波那契数列,但是与斐波那契数列的还是有一点区别,斐波那契数列从 0 开始,f(0)=0,f(1)=1,f(2)=1。斐波那契数列(Fibonacci Sequence),又称黄金分割数列,因为当 n 趋于无穷大时,前一个数与后一个数的比值无限接近于黄金比例(√5−12\frac{√5-1}{2}2√5−1的无理数,0.618…)。
3.递归实现
有了初始状态和状态转移方程,那么编程实现求解就不难了,参考下面的递归实现。
int fib(int n){ if (n <= 0)return -1; if (1 == n) return 1; if (2 == n) return 2; return fib(n-1)+fib(n-2);
}
3.1 时间复杂度分析
以递归实现斐波那契数,效率是非常低下的,因为对子问题的求解 fib(n-1) 和 fib(n-2) 两者存在重叠的部分,对重叠的部分重复计算造成了浪费。但递归求解其优点也是显而易见的,代码简单,容易理解。
设f(n)为参数为n时的时间复杂度,很明显,f(n) = f(n-1) + f(n-2) 变为 f(n) - f(n-1) - f(n-2) = 0,仔细一看,这就是数学上的二阶线性常系数齐次差分方程,求该差分方程的解,就是求得f(n)的非递归表达式,也就得到了上面递归算法的时间复杂度。关于齐次二阶常系数线性差分方程可能大家已经没有什么概念了,乍一听一脸懵逼,包括我自己,大学的高数基本已经还给老师了,但是涉及到算法,数学还是相当的重要并且扮演者不可替代的角色。这里简单解释一下我自己温习后对齐次二阶常系数线性差分方程的理解,不清楚的,大家还是要搜索相关资料,恶补一下吧!
差分概念:
“二阶线性常系数齐次”是对差分方程的修饰,“差分”也是对方程的修饰,先看一下差分的概念:
给定函数:ft=f(t),t=0,1,2...f_t=f(t),t=0,1,2...ft=f(t),t=0,1,2...,注意t的取值是离散的
一阶差分:Δyt=yt+1−yt=f(t+1)−f(t)\Delta y_t=y_{t+1}-y_t=f(t+1)-f(t)Δyt=yt+1−yt=f(t+1)−f(t)
二阶差分:
差分方程的定义:
含有自变量t和两个或两个以上的函数值yt,yt+1,...,yt+ny_t,y_{t+1},...,y_{t+n}yt,yt+1,...,yt+n的方程,称为差分方程。出现在差分方程中的未知函数下标的最大差称为差分方程的阶。差分方程中函数值yty_tyt的指数为1,称为线性查分方程,函数值yty_tyt的系数为常量,称为常系数查分方程。差分方程可以化简为形如:
如果f(t)=0f(t)=0f(t)=0,那么上面就是n阶线性齐次差分方程;
如果f(t)=0f(t)=0f(t)=0,那么上面就是n阶线性非齐次差分方程。
也就是说查分方程的常数项为0,就是齐次,非零就是非齐次。
如果查分方程中函数值yty_tyt前的系数是常量的话,那么就是常系数查分方程。
差分方程的表达式可以定义如下:
好了,了解了差分方程的阶,常系数,齐次,线性的概念,下面来辨识一下不同的差分方程吧。
有了关于差分方程的一些定义和概念,现在应该知道为什么f(n)-f(n-1)+f(n-2)=0叫作二阶线性常系数齐次差分方程了吧。因为n-(n-2)=2,所以是二阶,函数值f(n),f(n-1)和f(n-2)的指数是1,且系数均是常数,所以是线性常系数,又因为常数项为0,即等号右边为0,所以是齐次的。因为是根据函数值的表达式求函数的表达式,所以差分的,所以该方程就是恶心的二阶线性常系数齐次差分方程。
差分方程求解:
对于二阶线性常系数齐次差分方程的求解过程是,确定特征方程->求特征方程的根->由求特征方程的根确定通解的形式->再由特定值求得特解。
下面给出f(n)-f(n-1)+f(n-2)=0的解过程。
设f(n)=λnf(n)=\lambda^nf(n)=λn,那么f(n)-f(n-1)+f(n-2)=0的特征方程就是:λ2−λ+1=0\lambda^2-\lambda+1=0λ2−λ+1=0,求解得:$\lambda=(1±√5)/2 $。所以,f(n)的通解为:
由f(1)=1,f(2)=2可解得c1=(5+√5)/10, c2 ==(5-√5)/10,最终可得时间复杂度为:
我知道时间度的复杂常见的有且依序复杂度递增:
O(1), O(lgn),O(n)O(\sqrt n)O(n),O(n),O(nlgn),O(n2)O(n^2 )O(n2),O(n3)O(n^3 )O(n3),O(2n)O(2^n )O(2n),O(n!)。
那么上面求得的算法时间复杂度是归于哪个级别。很明显是O(2n)O(2^n)O(2n)。也就是说斐波那契数列递归求解的算法时间复杂度是O(2n)O(2^n )O(2n)。
关于斐波那契数列递归求解的期间复杂度我们简化其求解过程,按照如下方式求解。
递归的时间复杂度是: 递归次数*每次递归中执行基本操作的次数。所以时间复杂度是: O(2n)O(2^n)O(2n)。
3.2 空间复杂度
每一次递归都需要开辟函数的栈空间,递归算法的空间复杂度是:
递归深度N∗每次递归所要的辅助空间递归深度N*每次递归所要的辅助空间递归深度N∗每次递归所要的辅助空间
如果每次递归所需的辅助空间是常数,则递归的空间复杂度是 O(N)。因为上面的递归实现,虽然每次递归都会有开辟两个分支,按理说递归调用了 多少次,就开辟了多大的栈空间,按照这个逻辑,那么空间复杂度与时间复杂应该是一样的, 都是 O(2n)O(2^n)O(2n)。那么这个逻辑错在了哪里呢?首先我们要知道函数的调用过程大概是什么样的,调用者 (caller) 将被调用者 (callee) 的实参入栈,call 被调用者,被调用者中保留caller 的栈底指针 EBP,将 ESP 赋给 EBP 开始一个新的栈帧,函数结束后清理栈帧,pop 原函数栈底指针 EBP 到 ESP,这一步也就是恢复函数调用的现场。现在再来看看上面斐波那契数列的递归实现,因为是单线程执行,以 Fib(5) 为例,函数执行的过程应该如下图所示:
可见递归的深度越深,开辟的实参栈空间就会越大。图中最深处的开辟了最大的辅助空间,当函数执行的流程向上回溯时,你就会发现,后面开辟的辅助栈空间都是在前面开辟的栈空间上开辟的,也就是空间的重复利用,所以说递归算法的空间复杂度是递归最大的深度*每次递归开辟的辅助空间,所以斐波那契数列的递归实现的空间复杂度是 O(n)。
图中示例的是单线程情况下递归时的函数执行流程,但是在多线程的情况下,就不是这个样子,因为每个线程函数并发执行,拥有自己的函数栈,所以空间复杂度要另当计算,这里就不做深究,有兴趣的读者可自行研究。
4.迭代实现
递归实现虽然简单易于理解,但是O(2n)O(2^n)O(2n)的时间复杂度和O(n)的空间却让人无法接受,下面迭代法的具体实现,比较简单,就不再赘述实现步骤。时间复杂度为O(n),空间复杂度为O(1)。
int fibIteration(int n){ if (n <= 0)return -1; if (1 == n) return 1; if (2 == n) return 2;int res=0,a=1,b=2;for(int i=3;i<=n;++i){res=a+b;a=b;b=res;}return res;
}
这个方法是求斐波那契数列的最快方法吗?当然不是,最快的应该是下面的矩阵法。
5.矩阵法
根据上面的递归公式,我们可以得到。
因而计算f(n)就简化为计算矩阵的(n-2)次方,而计算矩阵的(n-2)次方,我们又可以进行分解,即计算矩阵(n-2)/2次方的平方,逐步分解下去,由于折半计算矩阵次方,因而时间复杂度为O(logn)。
下面给出网友beautyofmath在文章关于斐波那契数列三种解法及时间复杂度分析中的实现。
#include <iostream>
using namespace std;class Matrix
{
public:int n;int **m;Matrix(int num){m=new int*[num];for (int i=0; i<num; i++) {m[i]=new int[num];}n=num;clear();}void clear(){for (int i=0; i<n; ++i) {for (int j=0; j<n; ++j) {m[i][j]=0;}}}void unit(){clear();for (int i=0; i<n; ++i) {m[i][i]=1;}}Matrix operator=(const Matrix mtx){Matrix(mtx.n);for (int i=0; i<mtx.n; ++i) {for (int j=0; j<mtx.n; ++j) {m[i][j]=mtx.m[i][j];}}return *this;}Matrix operator*(const Matrix &mtx){Matrix result(mtx.n);result.clear();for (int i=0; i<mtx.n; ++i) {for (int j=0; j<mtx.n; ++j) {for (int k=0; k<mtx.n; ++k) {result.m[i][j]+=m[i][k]*mtx.m[k][j];} }}return result;}
};
int main(int argc, const char * argv[]) {unsigned int num=2;Matrix first(num);first.m[0][0]=1;first.m[0][1]=1;first.m[1][0]=1;first.m[1][1]=0;int t;cin>>t;Matrix result(num);result.unit();int n=t-2;while (n) {if (n%2) {result=result*first;}first=first*first;n=n/2;}cout<<(result.m[0][0]+result.m[0][1])<<endl;return 0;
}
有兴趣的读者可自行给出实现,本人后续再补充代码。
6.问题拓展
青蛙跳台阶问题可以引申为如下问题:
一只青蛙一次可以跳上1级台阶,也可以跳上2 级,……,也可以跳上n 级,此时该青蛙跳上一个n级的台阶总共有多少种跳法?
##6.1问题分析
当n = 1 时, 只有一种跳法,即1阶跳:Fib(1) = 1;
当n = 2 时, 有两种跳的方式,一阶跳和二阶跳:Fib(2) = Fib(1) + Fib(0) = 2;
当n = 3 时,有三种跳的方式,第一次跳出一阶后,后面还有Fib(3-1)中跳法; 第一次跳出二阶后,后面还有Fib(3-2)中跳法,一次跳到第三台阶,Fib(3) = Fib(2) + Fib(1)+Fib(0)=4;
当n = n 时,共有n种跳的方式,第一次跳出一阶后,后面还有Fib(n-1)中跳法; 第一次跳出二阶后,后面还有Fib(n-2)中跳法…第一次跳出n阶后, 后面还有Fib(n-n)中跳法。所以Fib(n) = Fib(n-1)+Fib(n-2)+Fib(n-3)+…+Fib(0),又因为Fib(n-1)=Fib(n-2)+Fib(n-3)+…+Fib(0),两式相减得:Fib(n)-Fib(n-1)=Fib(n-1),所以Fib(n) = 2*Fib(n-1),n >= 2。递归等式如下:
6.2 具体实现
递归等式是一个以2为公比的等比数列,所以递归和迭代实现起来都比较简单,参考如下:
//递归法
//时间复杂度O(n),空间复杂度O(n)
int fib(int n){if (1 == n) return 1;return 2*fib(n-1);
}//迭代法
//时间复杂度O(n),空间复杂度O(1)
int fib(int n){int res=1;if (1 == n) return res; for(int i=2;i<=n;++i)res=2*res; return res;
}
7.小结
历时两天,参考了很多博文资料,即当中也遇到了很多不解的问题,很痛苦,尤其是研究已经忘记了的差分方程,不过还是坚持了下来。本篇力求较全面地给出青蛙跳台阶问题分析,各种解法以及时间复杂度和空间复杂度的分析,让大家能够不留疑惑地了解斐波那契数列的求解。
参考文献
[1] 斐波那契数列.百度百科
[2] 青蛙跳台阶问题
[3]关于斐波那契数列三种解法及时间复杂度分析
[4] 差分方程的基本概念
[5] 二阶线性常系数齐次差分方程的求解
[6] 时间复杂度&空间复杂度分析
青蛙跳台阶问题暨斐波那契数列相关推荐
- 7-12 兔子跳楼梯 高精度 java 斐波那契数列
小兔子喜欢蹦蹦跳跳上楼梯 ,它能一次跳1阶楼梯,也能一次跳上2阶楼梯.问小兔子要上一个n阶的楼梯,最多有多少种不同上楼的走法? 输入格式: 输入一行包含一个整数 n,表示有几阶楼梯. 输出格式: 上楼 ...
- C语言:跳楼梯问题(斐波那契数列)(vs)(递归)
一,问题: 小只因跳楼梯: 众所周知美国校队只因一次可以跳上1级台阶,也可以跳上2级.求该只因跳上一个n级的台阶总共有多少种跳法? 二,思路: /*从逆向想: x层的走法其实就是其前一层或前两层的走法 ...
- 斐波那契数列 青蛙跳台阶 变态跳台阶
目录 一.斐波那契数列 二.青蛙跳台阶问题 三.变态跳台阶 一.斐波那契数列 题目:写一个函数,输入n,求斐波那契数列的第n项. 思路:用递归的方法,f(n) =f(n-1) + f(n-2).代码比 ...
- 青蛙跳台阶:我如何得知它是一道斐波那契数列题?——应用题破题“三板斧”
本文以C语言实现. 目录 前言 一.斐波那契阿数列基础知识 二.引例:青蛙跳台阶 三.破题分析:举例归纳 1. 三板斧的使用 举例 模拟(必要时画图) 找规律 2. 代码展示 四.拓展用例:矩形覆盖问 ...
- 斐波那契数列的编程题,青蛙跳台
斐波那契数列的编程题,青蛙跳台 一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶.求该青蛙跳上一个 n 级的台阶总共有多少种跳法. 答案需要取模 1e9+7(1000000007),如计算初始结果为: ...
- 斐波那契数列及青蛙跳台阶问题
题目1: 写一个函数,输入n,求斐波那契(Fibonacci)数列的第n项. 斐波那契(Fibonacci)数列定义例如以下: f(n)=⎧⎩⎨⎪⎪0,1,f(n−1)+f(n−2),n=0n=1n& ...
- 牛客网刷题java之(斐波那契数列)一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
题目:一只青蛙一次可以跳上1级台阶,也可以跳上2级.求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果). 分析: 对于本题,前提只有 一次 1阶或者2阶的跳法. a.如果两种跳法, ...
- 由递推关系式用差分方程的方法得到通项公式实现求斐波那契数列的第n项;迭代、递归、栈、差分方程之间的本质联系以及由推广的迭代法解决“变态青蛙跳台阶”问题;汉诺塔问题的数字特征以及用递归解决的原理推导。
最近几天在研究算法中一个比较基础且突出的问题,就是关于"递推关系式.递归.迭代.序列前k项和"之间的区别与联系. 一.斐波那契数列与差分方程 首先我们考察一个经典的算法,求斐波那契 ...
- 笔试题:一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。该题有三种解法:递归的方法求解斐波那契数列、用概率与统计的数学方法解决,3.动态规划
笔试题 一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶.求该青蛙跳上一个 n 级的台阶总共有多少种跳法.该题有三种解法:1.递归的方法求解斐波那契数列.2.用概率与统计的数学方法解决,3.动态规划 ...
最新文章
- Python构建lassocv模型并输出Rad-score公式
- 我的Android进阶之旅------gt;Java全角半角的转换方法
- Enumerator a Composite Structure
- Swish激活 hswish激活
- BitMap位图与海量数据的理解与应用
- discuz 标签详解
- 第七章:清楚简洁的英文 --《英语科技写作(文法与修辞原则)》by 方克涛
- 利用SciTE的导出功能保持代码语法着色效果
- java 使用vsphere 创建虚拟机‘_Java数组的创建及使用
- 数据科学包1---numpy
- UltraISO9.3.0.2610中文绿色注册版
- Msm8960(APQ8064)平台的MSM-AOSP-kitkat编译适配(3):寻找正确的代码版本
- 爬虫,酷我音乐接口解析
- pc端下载微信视频号中的视频
- 《Windows 8 权威指南》——2.2 Windows 8 Metro界面
- 2015移动技术白皮书
- python word转excel_看Python如何无缝转换Word和Excel
- php activemq实例,php操作ActiveMQ - 小周博客,小周个人博客,程序猿小王子,技术博客,个人博客模板,php博客系统,设计模式,wzyl - 黑夜遮不住光亮...
- 零代码积木编程案例分享
- 谈今天的头条 台湾地震影响海底光纤
热门文章
- 前端:高德地图快速入门使用
- webpack4+node合并资源请求, 实现combo功能(二十三)
- 你不知道的Event
- 一起撸个朋友圈吧 (Step6) 评论对齐(点击评论对齐)【下】
- python操作日期和时间的方法
- win2003 程序时间提供程序 NtpClient错误解决
- [Java] 蓝桥杯ADV-205 算法提高 拿糖果
- 【C++】n_element的用法
- L2-006. 树的遍历-PAT团体程序设计天梯赛GPLT
- mysql主库从库在同一台服务器_mysql数据库从一台服务器迁移到另一台服务器上...