复杂度分析
> 数据结构和算法本身解决的是"快"(让代码运行的更快)和"省"(让代码更省存储空间)的问题;
> 执行效率是算法一个非常重要的考量指标;
> 如何衡量"执行效率" -> 时间, 空间复杂度;
> 复杂度分析是整个算法学习的精髓,只要掌握了它,数据结构和算法的内容基本上就掌握了一半;
大 O 复杂度表示法
- 大 O 时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以,也叫作渐进时间复杂度(asymptotic time complexity),简称时间复杂度;
- 当 n 很大时,你可以把它想象成 10000、100000。而公式中的低阶、常量、系数三部分并不左右增长趋势,所以都可以忽略。我们只需要记录一个 最大量级 就可以了;
1. T(n) -> 代码执行的时间;
2. n -> 数据规模的大小;
3. f(n) -> 每行代码执行的次数总和;
4. 大O -> 代码的执行时间 T(n) 与 f(n) 表达式成正比
详细示例 first (求 1,2,3…n 的累加和)
int cal(int n) {
int sum = 0;
int i = 1;
for (; i <= n; ++i) {
sum = sum + i;
}
// return语句一般不计算在内
return sum;
}
-
前提假设
假设每行代码执行的时间都一样,为 unit_time (实际的情况是每行代码对应的 CPU 执行的个数、执行的时间都不一样,但是,我们这里只是粗略估计,故有此假设)
-
代码执行时间计算(cal函数内部的代码)
code line execute time 2 1 unit_time 3 1 unit_time 4 n unit_time 5 n unit_time
Total Time: (2n+2)*unit_time => 代码的总执行时间 T(n) 与"每行代码的执行次数"(此处为
2n+2
)成正比
详细实例 second
int cal(int n) {
int sum = 0;
int i = 1;
int j = 1;
for (; i <= n; ++i) {
j = 1;
for (; j <= n; ++j) {
sum = sum + i * j;
}
}
}
-
代码执行时间计算(cal函数内部的代码)
code line execute time 2 1 unit_time 3 1 unit_time 4 1 unit_time 5 n unit_time 6 n unit_time 7 n² unit_time 8 n² unit_time
整段代码总的执行时间: T(n) = (2n2+2n+3)*unit_time
-
重要规律
尽管我们不知道 unit_time 的具体值,但是通过这两段代码执行时间的推导过程,我们可以得到一个非常重要的规律,那就是,所有代码的执行时间 T(n) 与每行代码的执行次数 n 成正比。将此规律总结成公式, 就得到大O表示法(second show up 🙂):
用大 O 表示法表示刚讲的那两段代码的时间复杂度,就可以记为:T(n) = O(n); T(n) = O(n²)
;
时间复杂度分析
方法1: 只关注循环执行次数最多的一段代码
根据: 大 O 这种复杂度表示方法只是表示一种变化趋势。我们通常会忽略掉公式中的常量、低阶、系数,只需要记录一个最大阶的量级就可以了
方法2: 分段取最大 - 总复杂度等于量级最大的那段代码的复杂度
将代码分成独立的几段, 分别计算各段的复杂度, 取量级最大的复杂度
方法3: 嵌套法则 - 嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
如果 T1(n)=O(f(n)),T2(n)=O(g(n));那么 T(n)=T1(n)*T2(n)=O(f(n))*O(g(n))=O(f(n)*g(n)).
常见的复杂度量级
- 多项式量级(由n作为底数)
- 非多项式量级(n不是作为底数, 上图只有两个:
O(2n)
和O(n!)
, 非常低效)
时间复杂度为非多项式量级的算法问题叫作 NP(Non-Deterministic Polynomial,非确定多项式)问题。
常见多项式时间复杂度
- O(1)
只要代码的执行时间不随 n 的增大而增长,这样代码的时间复杂度我们都记作 O(1)。或者说,一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是Ο(1)
- O(logn)、O(nlogn)
- 对数阶时间复杂度非常常见,同时也是最难分析的一种时间复杂度;
- 对数阶时间复杂度的表示方法里,我们忽略对数的“底”,统一表示为 O(logn);
- 如果一段代码的时间复杂度是 O(logn),我们循环执行 n 遍,时间复杂度就是 O(nlogn) 了。而且,O(nlogn) 也是一种非常常见的算法时间复杂度。比如,归并排序、快速排序的时间复杂度都是 O(nlogn);
- O(m+n)、O(m*n)
代码的复杂度由两个数据的规模来决定
空间复杂度分析
时间复杂度的全称是渐进时间复杂度,表示算法的执行时间与数据规模之间的增长关系。类比一下,空间复杂度全称就是渐进空间复杂度(asymptotic space complexity),表示算法的存储空间与数据规模之间的增长关系。