(一)开发环境搭建
(二)斐波那契数
(三)算法的评估
(四)时间复杂度的估算
(五)大O表示法
(六)斐波那契数复杂度分析
(七)leetcode
题目:求第n个斐波那契数(fibonacci number)
分析:斐波那契数就是下一个数等于前面两个数相加,如:0 1 1 2 3 5 8 13 …
我们先用递归(自己调用自己)实现,如下:
public static int fib1(int n) {
if (n <= 1) return n;
return fib1(n - 1) + fib1(n - 2);
}
public static void main(String[] args) {
System.out.println(fib1(0));
System.out.println(fib1(1));
System.out.println(fib1(2));
System.out.println(fib1(3));
System.out.println(fib1(4));
}
效果如下:
看似没有问题,但是当n比较大的时候,就有问题了,如下:
可以看到已经卡死了,CPU的风扇狂转,我们换一种算法试试
分析:
n: 0 1 2 3 4 5
fib(n):0 1 1 2 3 5 8 13 ...
当n=2时,需要相加运算一次
当n=3时,需要相加运算两次
…
所以我们需要相加n-1次
这种算法其实就是迭代,如下:
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) {
System.out.println(fib2(64));
}
效果如下:
我们使用TimeTool工具类来对比两种算法的运行时间,如下:
package com.zzq;
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("-------------------------------------");
}
}
public static void main(String[] args) {
int n = 40;
TimeTool.check("递归", new TimeTool.Task() {
@Override
public void execute() {
System.out.println(fib1(n));
}
});
TimeTool.check("迭代", new TimeTool.Task() {
@Override
public void execute() {
System.out.println(fib2(n));
}
});
}
代码的长短并不能反映代码的性能,如下:
分析:fib1用的是递归,代码量比较少,但是性能远不如fib2的迭代
如果单从执行效率上进行评估,可能会想到这么一种方案:比较不同算法对同一组输入的执行处理时间,这种方案也叫做:事后统计法
但是这种方法有明显的缺点:
一般从以下维度来评估算法的优劣:
public static void test1(int n) {
// 1(因为下面三个语句只会执行其中一个)
if (n > 10) {
System.out.println("n > 10");
} else if (n > 5) { // 2
System.out.println("n > 5");
} else {
System.out.println("n <= 5");
}
// 1 + 4 + 4 + 4
// 13
for (int i = 0; i < 4; i++) {
System.out.println("test");
}
// 14
}
public static void test2(int n) {
// 1 + 3n
for (int i = 0; i < n; i++) {
System.out.println("test");
}
}
public static void test3(int n) {
// 1 + 2n + n * (1 + 3n)
// 1 + 2n + n + 3n^2
// 3n^2 + 3n + 1
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
System.out.println("test");
}
}
}
public static void test4(int n) {
// 1 + 2n + n * (1 + 45)
// 1 + 2n + 46n
// 48n + 1
for (int i = 0; i < n; i++) {
for (int j = 0; j < 15; j++) {
System.out.println("test");
}
}
}
public static void test5(int n) {
//执行过程中n的变化: 8 (4 2 1) 0 , 括号中的有效
//就是看除多少次小于或等于0
// 8 = 2^3
// 16 = 2^4
// 3 = log2(8)
// 4 = log2(16)
// 执行次数 = log2(n)
while ((n = n / 2) > 0) {
System.out.println("test");
}
}
public static void test6(int n) {
// log5(n)
while ((n = n / 5) > 0) {
System.out.println("test");
}
}
public static void test7(int n) {
// 1 + 2*log2(n) + log2(n) * (1 + 3n)
// 1 + 3*log2(n) + 2 * nlog2(n)
for (int i = 1; i < n; i += i) { // i+=i 相当于 i=i*2 , 就是看乘多少次大于或等于n , n=1*2*2*2.... , 也是log2n的关系
// 1 + 3n
for (int j = 0; j < n; j++) {
System.out.println("test");
}
}
}
public static void test10(int n) {
// 1+3n
int a = 10;
int b = 20;
int c = a + b;
int[] array = new int[n];
for (int i = 0; i < array.length; i++) {
System.out.println(array[i] + c);
}
忽略常数、系数、低阶
9 >> O(1)
2n + 3 >> O(n)
n2 + 2n + 6 >> O(n2)
4n3 + 3n2 + 22n + 100 >> O(n3)
对数阶的细节
log2n = log29 ∗ log9n
所以 log2n 、log9n 统称为 logn
复杂度排序:O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)
可以借助函数生成工具对比复杂度的大小:https://zh.numberempire.com/graphingcalculator.php
对之前的代码用大O表示法估算时间复杂度
public static void test1(int n) {
// 1(因为下面三个语句只会执行其中一个)
if (n > 10) {
System.out.println("n > 10");
} else if (n > 5) { // 2
System.out.println("n > 5");
} else {
System.out.println("n <= 5");
}
// 1 + 4 + 4 + 4
// 13
for (int i = 0; i < 4; i++) {
System.out.println("test");
}
// 14
// O(1)
}
public static void test2(int n) {
// 1 + 3n
// O(n)
for (int i = 0; i < n; i++) {
System.out.println("test");
}
}
public static void test3(int n) {
// 1 + 2n + n * (1 + 3n)
// 1 + 2n + n + 3n^2
// 3n^2 + 3n + 1
// O(n^2)
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
System.out.println("test");
}
}
}
public static void test4(int n) {
// 1 + 2n + n * (1 + 45)
// 1 + 2n + 46n
// 48n + 1
// O(n)
for (int i = 0; i < n; i++) {
for (int j = 0; j < 15; j++) {
System.out.println("test");
}
}
}
public static void test5(int n) {
//执行过程中n的变化: 8 (4 2 1) 0 , 括号中的有效
//就是看除多少次小于或等于0
// 8 = 2^3
// 16 = 2^4
// 3 = log2(8)
// 4 = log2(16)
// 执行次数 = log2(n)
// O(logn)
while ((n = n / 2) > 0) {
System.out.println("test");
}
}
public static void test6(int n) {
// log5(n)
// O(logn)
while ((n = n / 5) > 0) {
System.out.println("test");
}
}
public static void test7(int n) {
// 1 + 2*log2(n) + log2(n) * (1 + 3n)
// 1 + 3*log2(n) + 2 * nlog2(n)
// O(nlogn)
for (int i = 1; i < n; i += i) { // i+=i 相当于 i=i*2 , 就是看乘多少次大于或等于n , n=1*2*2*2.... , 也是log2n的关系
// 1 + 3n
for (int j = 0; j < n; j++) {
System.out.println("test");
}
}
}
public static void test10(int n) {
// 1+3n
// O(n)
int a = 10;
int b = 20;
int c = a + b;
int[] array = new int[n];
for (int i = 0; i < array.length; i++) {
System.out.println(array[i] + c);
}
public static void test10(int n) {
// O(n)
int a = 10;
int b = 20;
int c = a + b;
int[] array = new int[n];
for (int i = 0; i < array.length; i++) {
System.out.println(array[i] + c);
}
}
分析:n为多少就向堆空间申请多少个int类型,所以空间复杂度是O(n)
迭代法是O(n)
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 int fib1(int n) {
if (n <= 1) return n;
return fib1(n - 1) + fib1(n - 2);
}