关于斐波那契数列的想法
在大一的时候,初学c语言,我们就学过了关于斐波那契数列的计算,但是当时学的递归的方法计算起比较大的数值n,电脑会跑很久甚至都跑不动。
这次就用一个优化后的计算方法与递归方法作对比,通过一些例子学会计算时间复杂度之后,计算这两个算法的时间复杂度,看看算某些数值时需要用时多久。
两种算法演示效果
递归方法
public static int fib1(int n){//递归方法
if(n<=1) return n;
return fib1(n-1)+fib1(n-2);
}
递归的方法很简单,就是第n个斐波那契数是n-1个斐波那契数加上n-2个数,一直递归调用直到n=1与n=0时停下。
可以里看出,这里面存在着许多重复的调用,比如计算fib(5)时,fib(2)就调用了3次,这样大大不利于计算机的计算效率。
优化的算法
public static int fib2(int n){//优化的方法
if(n<=1) return n;
int first = 0;
int second = 1;
for (int i = 0; i < n-1; i++) {
int sum = first +second;
first = second;
second = sum;
}
return second;
}
*0 1 2 3 4 5 6 7 8//这里是n
*0 1 1 2 3 5 8 13 21//这是n对应的结果
每次结果都是两个数相加,当次的是上一次计算中的second加上这次的first。
我们设定最终输出的值是second,那么每一次计算时sum=first+second,sum用来存计算结果,first=second,这时候的second就应该成为下一次计算的first,那么second=sum,输出这次计算结果。
为了方便对比运行速度,我们把两个个方法的运行时间显示出来。
下面展示完整代码:
basic01.java
package com.hgq;
import com.hgq.TimeTool.Task;
/**
* @author 89241
*0 1 2 3 4 5 6 7 8
*0 1 1 2 3 5 8 13 21
*斐波那契数列
*/
public class basic01 {
public static int fib1(int n){//递归方法
if(n<=1) return n;
return fib1(n-1)+fib1(n-2);
}
public static int fib2(int n){//优化的方法
if(n<=1) return n;
int first = 0;
int second = 1;
for (int i = 0; i < n-1; i++) {
int sum = first +second;
first = second;
second = sum;
}
return second;
}
public static void main(String[] args) {//匿名类
// TODO 自动生成的方法存根
final int n=46;
TimeTool.check("fib1", new Task(){
public void execute(){//要执行的语句写在里面
System.out.println(fib1(n));
}
});
TimeTool.check("fib2", new Task(){
public void execute(){
System.out.println(fib2(n));
}
});
}
}
TimeTool.java
package com.hgq;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TimeTool {
private static final SimpleDateFormat fmt = new SimpleDateFormat("HH:mm:ss.SSS");
public interface Task{
void execute();
}
public static void check(String title,Task task){
if(task == null)return;
title = (title == null)? "" :("【" + title +"】");
System.out.println(title);
System.out.println("开始:"+ fmt.format(new Date()));
long begin = System.currentTimeMillis();
task.execute();
long end = System.currentTimeMillis();
System.out.println("结束:"+ fmt.format(new Date()));
double delta =(end - begin)/1000.0;
System.out.println("耗时"+ delta+"秒");
System.out.println("------------------");
}
}
演示结果
可以看出优化后的算法几乎不耗时,但是递归算法却花了将近13秒,那么这是为什么呢,怎么去判断它的耗时程度呢?这就需要掌握时间复杂度的计算问题了。
时间复杂度计算例子
时间复杂度大体意思是程序需要被执行多少次,或者说是执行的时间。
例1:
这里面if判断,虽然有三句,但是每次只挑其中一句,所以算1次,for循环中,int i=0算一次,判断i<4要执行4次,i++也要执行4次,那么相对应的输出语句也要4次,最后总的就是14次。
O(1)。
然后我们了解一下大O表示法:
效率对比:
数据规模较小时:
较大时:
所以这里的时间复杂度14->O(1)。
又因为只定义了int i,所以空间复杂度O(1).
例2:
这里同上,i=0算1次,剩下的每个都是n次,共3n,加起来就是3n+1,
用大O表示法就是:O(n)。
只定义了int i,所以空间复杂度O(1).
例3:
外循环:1+n+n,内循环:n*(1+3n),总的:3n^2+3n+1
时间复杂度O(n^2)。
例4:
外循环:1+2n,内循环:n*(1+15*3),总:48n+1
O(n)。
例5:
while循环类似if,我们直接算里面的语句执行的次数。如n=8,可以执行3次,n=16,可以执行4次,所以次数=log2(n)。
O(logn)
例6:
类似的,log5(n)
O(logn)
例7:
外循环:1+2log2(n),内循环:log2(n)(1+3n),总:2nlog2(n)+3*log2(n)+1
O(nlogn+logn)->O(nlogn) (因为低阶需要省略掉)。
比如n+logn->O(n)(这里面logn更低阶)。
例8:用来说明空间复杂度:
前3个都是定义常数,但后面定义了数组,n项,所以空间复杂度O(n)。
回到之前的两个算法
- 计算改良的算法的时间复杂度为:O(n)。
- 对于递归算法里的,每调用一次fib1就执行一次加法,所以要看最终调用了多少次这个函数,而前面给出了调用示例图,计算fib1(5),总调用1+2+4+8=15=2n-1-1= 0.5*2n-1,所以时间复杂度为O(2n).
- 回看前面的每个时间复杂度的曲线可以看出,这两个算法的时间复杂度在数据为几十时就已经体现出了巨大的差异。
- 下面来用实际例子大概说一下两个算法需要的时间:
所以算法好的话,能够节省非常多的时间。
平常开发时,可以在空间和时间的资源中权衡,选取最优。
补充:
这样的程序要考虑两个循环,最后时间复杂度为O(n+k)。
学会了这题就可以去leetcode-cn.com里去练习题目了,509题。
注:这是我用来记录学习数据结构与算法过程的博客,学习的材料是小码哥的恋上数据结构与算法,本文中的一些图片也是来源于他的教学。后续会继续发布后面的学习笔记。如有发现错误欢迎指出,下次上线一定修改,希望一起进步!