1.为什么需要复杂度分析
虽然将代码执行⼀遍,通过统计、监控等手段就能得到算法执行时间和占用内存大小(有些书上将这种方法称为事后统计法),但是这种方法有2个局限性。
- 测试结果非常依赖测试环境。
- 测试结果受数据规模影响很大。
所以一般不采用这种方法,而是采用时间复杂度和空间复杂度两方面来表示算法复杂度。
2.大O时间复杂度的由来与表示方法
eg1.
int cal(int n){
int sum = 0;
int i = 1;
for (;i <= n;i++){
sum = sum + i;
}
return sum;
}
假设执行每行代码的时间是n,则总时间T(n) = 2n + 3(一个循环条件n次,循环结果n次,共2n次)
eg2.
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;
}
}
}
外部for循环执行了2n次,内部for循环执行了2n^2, 所以T(n) = 3+2n+2n^2
至此,我们可以总结出一个规律:所有代码的执行时间 T(n) 与每行代码的执行次数 n 成正比,可以写成一个公式:T(n) = O(f(n))。T(n)表示代码执行的总时间,n表示数据规模的大小,f(n)表示每段代码执行的次数之和,O表示T(n)与f(n)成正比。这就是大O时间复杂度表示法。
注:大O时间复杂度实际并不是代码时间执行时间,只是表示一种数据代码执行时间,随数据规模增长的变化趋势。所以也叫做渐进时间复杂度,简称:时间复杂度
3.时间复杂度如何分析
1.只关注循环次数最多的那一段代码
2.加法法则:总复杂度等于量级最大的那一段代码的复杂度
时间复杂度表示的是一种趋势,所以可以忽略掉低阶、常量、系数,只用关心最高阶即可。
所以eg1的时间复杂度为O(n),eg2为O(n^2)
3.乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂的乘积
eg3.
int cal(int n) {
int ret = 0;
int i = 1;
for (; i < n; ++i) {
ret = ret + f(i);
}
}
int f(int n) {
int sum = 0;
int i = 1;
for (; i < n; ++i) {
sum = sum + i;
}
return sum;
}
for(int i = 0;i < n;i++){
for(int j = 0; j < i;j++){}
}
这两种时间复杂度为O(n^2)
4.常见的的时间复杂度:
1.多项式时间复杂度:
除O(1),O(n),O(n^2)外常见的还有O(logn)、O(nlogn)、O(m + n)、O(m * n)
1.1 O(1):
只要代码不随n的增长而变化,即为这O(1)。
1.2 O(logn):
eg4.
int i = 1;
while(i <= n) {
i = i * 2;
}
i每循环一次就乘2,所以循环m次之后,2^m > n,则m = log ( n ) \log(n) log(n)次。
根据换底公式,所有对数形式的都可以换为以2为底的对数.
1.3 O(nlogn):
eg5.
int i = 1;
for(int i = 0;i < n;i++){
while(i <= n) {
i = i * 2;
}
}
1.4 O(m + n)
eg6.
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;
}
无法事先评估m与n的量级大小,所以无法直接省略掉一个,所以为(m + n)。
O(m * n) 同理。
2.非多项式时间复杂度:(这种情况较少,当n越来越大时,非多项式的时间复杂度急剧增加,因此非多项式的算法基本不用)
O(2^n)
O(n!)
5.最好,最坏,平均时间复杂度
eg7.
int find(int[] array, int n, int x) {
int i = 0;
int pos = -1;
for (; i < n; ++i) {
if (array[i] == x) {
pos = i;
break;
}
}
return pos;
}
最好情况:O(1)
最坏情况:O(n)
平均:共有(n + 1)种情况,n种在数组中,一种不在数组中,加权平均之后,时间复杂度还是为O(n)

也可以这样理解:最平均的话就是在最中间的那个位置,当n逐渐变大的时候,中间的次数也会变化,所以也是O(n)
6.空间复杂度分析
空间复杂度是对一个算法在运行临时占用存储空间大小的度量。
eg8.
public static int sum(int n) {
int count = 0;
for (int i = 1; i <= n; i++) {
count += i;
}
return count;
}
算法执行所需要的临时空间不随着某个变量n的大小而变化,即此算法空间复杂度为一个常量,可表示为S(n) = O(1)。
eg9.
int[] m = new int[n]
for(i=1; i<=n; ++i)
{
j = i;
j++;
}
这段代码中,第一行new出来一个数组之后占用的空间大小为n,,此后几行虽然有循环,但是没有再分配新的空间,因此这段代码的复杂度只用看第一行即可,S(n) = O(n)
附: