1. 什么是算法
- 算法是用于解决特定问题的一系列的执行步骤,比如以下代码。
/**
* 计算 a 和 b 的和
*/
public static int plus(int a, int b) {
return a + b;
}
/**
* 计算 1 + 2 + 3 + ... + n
*/
public static int sum(int n) {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return sum;
}
- 使用不同算法,解决同一个问题,效率可能相差非常大。 比如下面的求和代码:
/**
* 计算 1 + 2 + 3 + ... + n
*/
public static int sum(int n) {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return sum;
}
/**
* 计算 1 + 2 + 3 + ... + n
*/
public static int sum2(int n) {
return (1 + n) * n / 2;
}
2. 如何评判一个算法的好坏
-
如果单从执行效率上进行评估,可能会想到这么一种方案
- 比较不同算法对同一组输入的执行处理时间
- 这种方案也叫做:事后统计法
-
上述方案有比较明显的缺点
- 执行时间严重依赖硬件以及运行时各种不确定的环境因素
- 必须编写相应的测算代码
- 测试数据的选择比较难保证公正性
-
一般从以下维度来评估算法的优劣
- 正确性、可读性、健壮性(对不合理输入的反应能力和处理能力)
- 时间复杂度(time complexity):估算程序指令的执行次数(执行时间)
- 空间复杂度(space complexity):估算所需占用的存储空间
3. 大O表示法(Big O)
- 一般用大O表示法来描述复杂度,它表示的是数据规模 n 对应的复杂度
- 忽略常数、系数、低阶
- 9 >> O(1)
- 2n + 3 >> O(n)
- n2 + 2n + 6 >> O(n2)
- 4n3 + 3n2 + 22n + 100 >> O(n3)
- 注意:大O表示法仅仅是一种粗略的分析模型,是一种估算,能帮助我们短时间内了解一个算法的执行效率
4. 对数阶的细节
对数阶一般省略底数 log2n = log29 ∗ log9n,所以 log2n 、log9n 统称为 logn
5. 常见的复杂度
执行次数 | 复杂度 | 非正式术语 |
---|---|---|
12 | O(1) | 常数阶 |
2n + 3 | O(n) | 线性阶 |
4n2 + 2n + 6 | O(n2) | 平方阶 |
4log2n + 25 | O(logn) | 对数阶 |
3n + 2nlog3n + 15 | O(nlogn) | nlogn阶 |
4n3 + 3n2 + 22n + 100 | O(n3) | 立方阶 |
2n | O(2n) | 指数阶 |
-
复杂度大小:
O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn) -
可以使用函数生成工具对比复杂度的大小:
复杂度对比工具 -
规模数据较小时
-
数据规模较大时
6. fib函数的时间复杂度分析
- 1 + 2 + 4 + 8 = 20 + 21 + 22 + 23 = 24 − 1 = 2n−1 − 1 = 0.5 ∗ 2n − 1
- 所以复杂度是 O(2n)
fib 算法比较:
/**
* 复杂度 O(2^n)
*/
public static int fib1(int n) {
if (n <= 1) {
return n;
}
return fib1(n - 2) + fib1(n - 1);
}
/**
* 复杂度 O(n) 5
*/
public static int fib2(int n) {
if (n <= 1) {
return n;
}
int first = 0, second = 1;
while (n-- > 1) {
second += first;
first = second - first;
}
return second;
}
- 传统递归方式复杂度是 O(2n)
- 改进后复杂度变为了O(n)
7. 算法的优化方向
- 用尽量少的存储空间
- 用尽量少的执行步骤(执行时间)
- 根据情况,可以
- 空间换时间
- 时间换空间
8. 多个数据规模的情况
下述代码复杂度为 O(n + k)
/**
* 时间复杂度 n + k
*/
public static void test(int n, int k) {
for (int i = 0; i < n; i++) {
System.out.println("test_n" + i);
}
for (int i = 0; i < k; i++) {
System.out.println("test_k" + i);
}
}