「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」
思考:
常常有人说,我们项目之前都会进行性能测试,再做代码的时间复杂度、空间复杂度分析,是不是多此一举呢?而且,每段代码都分析一下时间复杂度、空间复杂度,是不是很浪费时间呢?你怎么看待这个问题呢?
第一个问题:
性能测试结果很大程度依赖于环境以及测试场景数据量,不同环境不同场景和数据量,结果会明显不同,不好抽象化来评估,而时间空间复杂度分析解耦于环境,可对其做抽象化评估。
第二个问题:
没重点全量分析的确性价比低,个人觉得核心功能核心代码做分析,非核心功能及核心代码可选择忽略不分析。
什么是复杂度分析?
- 数据结构和算法核心是‘快’和‘省’问题
- 如何量化考量指标:时间复杂度(快)+空间复杂度(省)
- 时间复杂度:全称是渐进时间复杂度,表示算法的执行时间与数据规模之间的增长关系。
- 空间复杂度:全称渐进空间复杂度(asymptotic space complexity),表示算法的存储空间与数据规模之间的增长关系。
为什么需要复杂度分析?
- 直接运行测试(事后统计法)测试结果非常依赖测试环境与数据规模,故需要一个不用具体的测试数据来测试,就可粗略估算执行效率的方法
如何进行时间复杂度分析
大O复杂度标识法
- 原理:
- 代码执行实际与每行代码执行次数成正比,用公式T(n)=O(f(n))表示,T(n)-代码执行的时间、n-表示数据规模、f(n)-每行代码执行次数总和,则公式O-表示代码执行时间T(n)与f(n)成正比。
- 表示代码执行时间随数据规模增长的变化趋势,公式中低阶、常量、系数并不左右增长趋势,故如T(n)=O(2n2+2n+3)==》T(n)=O(n2)
常见分析技巧
- 单段代码看高频:比如 循环
- 加法法则:多段取量级最大段
- 乘法法则:嵌套取内外乘积
常见复杂度量级
- 多项式量级
- 常量阶O(1):算法不存在循环、递归,均属于这类
- 对数阶/线性对数阶 O(logn)/O(nlogn):归并排序、快速排序
- 线性阶O(n)
- 平方阶O(n2)/立方阶O(n3)...k次方阶O(nk)
- O(m+n)/O(m*n):代码中有两段同量级复杂度,无法事先评估m和n的量级
// 对数阶 O(logn)
i=1;
while (i <= n){
i = i * 3;
}
// 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;
}
- 非多项式量级:NP(Non-Deterministic Polynomial,非确定多项式),随n增加执行时间会急剧增加,故效益非常低下。比如
- 指数阶O(2n)
- 阶乘阶O(n!)
空间复杂度
时间复杂度的全称是渐进时间复杂度,表示算法的执行时间与数据规模之间的增长关系。
空间复杂度全称就是渐进空间复杂度(asymptotic space complexity),表示算法的存储空间与数据规模之间的增长关系。
复杂度4个重要概念
有些情况下,复杂度和测试数据量级或者条件有关,不同条件下,复杂度会有明显区别,这时就需要我们考虑其它维度来表示,故引入这四个概念。
- 最好情况时间复杂度(best case time complexity)
- 最坏情况时间复杂度(worst case time complexity)
- 平均情况时间复杂度(average case time complexity):适用于每个情况下复杂度不一样场景表示,对每个复杂度情况做加权平均值计算,比如,数组中数据查找匹配。
- 均摊时间复杂度(amortized time complexity):适用于小概率性出现最坏情况复杂度的场景,将小概率最坏复杂度和全部复杂度情况均摊计算,一般情况均摊复杂度等于最好时间复杂度。如下数组的操作示例,触发扩容时复杂度O(n),未触发是O(1),可见一个O(n)伴随n-1个O(1),均摊后为O(1)~
// 全局变量,大小为10的数组array,长度len,下标i。
int array[] = new int[10];
int len = 10;
int i = 0;
// 往数组中添加一个元素
void add(int element) {
if (i >= len) { // 数组空间不够了
// 重新申请一个2倍大小的数组空间
int new_array[] = new int[len*2];
// 把原来array数组中的数据依次copy到new_array
for (int j = 0; j < len; ++j) {
new_array[j] = array[j];
}
// new_array复制给array,array现在大小就是2倍len了
array = new_array;
len = 2 * len;
}
// 将element放到下标为i的位置,下标i加一
array[i] = element;
++i;
}