(参考:极客时间数据结构与算法专栏)
事后统计法:将代码运行一遍,通过统计、监控得到算法执行的时间和占用的内存大小。
事后统计法局限性:
1、非常依赖测试环境;
2、结果受数据规模的影响很大。
由于事后统计法有局限性,我们就需要不用具体的测试数据来测试,就能粗略估计算法执行
效率的方法,这就是大O复杂度表示法。
大O公式:
大O公式中T(n)它表示代码执行的时间,n 表示数据规模的大小,f(n) 表示每行代码执行次数
总和,O表示代码的执行时间 T(n) 与 f(n) 表达式成正比。大O公式表示代码执行时间随数据规模增
长的变化趋势,并不是代码执行的实际时间。
具体分析如下:
注:我们假设每行代码执行的时间都一样,为 unitTime。
private int cal(int n) {
int sum = 0;
int i = 1;
for (; i <= n; ++i) {
sum = sum + i;
}
return sum;
}
以上代码第 2、3 行代码分别需要 1 个 unitTime的执行时间,第 4、5 行都运行了 n 遍,需要
2n* unitTime的执行时间,这段代码总的执行时间就是 (2n+2)*unitTime,用公式表示即:
private 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 成正比,这个规律
也就是我们的大O公式。
前面是大 O 时间复杂度的由来和表示方法。那么如何分析一段代码的时间复杂度呢?主要有
三个方法:
1. 只关注循环执行次数最多的一段代码(取最大阶的量级)。例:
private int cal(int n) {
int sum = 0;
int i = 1;
for (; i <= n; ++i) {
sum = sum + i;
}
return sum;
}
这段代码中第 2、3 行代码都是常量级的执行时间,与 n 的大小无关,对于复杂度没有影响。
循环执行次数最多的是第 4、5 行代码,执行了 n 次,所以总的时间复杂度就是 O(n)。
2. 加法法则:总复杂度等于量级最大的那段代码的复杂度。例:
private int cal(int n) {
int sum_1 = 0;
int p = 1;
for (; p < 100; ++p) {
sum_1 = sum_1 + p;
}
int sum_2 = 0;
int q = 1;
for (; q < n; ++q) {
sum_2 = sum_2 + q;
}
int sum_3 = 0;
int i = 1;
int j = 1;
for (; i <= n; ++i) {
j = 1;
for (; j <= n; ++j) {
sum_3 = sum_3 + i * j;
}
}
return sum_1 + sum_2 + sum_3;
}
通过分析可以得出sum1的执行时间复杂度为常量(这段代码循环执行了 100 次,是一个常量的执行时间,跟 n 的规模无关。只要是一个已知的数,跟 n 无关,就是常量级的执行时间),
sum2的执行时间复杂度为O(n),sum3的执行时间复杂度为O(n^2),根据加法法则整段代码的时间
复杂度为O(n2)。抽象为公式即:
如果
,
那么
3. 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积。
类比加法法则可以得出乘法法则公式为:
如果
那么
具体的代码上,可以把乘法法则看成是嵌套循环。
常见的复杂度从低阶到高阶有:O(1)、O(logn)、O(n)、O(nlogn)、O(n2 )。