天天看点

【数据结构】算法复杂度分析

1.为什么需要复杂度分析

虽然将代码执行⼀遍,通过统计、监控等手段就能得到算法执行时间和占用内存大小(有些书上将这种方法称为事后统计法),但是这种方法有2个局限性。

  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)

附:

【数据结构】算法复杂度分析

继续阅读