复杂度分析(上)

1. 大O复杂度表示法

先上代码举个例子

 int cal(int n) {   
 	int sum = 0;   
 	int i = 1;   
 	for (; i <= n; ++i) {    
 		 sum = sum + i;   
 	}   
 	return sum; 
 }

分析:我们假设每行代码执行的时间都一样,为unit_time,在这个假设的基础上,这段代码的总执行时间是多少呢?
第 2、3 行代码分别需要 1 个 unit_time 的执行时间,第 4、5 行都运行了 n 遍,所以需要 2n*unit_time 的执行时间,所以这段代码总的执行时间就是 (2n+2)*unit_time。可以看出来,所有代码的执行时间 T(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;     
	 			}   
	 	} 
	}

我们依旧假设每个语句的执行时间是unit_time,那么这段代码的总执行时间T(n)是多少呢?
第 2、3、4 行代码,每行都需要 1 个 unit_time 的执行时间,第 5、6 行代码循环执行了 n 遍,需要 2n * unit_time 的执行时间,第 7、8 行代码循环执行了 n 2 遍,所以需要 2n2 * unit_time的执行时间。所以整段代码总的执行时间为:T(n) = (2n2+2n+3) * unit_time。

通过这两个例子我们推导出一个规律

所有代码的执行时间 T(n) 与每行代码的执行次数 n 成正比。
把这个规律总结成一个公式,此时,大O就登场了~
T(n) = O ( f (n) )
具体解释一下这个公式:T(n)表示代码执行的时间,n表示数据规模的大小,f(n)表示每行代码执行的次数总和。公式中的O,表示代码的执行时间T(n)与f(n)表达式成正比。
大O时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以也叫作渐进时间复杂度,简称时间复杂度。

2. 时间复杂度分析

1. 只关注循环执行次数最多的一段代码

我们在分析一个算法、一段代码的时间复杂度的时候,只关注循环执行次数最多的那一段代码就可以了。

依然是分析之前的那段代码

 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. 加法法则:总复杂度等于量级最大的那段代码的复杂度

先上代码

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;
}

sum_1的时间复杂度跟n的规模无关。
sum_2的时间复杂度是O(n)。
sum_3的时间复杂度是O(n2)。
取最大的量级,整段代码的时间复杂度为O(n2)。
总的时间复杂度等于量级最大的那段代码的时间复杂度。
用公式表示:
如果T1(n) = O( f (n) ),T2(n) = O ( g(n) ),那么T(n) = T1(n) + T2(n) = max(O( f(n) ), O( g(n) ) ) = O( max(f(n), g(n)) )。

3.乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积

公式:如果 T1(n)=O( f(n) ),T2(n)=O( g(n) ),那么T(n) = T1(n) * T2(n) = O( f(n) ) * O( g(n) ) = O(f(n) * g(n))。

举个栗子

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; 
 }

假设f(n)只是一个普通的操作,那么cal()里面的for循环的时间复杂度就是T1(n) = O(n),但是f(n)的时间复杂度是T2(n) = O(n),所以整个cal()函数的时间复杂度是T(n) = T1(n) * T2(n) = O(n * n) = O( n 2)。

接下来是几种常见的多项式时间复杂度。

1. O(1)

O(1)只是常量级时间复杂度的表示方法,并不是只执行了一行代码。

举个时间复杂度为O(1)的栗子

int i = 2;
int j = 3;
int sum = i + j;

一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是O(1)。

2. O(logn),O(nlogn)

举个栗子

 i=1; 
 while (i <= n)  {   
 	i = i * 2; 
 }

此时间复杂度为O(logn)
在采用大O标记复杂度的时候,可以忽略系数,即O(Cf(n)) = O(f(n))。
如果一段代码的时间复杂度是O(f(n)),我们循环执行了n遍,时间复杂度就是O(nlogn)了。

3. O(m+n),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; 
}

这段代码的时间复杂度是O(m+n)。
针对这种情况,原来的加法法则则变为:T1(m)+T2(n)=O(f(n) + g(n))。但是乘法法则依然有效:T1(m)*T2(n) = O(f(n) * g(n))。

3. 空间复杂度分析

空间复杂度全称就是渐进空间复杂度,表示算法的存储空间与数据规模之间的增长关系。

举个栗子

void print(int n) {  
	int i = 0;  
	int[] a = new int[n];  
	for (i; i <n; ++i) {    
		a[i] = i * i;  
	}
 
  for (i = n-1; i >= 0; --i) {    
  	print out a[i]  
  	} 
 }

第二行代码中,申请了一个空间存储变量i,但是它是常量阶的,与数据规模n无关,第三行申请了一个大小为n的int型数组,除此之外剩下的代码都没有占多少空间,所以整段代码的空间复杂度是O(n)。
常见的空间复杂度就是O(1),O(n),O(n2),对数阶复杂度基本用不上。

4. 内容小结

复杂度也叫渐进复杂度,包括时间复杂度和空间复杂度,用来分析算法执行效率与数据规模 之间的增长关系,可以粗略地表示,越高阶复杂度的算法,执行效率越低。常见的复杂度并 不多,从低阶到高阶有:O(1)、O(logn)、O(n)、O(nlogn)、O(n2)。

你可能感兴趣的:(数据结构与算法,算法,数据结构)