时间复杂度和空间复杂度基础

时间复杂度

时间复杂度有好多种定义方式,什么big O,small o,big Omega,small omega,balabala,首先先了解几个概念:
T ( n ) T(n) T(n)是Every-case time complexity,就是用来定义一个算法对于大小为 n n n的对象所做的运算次数。
T ( n ) T(n) T(n)就还有 W ( n ) W(n) W(n),W代表worst; B ( n ) B(n) B(n),B代表best; A ( n ) A(n) A(n),A代表average等等。

Big O

着重介绍以下Big O的定义,和如何用定义证明例子

定义

在这里插入图片描述
看起来很绕口,其实关键点就是 c f ( n ) cf(n) cf(n)是一个上限, c c c是个正的常数, n n n也是个大于等于某个非负数 N N N的常数。

例子

一、证明 n 2 + 10 n ∈ O ( n 2 ) n^2+10n \in O(n^2) n2+10nO(n2)
n 2 + 10 n ≤ n 2 + 10 n 2 = 11 n 2 n^2+10n \le n^2+10n^2 = 11n^2 n2+10nn2+10n2=11n2
通过上面的定义, c = 11 , N = 1 c=11, N=1 c=11,N=1,我们就证明了 n 2 + 10 n ∈ O ( n 2 ) n^2+10n \in O(n^2) n2+10nO(n2)
二、判断 2 2 n ∈ O ( 2 n ) 2^{2n} \in O(2^n) 22nO(2n)对不对
2 2 n = 2 n 2 n ≤ c 2 n 2 n ≤ c 2^{2n} = 2^n 2^n \le c2^n 2^n \le c 22n=2n2nc2n2nc
我们得找一个正常数 c c c,但这里 n n n不确定,所以就找不到啦。因此,通过定义证明了 2 2 n ∈ O ( 2 n ) 2^{2n} \in O(2^n) 22nO(2n)是错的(其实一眼就看得出来是属于 O ( 4 n ) O(4^n) O(4n))。当题目比较复杂时,我们也可以将式子转换为求极限再借助洛必达法则来求解。
l i m n → ∞ g ( n ) f ( n ) = { c i m p l i e s g ( n ) ∈ Θ ( f ( n ) ) i f c > 0 0 i m p l i e s g ( n ) ∈ o ( f ( n ) ) z i m p l i e s g ( n ) ∈ ω ( f ( n ) ) lim_{n \rightarrow \infty} \frac{g(n)}{f(n)}=\left\{ \begin{aligned} c \quad implies \quad g(n) \in \Theta(f(n)) \quad if \quad c > 0 \\ 0 \quad implies \quad g(n) \in o(f(n)) \\ z \quad implies \quad g(n) \in \omega(f(n)) \end{aligned} \right. limnf(n)g(n)=cimpliesg(n)Θ(f(n))ifc>00impliesg(n)o(f(n))zimpliesg(n)ω(f(n))

其他

Big O是最常用的,还有其他的一些定义比如big Omega ( Ω ( n ) \Omega(n) Ω(n)), big Theta ( Θ ( n ) \Theta(n) Θ(n)), small o( o ( n ) o(n) o(n)), small omega ( ω ( n ) \omega(n) ω(n))等等。我们就用下面的这个例子大概讲一讲他们的关系,这样可以更好的理解。
在这里插入图片描述
上面的图片中括号内的 n 2 n^2 n2其实就是f(n),然后将下列 O ( n ) , Ω ( n ) , Θ ( n ) , o ( n ) , ω ( n ) O(n), \Omega(n), \Theta(n), o(n), \omega(n) O(n),Ω(n),Θ(n),o(n),ω(n)分别代入上面big O的定义来看,就可以很好的理解了。
O ( n ) : g ( n ) ≤ c f ( n ) O(n): g(n) \le cf(n) O(n):g(n)cf(n)(存在c (exsit some positive real constant c c c))
Ω ( n ) : g ( n ) ≥ c f ( n ) \Omega(n): g(n) \ge cf(n) Ω(n):g(n)cf(n)(存在c)
Θ ( n ) : c f ( n ) ≤ g ( n ) ≤ d f ( n ) \Theta(n): cf(n) \le g(n) \le df(n) Θ(n):cf(n)g(n)df(n) (也就是 Θ ( f ( n ) ) = O ( f ( n ) ) ∩ Ω ( f ( n ) ) \Theta(f(n)) = O(f(n)) \cap \Omega(f(n)) Θ(f(n))=O(f(n))Ω(f(n)))(存在c)
o ( n ) : g ( n ) ≤ c f ( n ) o(n): g(n) \le cf(n) o(n):g(n)cf(n)(任意c (every positive real constant c c c))
ω ( n ) : g ( n ) ≥ c f ( n ) \omega(n): g(n) \ge cf(n) ω(n):g(n)cf(n)(任意c)

两大法则

那么如何用判断一段代码的时间复杂度(一般指big O)呢?这里有三大法则。

1. 除了执行最多次的代码,别的统统忽略

例如:

void func(int n){
	int a = 0;
	int b = 1;
	for(int i = 0; i < n; i++){
		a += i;
		b += a;
	}
	for(int i = 0; i < n; i++){
		for(int j = 0; j < n; j++){
			cout << a;
		}
		cout << b << endl;
	}
}

这段代码很明显就可以看出两个for loop执行的代码次数最多,所以只关注它即可。这里也就是 O ( n 2 ) O(n^2) O(n2)

2. 有嵌套的,就把它们乘起来

例如:

void for_loop(int a, int n){
	for(int j = 0; j < n; j++){
		cout << a;
	}
}
void func(int n){
	int a = 0;
	int b = 1;
	for(int i = 0; i < n; i++){
		a += i;
		b += a;
	}
	for(int i = 0; i < n; i++){
		for_loop(a, n);
		cout << b << endl;
	}
}

先用法则二将for_loop函数替换成上面的代码,其实和上面的例子一样,都是两个for loop,这时候只需要将他们的乘起来,也就是 n × n = n 2 n \times n = n^2 n×n=n2,就能得到答案 O ( n 2 ) O(n^2) O(n2)了。有一种整体法的数学思想在里面。

特殊例子

O(logn)是什么?

例如:

int sum = 1;
for(int i = 1; i <= n; i++){
	i *= 3
	sum += i
}
cout << sum << endl;

此段代码中,变量 i i i每次都是上次的3倍(3, 9 , 27…),假设执行次数为 x x x,我们可以列出这个等式 3 x = n 3^x = n 3x=n,化简得到 x = l o g 3 n x = log_3n x=log3n,我们一般都习惯用2为底的对数来表示,所以进一步化简得 x = l o g 3 2 ⋅ l o g 2 n x = log_32 \cdot log_2n x=log32log2n,总执行步骤次数就是 O ( l o g n ) O(logn) O(logn)了。

空间复杂度

空间复杂度远没有时间复杂度难算。计算空间复杂度只需要计算总的分配空间就可以了。
例如:

int* create_arr(int n){
	int* arr = new int[n];
	return arr;
}

此段代码就分配了 n n n大小的空间。

参考

  1. 两年前自己整理的笔记和老师的课件
  2. 《数据结构与算法分析——C语言描述》

猜你喜欢

转载自blog.csdn.net/skywuuu/article/details/114630975