几种复杂度的斐波那契数列的Java实现
一:斐波那契数列问题的起源
13世纪初期,意大利数论家Leonardo Fibonacci在他的著作Liber Abaci中提出了兔子的繁殖问题:
如果一开始有一对刚出生的兔子,兔子的长大需要一个月,长大后的兔子每个月能生产一对兔子,假设兔子不会死亡,那么一年后有多少只兔子?
不难看出每个月的兔子的总数可以用以下数列表示:1,1,2,3,5,8,13......
二:最直观的算法
1.算法实现
通过观察我们不难发现斐波那契数列从第三项开始每一项都是前两项的和,因此我们不难总结出该数列的递推公式:

根据此地推公式我们可以很直观地得出斐波那契数列地二分递归实现:
long Fib(int n){
return (2 > n) ? (long) n : Fib(n -1) + Fib(n - 2);
}
2.时间复杂度分析
虽然此种算法实现简单一目了然但该算法地效率极其低下,下面分析一下该算法地时间复杂度:
按照该算法地思路,将Fib(n)所需地时间记为T(n)则T(n) = T(n - 1) + T(n - 2)。由此我们可以得出该时间复杂度地递推公式

为了便于计算我们将n 大于等于 2 地公式写作

不难看出这是一个二阶常系数齐次差分方程

假设
为方程的一个解,则 
特征方程的两个根为:
所以通解为:
其中C1 ,C2为常数,将
带入方程可得:
可以看出此种递归算法的时间复杂度为指数量级,因此随着n的增大
算法所需的时间也会急剧上升。
三:线性时间复杂度版本
1.算法实现
上一种算法之所以会产生指数量级的时间复杂度,是因为算法是根据表面定义Fib(n) = Fib(n - 1) +Fib(n - 2)的误导,事实上子问题
Fib(n - 1)和Fib(n - 2)并不是独立的。
线性迭代算法:
public class Test {
public static void main(String[] args) {
System.out.println(Fib(8));
}
//线性复杂度迭代,常数空间复杂度
public static int Fib(int n) {//计算第n项
int prev = 0;
int next = 1;//初始化:Fib(0) = 0,Fib(1) = 1
while(n-- > 1) {
next = prev + next;
prev = next - prev;//通过n次加减计算Fib(n)
}
return next;
}
}
以上算法不仅只需O(n)时间,而且只需常数规模的附加空间
通过以上迭代算法,我们可以实现如下线性递归版本:
public class Test {
public static void main(String[] args) {
System.out.println(Fib(8,0,1));
}
/* n:求斐波那契数列的第n项
* first:Fib(0) = 0
* second: Fib(1) = 1
* */
public static int Fib(int n,int first,int second) {
if(n < 2) {
return n;
}
if(n == 2) {
return first + second;//递归基
}else{
return Fib(n - 1,second,first+second);
}
}
}
以上两种方法都是利用变量记录相邻两项的值,然后相加算出下一项的值,从而实现线性时间复杂度。
四,利用矩阵相乘实现O(logn)复杂度的算法
根据斐波那契数列的递推公式,可以使用矩阵相乘的形式:

