斐波那契数与复杂度相关知识学习与理解
斐波那契数
斐波那契数 ,我们在高中的时候就学过,但是总所周知我们学过的都给了老师
斐波那契数列,又称黄金分割数列或兔子数列,该数列为0、1、1、2、3、5、8、13、21、34、…,可以看到它的性质是前两项之和等于后一项。
其它相关的一些知识自行百度进行理解就好.我们说一下,网上大部分的解题思路
第一种 递归算法
/**
* 0 1 1 2 3 5 8 13 ...
* 下面的数是前面两个数的相加
* 0+1 = 1 1+2 =3 3+2 =5
* 求第N个斐波那契数的算法
*/
//思路 1 递归
private static int fib(int n) {
if (n <= 1) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
//思路二,赋值
private static int fib2(int n) {
//思路 1 递归
if (n <= 1) {
return n;
}
int first = 0;
int second = 1;
//9-10 很多人不懂这里为什么会是n-1 讲道理因为我们的数是从 0 开始的
for (int i = 0; i < n - 1; i++) {
int sum = first + second;
first = second;
second = sum;
}
return second;
}
public static void main(String[] args) {
// for (int i = 0; i < 10; i++) {
// System.out.println(i+"result="+fib(i));
// }
//死 循环
// System.out.println(fib2(30));
//测试消耗多长时间
int n = 15;
Times.test("fib2",new Times.Task(){
@Override
public void execute() {
System.out.println(fib2(n));
}
});
}
网上大部分都是说第二种好但是却从来没有人问说为什么第二种为什么会执行这么快? 其实当n=46的时候就已经很费时间了,
我们来看看下面这个这个是测试类:
package cn.leecode;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Times {
private static final SimpleDateFormat fmt = new SimpleDateFormat("HH:mm:ss.SSS");
public interface Task {
void execute();
}
public static void test(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("-------------------------------------");
}
}
进过测试我们得知,第二种算法明显快很多倍,那么为什么呢? 我们来看看下面这张示意图(递归算法)
但是问题来了,为什么这个东西执行会这么慢呢?
我们看一下图解的递归方法的
图片中我们假设我们传入的数字是6 可以明显的看到,在它本身的递归操作中有很多,重复的运算,也就是说,当传入6的时候,它会先执行bib(5)这个方法, 然后在依次执行
具体的执行顺序 请看这位博主
我们可以看到使用不同算法,解决同一个问题,效率可能相差非常大
如何评判一个算法的好坏?
◼ 如果单从执行效率上进行评估,可能会想到这么一种方案
比较不同算法对同一组输入的执行处理时间
这种方案也叫做:事后统计法
◼ 上述方案有比较明显的缺点
执行时间严重依赖硬件以及运行时各种不确定的环境因素 必须编写相应的测算代码
测试数据的选择比较难保证公正性
◼ 一般从以下维度来评估算法的优劣
正确性、可读性、健壮性(对不合理输入的反应能力和处理能力)
时间复杂度(time complexity):估算程序指令的执行次数(执行时间) 空间复杂度(space complexity):估算所需占用的存储空间
复杂度是什么?
对于复杂度我们一般采用大O表示法(Big O)
复杂度具体看这里
好我们在看一些简单的例子:
很明显上面的代码执行了 1+4+4+4 次,但是它是一个常数,因此用 O(1) 来表示
public static void test2(int n) {
// O(n)
// 1 + 3n
for (int i = 0; i < n; i++) {
System.out.println("test");
}
}
这段呢? 因为我们不确定 N是多少个数,所以用大O 表示就是 O(n)
◼ 忽略常数、系数、低阶
9 >> O(1) 如果具体的次数 是常数, 那么就这样表示
2n + 3 >> O(n)
n2 + 2n + 6 >> O(n2) 如果有次方的话,选最大的次方数,也就是O(n2)
4n3 + 3n2 + 22n + 100 >> O(n3) 写法上,n3 等价于 n^3 O(n3)
O(n^2例子)
public static void test3(int n) {
// 1 + 2n + n * (1 + 3n)
// 1 + 2n + n + 3n^2
// 3n^2 + 3n + 1
// O(n^2)
// O(n)
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
System.out.println("test");
}
}
}
他们的关系是
O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)
回到我们的斐波那契数我们来简单算算它的复杂度
我们先来看第二种算法 :
那么第一种呢?
public static int fib1(int n) {
if (n <= 1) return n;
return fib1(n - 1) + fib1(n - 2);
}
我们可以发现 它中间有一个 + 号,如果 n 是5 那么 这个+ 号就执行了5次
也就是说,它最终执行了多少次,要看这个fib1 这个函数执行了多少次,
那么它执行了多少次呢 ? 我们看一下示意图 :
fib(5) 执行一次 + fib(3) 执行2次 + fib(fib1) 执行8 次 = 2的0次 换算下来 是 0.5 * 2的n次方 -1 常数忽略不计 所以最后 算出 复杂度
这就是 为什么递归算法执行那么慢的原因