03丨上:如何分析统计算法的执行效率和资源消耗?
概述
数据结构和算法本身解决的是“快”和“省”的问题,即如何让代码运行得更快,如何让代码更省存储空间。
时间、空间复杂度分析是整个算法学习的精髓,必须掌握。
为什么需要复杂度分析
事后统计法:代码跑一遍通过统计、监控,就能得到算法执行的时间和占用的内存大小。
缺点:(1)测试结果依赖环境
(2)需要具体的测试数据。
我们需要一个不用具体的测试数据来测试,就可以粗略地估计算法的执行效率的方法。
大O复杂度表示法
所有代码的执行时间 T(n) 与每行代码的执行次数 n 成正比。即:
其中:
表示代码执行时间;表示数据规模的大小;表示每行代码执行次数的总和。公式中的 O,表示代码的执行时间 与表达式成正比。
实例:
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;
}
}
}
设每个语句的执行时间是unit_time,此时:。
大 O 时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以,
也叫作渐进时间复杂度(asymptotic time complexity),简称时间复杂度。
时间复杂度分析
1. 只关注循环执行次数最多的一段代码
我们在分析一个算法、一段代码的时间复杂度的时候,只关注循环执行次数最多的那一段代码就可以了。
2. 加法法则:总复杂度等于量级最大的那段代码的复杂度
平行用加法法则
注意:只要是一个常量的执行时间,与n的规模无关,无论它多大,我们都当做.
3. 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
嵌套用乘法法则
几种常见时间复杂度实例分析
复杂度量级
粗略分为:多项式量级和非多项式量级。其中,非多项式量级只有两个:和。
1、
只要代码的执行时间不随n的增大而增长,这样时间代码的复杂度我们都记作。或者说,一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行代码,其时间复杂度也是。
2、、
对数阶时间复杂度非常常见,同时也是最难分析的一种时间复杂度。
计算一下两个代码的时间复杂度:
(1)
i=1;
while (i <= n) {
i = i * 2;
}
(2)
i=1;
while (i <= n) {
i = i * 3;
}
(1)为,(2)为,但实际上,我们把所有对数阶的时间复杂度都记为。我们知道,对数之间是可以互相转换的,log3n 就等于log32 * log2n,所以 O(log3n) = O(C * log2n),其中 C=log32 是一个常量。基于我们前面的一个理论:在采用大 O 标记复杂度的时候,可以忽略系数,即 O(Cf(n)) = O(f(n))。
3、O(m+n)、O(m*n)
代码的复杂度由两个数据的规模来决定。
int cal(int m, int n) {
int sum_1 = 0;
int i = 1;
for (; i < m; ++i) {
sum_1 = sum_1 + i;
}
int sum_2 = 0;
int j = 1;
for (; j < n; ++j) {
sum_2 = sum_2 + j;
}
return sum_1 + sum_2;
}
O(m+n)并行结构
O(m*n)嵌套结构
空间复杂度分析
空间复杂度的全称是渐进空间复杂度(asymptotic space complexity),表示算法的存储空间与数据规模之间的增长关系。
常见的空间复杂度就是O(1)、O(n)、O(n2 ),像 O(logn)、O(nlogn) 这样的对数阶复杂度平时都用不到。
常见时间复杂度O(1)、O(logn)、O(n)、O(nlogn)、O(n2 )。
04丨复杂度分析(下):浅析最好、最坏、平均、均摊时间复杂度
最好情况时间复杂度就是,在最理想的情况下,执行这段代码的时间复杂度。
最坏情况时间复杂度就是,在最糟糕的情况下,执行这段代码的时间复杂度。
平均情况时间复杂度
要考虑到执行结果的每种情况,并且计算出每种情况出现的概率。
最终计算出来的平均时间复杂度的值就是概率论中的加权平均值,也叫作期望值,所以平均时间复杂度的全称应该叫加权平均时间复杂度或者期望时间复杂度。
均摊时间复杂度
在代码执行的所有复杂度情况中绝大部分是低级别的复杂度,个别情况是高级别复杂度且发生具有时序关系时,可以将个别高级别复杂度均摊到低级别复杂度上(摊还分析法)。基本上均摊结果就等于低级别复杂度。