所以通过n - 1次矩阵乘法就能算出第n项和第n-1项的值,其中前n-2次矩阵乘法乘数都是同一个矩阵,我们
将子问题:求
分解为求该矩阵(n-1)/2次方的平方,再分解为(n - 1)/4次方的平方的平方...
如:
只需计算3次即可,所以时间复杂度为对数量级。
为实现该算法,需要一个矩阵类,能处理矩阵之间的乘法,并能获取指定元素,完整代码如下:
import java.util.ArrayList;
import java.util.Scanner;
public class MatrixTest{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int num = sc.nextInt();
System.out.println(matrixFib(num));
} public static long matrixFib(int num) {
if(num <= 1){
return num;
}
Matrix first = new Matrix(2, 2);
first.setElement(1, 1, 1);
first.setElement(1, 2, 1);
first.setElement(2, 1, 1);
first.setElement(2, 2, 0); Matrix result = new Matrix(2,1);
result.setElement(1, 1, 1);
result.setElement(2, 1, 0);//注意矩阵乘法的顺序是固定的,两个矩阵不能交换
//输入num代表第几项斐波那契数
int n = num - 1;//根据递推式求第num项,只需求first矩阵的num - 1次方
while(n > 0) {
if(n % 2 != 0) {
result = first.MultiMatri(result);
}
if((n /= 2) > 0)//当n/= 2 < 0 时,下次不会进入循环,所以没必要再运算一次
first = first.MultiMatri(first);
//n /= 2;//当输入规模足够大时,每次都判断的代价大于多做一次矩阵乘法
}
return result.getElement(1, 1);
}
}
class Matrix {
//成员变量:一个当作矩阵的二维数组
private int row;//当前矩阵的行数
private int col;//当前矩阵的列数
public ArrayList<ArrayList<Integer>> matrix;//二维数组用于保存矩阵
//传入行数和列数构造一个零矩阵
public Matrix(int row, int col) {
this.row = row;
this.col = col;
matrix = new ArrayList<ArrayList<Integer>>(row);
for(int i = 0;i < row;i++) {
ArrayList<Integer> list = new ArrayList<Integer>(col);
for(int j = 0;j < col;j++) {
list.add(0);
}
matrix.add(list);
}
}
public int getRow() {//获取矩阵行数
return row;
} public int getCol() {//获取矩阵列数
return col;
} public ArrayList<ArrayList<Integer>> getMatrix() //返回保存矩阵的二维数组
{
return matrix;
} //获取元素a[row][col]
public int getElement(int row,int col) {
return matrix.get(row - 1).get(col - 1);
}
//设置a[row][col] = value
public void setElement(int row,int col,int value) {
matrix.get(row - 1).set(col - 1, value);
}
//获取某一行向量的值
public ArrayList<Integer> getRow(int row){
return matrix.get(row - 1);
}
//获取某一列向量的值
public ArrayList<Integer> getCol(int col){
ArrayList<Integer> arrCol = new ArrayList<Integer>();
for(int i = 0;i < row;i++) {
arrCol.add(matrix.get(i).get(col - 1));
}
return arrCol;
}
//向量点乘
public int MultiVec(ArrayList<Integer> v1,ArrayList<Integer> v2)
{
if(v1.size() != v2.size()) {
return -1;
}
int result = 0;
for(int i = 0;i < v1.size();i++) {
result += (v1.get(i))* (v2.get(i));
}
return result;
}
//矩阵乘法,只有第一个矩阵的列数等于第二个矩阵的行数才能相乘
public Matrix MultiMatri(Matrix matri1) {
if(getCol() != matri1.getRow())
return null;
Matrix matri2 = new Matrix(getRow(),matri1.getCol());//新矩阵的行列
for(int i = 1;i <= getRow();i++) {
for(int j = 1;j <= matri1.getCol();j++) {
matri2.setElement(i, j, MultiVec(getRow(i),matri1.getCol(j)));
}
}
return matri2;
}
}
以上就是笔者总结的关于斐波那契数列的各种算法实现。
几种复杂度的斐波那契数列的Java实现的更多相关文章
- 剑指Offer - 九度1387 - 斐波那契数列
剑指Offer - 九度1387 - 斐波那契数列2013-11-24 03:08 题目描述: 大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项.斐波那契数列的定义如下: ...
- 【斐波那契数列】java探究
题目描述 大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0). n<=39 解析 (1)递归方式 对于公式f(n) = f(n-1) + f(n ...
- 斐波那契数列【java实现】
java 实现斐波那契数列 以下是Java代码实现(递归与递推两种方式): import java.util.Scanner; /** * Fibonacci * * @author tongqian ...
- 算法小节(一)——斐波那契数列(java实现)
看到公司的笔试题中有一道题让写斐波那契数列,自己忙里偷闲写了一下 什么是斐波那契数列:斐波那契数列指的是这样一个数列 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
- 斐波那契数列(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方法的调用过程
先看斐波那契数列的定义: 斐波那契数列(Fibonacci sequence),又称黄金分割数列.因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为 ...
- (转)从斐波那契数列看Java方法的调用过程
斐波那契数列的定义: 斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家列安纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔 ...
- 剑指offer第二版面试题10:斐波那契数列(JAVA版)
题目:写一个函数,输入n,求斐波那契数列的第n项.斐波那契数列的定义如下: 1.效率很低效的解法,挑剔的面试官不会喜欢 使用递归实现: public class Fibonacci { public ...
随机推荐
- flume 配置与使用
1.下载flume,解压到自建文件夹 2.修改flume-env.sh文件 在文件中添加JAVA_HOME 3.修改flume.conf 文件(原名好像不叫这个,我自己把模板名改了) 里面我自己配的( ...
- java"=="与equals()方法的对照
总结:String s=new String(); s是在堆内存里的 String s2=new String(); s2是在堆内存又重新生成的一个. package com.da; //逆向思维:i ...
- js基础之变量类型
1.NAN(Not a number) 不是一个数字 自身:console.log(NaN==NaN)和console.log(NaN===NaN)返回值都是false; 其他函数,isNaN()可用 ...
- Design:目录
ylbtech-Design:目录 1.返回顶部 1. http://idesign.qq.com/#!index/feed 2. https://www.behance.net/ 3. 2.返回顶部 ...
- UML核心元素--边界
定义:边界是无形的,是可大可小的,同时参与者.用例和边界又有着相生相克的性质.与其说边界是UML元素,还不如说它是一种分析方法. 1.需求是动态的过程:系统边界是无形的,看不到的,不好理解,倒不如说需 ...
- jquery easyui 推荐博客 (MVC+EF+EasyUI+Bootstrap)
构建ASP.NET MVC5+EF6+EasyUI 1.4.3+Unity4.x注入的后台管理系统(52)-美化EasyUI皮肤和图标 系列目录 我很久以前就想更新系统的皮肤功能,Easyui 自 ...
- linux docket
什么是 Docker Docker 最初是 dotCloud 公司创始人 Solomon Hykes 在法国期间发起的一个公司内部项目,它是基于 dotCloud 公司多年云服务技术的一次革新,并于 ...
- 树莓派 Learning 002 装机后必要的操作 --- 09 root用户 密码
树莓派 装机后必要的操作 - root用户 密码 我的树莓派型号:Raspberry Pi 2 Model B V1.1 装机系统:NOOBS v1.9.2 树莓派使用的Linux是debian系统, ...
- Ubuntu使用crontab 使用举例
除了这些固定值外,还可以配合星号(*),逗号(,),和斜线(/)来表示一些其他的含义: 星号 表示任意值,比如在小时部分填写 * 代表任意小时(每小时) 逗号 ...
- 转:基于InfluxDB&Grafana的JMeter实时性能测试数据的监控和展示
本文主要讲述如何利用JMeter监听器Backend Listener,配合使用InfluxDB+Grafana展示实时性能测试数据 关于JMeter实时测试数据 JMeter从2.11版本开始,命令 ...