1、为什么要做复杂度分析?
数据结构和算法——解决“快”和“省”的问题,即如何让代码运行得更快,如何让代码更省存储空间;
事后统计分析法的局限性:
- 测试结果非常依赖测试环境(代码在机器上的运行环境);
- 测试结果受数据的规模影响很大。
因此,我们需要一个不用具体的测试数据来测试,就可以粗略地估计算法的执行效率的方法。
2、大O复杂度表示法:用“肉眼”估算代码的执行(读数据→运算→写数据)时间
核心:所有代码的执行时间 T(n) 与每行代码的执行次数 n 成正比。
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;
}
}
}
T(n)表示代码的执行时间,n表示数据规模的大小;f(n)表示每行代码执行的次数总和。
O表示代码的执行时间T(n) 与 f(n)成正比。→ 代码执行时间随数据规模增长的变化趋势
3、时间复杂度分析
- 只需关注“循环执行次数最多"的一段代码。
- 加法法则:总复杂度 = 量级最大的那段代码的复杂度;
- 乘法法则:嵌套循环 = 嵌套内外复杂度的乘积。
常见的时间复杂度实例分析:
多项式时间复杂度:
- 常量阶 O(1)
- 对数阶 O(log n)
- 线性阶 O(n)
- 线性对数阶 O(n log n)
- 平方阶、立方阶、k次方阶 O(n^k)
非多项式时间复杂度(Non-Deterministic Polynomial 非确定性多项式问题)——NP问题
- 指数阶 O(2^n)
- 阶乘阶 O(n!)
举例:O(log n)、O(n log n)
i=1;
while (i <= n) {
i = i * 2;
}
变量 i 每循环一次即乘以2, 当大于n时循环结束,其实际执行次数为
i=1;
k=0;
for (;k < n; ++k){
while (i <= n){
i = i * 3;
}
}
运用乘法法则,上述代码 的执行次数为
4、空间复杂度分析
渐进空间复杂度(asymptotic space complexity):算法的存储空间与数据规模之间的增长关系。
常见的空间复杂度为 O(1)、O(n)、O(n^2),即为变量的存储空间。
5、思考
项目之前的性能测试 与 代码的时间、空间复杂度分析的关系:
回答:摘选自”数据结构与算法之美“(侵删)
渐进时间,空间复杂度分析为我们提供了一个很好的理论分析的方向,并且它是宿主平台无关的,能够让我们对我们的程序或算法有一个大致的认识,让我们知道,比如在最坏的情况下程序的执行效率如何,同时也为我们交流提供了一个不错的桥梁,我们可以说,算法1的时间复杂度是O(n),算法2的时间复杂度是O(logN),这样我们立刻就对不同的算法有了一个“效率”上的感性认识。
当然,渐进式时间,空间复杂度分析只是一个理论模型,只能提供给粗略的估计分析,我们不能直接断定就觉得O(logN)的算法一定优于O(n), 针对不同的宿主环境,不同的数据集,不同的数据量的大小,在实际应用上面可能真正的性能会不同,个人觉得,针对不同的实际情况,进而进行一定的性能基准测试是很有必要的,比如在统一一批手机上(同样的硬件,系统等等)进行横向基准测试,进而选择适合特定应用场景下的最有算法。
综上所述,渐进式时间,空间复杂度分析与性能基准测试并不冲突,而是相辅相成的,但是一个低阶的时间复杂度程序有极大的可能性会优于一个高阶的时间复杂度程序,所以在实际编程中,时刻关心理论时间,空间度模型是有助于产出效率高的程序的,同时,因为渐进式时间,空间复杂度分析只是提供一个粗略的分析模型,因此也不会浪费太多时间,重点在于在编程时,要具有这种复杂度分析的思维。
尾注:第一次写一篇自己的blog,希望坚持下来,有更多自己的技术思考,love and share!