从斐波那契数列看java方法的调用过程
先看斐波那契数列的定义:
斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递归的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*)
翻译成java代码是:
public int Fibonacci(int n) {
if (n == 1 || n == 2) {
return 1;
}
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
代码很简单,简单的递归,就不解释了。
但是这个方法在实际执行的过程中有可能出现java.lang.StackOverflowError错误,这个错误是什么意思呢,jdk官方文档是这么解释的:应用程序递归太深而发生堆栈溢出时,抛出该错误。
JAVA程序运行时,会在内存中划分5片空间进行数据的存储。分别是:1:寄存器。2:本地方法区。3:方法区。4:栈。5:堆。我们上面说的“堆栈”就是指栈了,java栈中主要存储基本数据类型的值和对象的引用。那么在上面这个递归执行的过程中发生了什么呢?
我们先分析一下这个数列的公式:
F(n)=F(n-1)+F(n-2)
=(F(n-2)+F(n-3))+(F(n-3)+F(n-4))
=F(n-3)+F(n-4)+F(n-4)+F(n-5)+F(n-4)+F(n-5)+F(n-5)+F(n-6)
……
观察可以发现,这个方法在执行的过程中每多增加一层递归需要存储临时参数的栈空间就是上一层的两倍,而在最终递归结束前,每一层的方法参数n的值都不会被释放(出栈),所以栈的深度将会以O(2^n)的空间复杂度越压越深,最终达到最大深度导致栈溢出。
毫无疑问,对实现一个斐波那契数列来说,这个实现空间复杂度O(2^n)太大了,而实际上每一层方法传入的参数值n在成功传到下一层的时候就没用了,但是却一直不能出栈,导致了栈空间的浪费。
所以要优化这个算法,我们就只能让每层方法结束后及时出栈,也就是别用递归。不用递归用什么呢?很多时候,递归的功能可以通过循环来实现,循环内部的代码可以看作是一个方法的方法体,与递归不同的地方是循环代码没有返回值,但我们可以通过在循环内部更改外部变量的值来实现类似于返回值的效果。
有公式 F(n)=F(n-1)+F(n-2),且已知F(1)=1和F(2)=1,F(3)=1+1=2,F(4)=F(3)+F(2)=2+1=3,利用这个思路,我们可以得到如下的代码:
public int Fibonacci(int n) {
int l = 1, j = 1, k = 1;
for (int i = 3; i <= n; i++) {
k = l + j;
l = j;
j = k;
}
return n == 0 ? 0 : k;
}
解释一下:l 和 j 一开始分别为F(1)和F(2)的值,循环从n>2时开始(小于等于2直接返回k的初始值),每循环一层,k的值变为前两项 l 和 j 的和,然后更新 l 和 j 的值供下次循环使用。
改良后的算法最大的特点就是栈深度只有一层,没有无用的变量值浪费空间,空间复杂度达到了最优。
从斐波那契数列看java方法的调用过程的更多相关文章
- (转)从斐波那契数列看Java方法的调用过程
斐波那契数列的定义: 斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家列安纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔 ...
- Java实现斐波那契数列的多种方法
小编综合了很多算法相关的书籍以及其他,总结了几种求斐波那契数列的方法 PS:其中的第83行的递归法是求斐波那契数列的经典方法 public class 斐波那契数列 { //迭代法 public st ...
- 几种复杂度的斐波那契数列的Java实现
一:斐波那契数列问题的起源 13世纪初期,意大利数论家Leonardo Fibonacci在他的著作Liber Abaci中提出了兔子的繁殖问题: 如果一开始有一对刚出生的兔子,兔子的长大需要一个月, ...
- 斐波那契数列【java实现】
java 实现斐波那契数列 以下是Java代码实现(递归与递推两种方式): import java.util.Scanner; /** * Fibonacci * * @author tongqian ...
- 斐波那契数列(Java)
一.什么是斐波那契数列 斐波那契数列(Fibonacci sequence),又称黄金分割数列.因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为& ...
- 剑指Offer-7.斐波那契数列(C++/Java)
题目: 大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0). n<=39 分析: 斐波那契数列是0,1,1,2,3,5,8,13...也就是当前 ...
- 算法小节(一)——斐波那契数列(java实现)
看到公司的笔试题中有一道题让写斐波那契数列,自己忙里偷闲写了一下 什么是斐波那契数列:斐波那契数列指的是这样一个数列 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
- 【斐波那契数列】java探究
题目描述 大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0). n<=39 解析 (1)递归方式 对于公式f(n) = f(n-1) + f(n ...
- 剑指offer第二版面试题10:斐波那契数列(JAVA版)
题目:写一个函数,输入n,求斐波那契数列的第n项.斐波那契数列的定义如下: 1.效率很低效的解法,挑剔的面试官不会喜欢 使用递归实现: public class Fibonacci { public ...
随机推荐
- Prometheus Operator 自动发现和持久化
Prometheus Operator 自动发现和持久化 之前在 Prometheus Operator 下面自定义一个监控选项,以及自定义报警规则的使用.那么我们还能够直接使用前面课程中的自动发现功 ...
- MySQL 官方样板数据库sakila
Sakila示例数据库最初由MySQL AB文档团队的前成员Mike Hillyer开发,旨在提供可用于书籍,教程,文章,样本等示例的标准模式. Sakila示例数据库还用于突出MySQL的最新功能, ...
- 数据库设计规范、E-R图、模型图
(1)数据库设计的优劣: 糟糕的数据库设计: ①数据冗余冗余.存储空间浪费. ②数据更新和插入异常. ③程序性能差. 良好的数据库设计 ①节省数据的存储空间. ②能够保证数据的完整新. ③方便进行数据 ...
- Skywalking的增强与拦截机制
整理自架构经理(汤哥)的分享 字节增强条件匹配 在 skywalking 中实现很多基于 byte-buddy 的关于链式匹配查询的实现, 代码如下所示: public abstract class ...
- Cache的一些总结
输出缓存 这是最简单的缓存类型,它保存发送到客户端的页面副本,当下一个客户端发送相同的页面请求时,此页面不会重新生成(在缓存有限期内),而是从缓存中获取该页面:当然由于缓存过期或被回收,这时页面会重新 ...
- luogu P3750 [六省联考2017]分手是祝愿
luogu loj 可以发现在最优策略中,每种操作最多只会做一次,并且操作的先后顺序并不会影响答案,所以考虑从后往前扫,碰到一个\(1\)就对这个位置\(i\)进行操作,这样的操作一定是最优策略.记最 ...
- django 函数和类实现分页案例
方法一: 模拟分页from django.shortcuts import render,HttpResponse from app01 import models def hostnames(req ...
- asp.net frameworke处理程序的作用
1 向客户端发送响应的工作都由处理程序完成 2 任何实现System.web.ihttpHandler接口的类都可以作为传入的http请求的目标 3 如果需要重复使用自定义处理程序对象,需要创建自定义 ...
- 最简单的SAP云平台开发教程 - 如何开发UI5应用并运行在SAP云平台上
选择Services Catalog,根据关键字搜索到WebIDE服务,点击超链接打开WebIDE: 进入workspace,选择Git->Clone Repository: 从我的github ...
- Delphi 类的方法