一、复杂度理解
-
复杂度是数量级 (方便记忆、推广),不是具体的数字
-
程序执行时需要的计算量和内存空间(和代码是否简洁无关)
-
一般针对一个具体的算法,而非一个完整的系统
一般分为空间复杂度和时间复杂度。
二、空间复杂度
一个程序的空间复杂度是指该程序的运行所需内存的大小。
空间复杂度也是一个数学表达式,是对一个算法在运行过程种
1.为什么对空间复杂度感兴趣?
- 如果一个程序要运行在一个多用户计算机系统中,那我们需要指明该程序所需内存的大小。
- 在任何一个计算机系统上运行程序,都需要知道是否有足够的内存可以来运行该程序。
- 一个问题可能有若干个解决方案,它们对内存的需求各个不同。比如,对于某些计算机来说,一个C++编译器仅需要1MB的空间,而另一个C++编译器则需要4MB的空间。如果某些计算机的内存少于4MB,则只能选择1MB的C++编译器。如果较小的编译器和较大的编译器有着同样的作用,那么即使用户的计算机有更多内存,也会选择较小的编译器,以便把更多的内存留作他用。
- 利用空间复杂度,我们可以估算一个程序所能解决的问题最大可以是什么规模。比如,一个电路模拟程序要模拟一个具有x个元件和y个连线的电路,需要10^6+100(c+w)个字节的内存。如果可用内存的总量是5.01×10^8字节,那么最大可以模拟c+w≤5 000 000的电路。
2.空间复杂度的组成
指令空间(instruction space)
指令空间是指编译之后的程序指令所需要的存储空间。
指令空间的数量取决于如下因素:
- 把程序转换成机器代码的编译器。
- 在编译时的编译器选择。
- 目标机器。
在决定最终代码需要多少空间的时候,编译器是一个最重要的因素。图1-1是计算表达式的三段可以的代码,他们所需要的空间不一样。每一个代码都能由相应的编译器产生。
LOAD | a | LOAD | a | LOAD | a | ||
ADD | b | ADD | b | ADD | b | ||
STORE | t1 | STORE | t1 | STORE | t1 | ||
LOAD | b | SUB | c | SUB | c | ||
MULT | c | DIV | t1 | DIV | t1 | ||
STORE | t2 | STORE | t2 | STORE | t2 | ||
LOAD | t1 | LOAD | b | LOAD | b | ||
ADD | t2 | MUL | c | MUL | c | ||
STORE | t3 | STORE | t3 | ADD | t2 | ||
LOAD | a | LOAD | t1 | ADD | t1 | ||
ADD | b | ADD | t3 | ADD | 4 | ||
SUB | c | ADD | t2 | ||||
STORE | t4 | ADD | 4 | ||||
LOAD | a | ||||||
ADD | b | ||||||
STORE | t5 | ||||||
LOAD | t4 | ||||||
DIV | t5 | ||||||
STORE | t6 | ||||||
LOAD | t3 | ||||||
ADD | t6 | ||||||
ADD | 4 | ||||||
A | ) | B | ) | C | ) |
即使采用相同的编译器,编译后的程序代码也可能不同。例如,一个编译器可能具备优化选项,如代码大小的优化和执行时间的优化等。在非优化模式下,编译器产生的是图1-1B)的代码。在优化模式下,编译器可能利用知识而产生了图1-1C)所示更快、更高效的代码。使用优化模式会增加程序编译的时间。
一个程序还可能需要其他的额外的空间,如t1、t2...临时变量所占用的空间。
数据空间(date space)
对于各种数据类型,C/C++语言并没有指定他们的空间大小,只是大多数C/C++编译器有相应的空间分配,例如int,char...数据类型所占空间大小不同,范围也不同。
一个结构变量的空间大小是每个结构成员所需的空间大小之和。类似的,一个数组的空间大小是数组的长度乘以一个数组元素的空间大小。
类型 | 空间大小(字节数) | 范围 |
bool | 1 | {true,false} |
char | 1 | [-128,127] |
unsigned char | 1 | [0,255] |
short | 2 | [-32768,32767] |
unsigned short | 2 | [0,65535] |
int | 4 | [-2^31,2^21-1] |
float | 4 | ±3.4E±38(7位) |
double | 8 | ±1.7E±308(15位) |
考虑如下数组声明:
double a[100];
int b[rows][cols];
当计算分配给一个数组的空间时,我们只关心分配给数组元素的空间。数组a的空间是100个double类型元素所占用的空间。若每个元素空间是8个字节,则数组a的空间是800个字节。
三、时间复杂度
一、时间复杂度的组成
时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。一个算法执行所耗费的时间,从理论上说,是不能算出来的,只有我们把程序放在机器上跑起来,才能知道。
但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个分析方式。一个算法所花费的时间与其中语句的执行次数成正比例。算法中的基本操作的执行次数,为算法的时间复杂度。
即:找到某条基本语句与问题规模N之间的数学表达式,就是算出了该算法的时间复杂度。
二、操作计数
估算一个程序或者函数的时间复杂度,一种方法是选择一种或多种关键操作 ,例如加、减...然后确定每一种操作的执行次数。
例一
此函数的表达式可以表示为:
实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法。
渐近表达式即为取表达式决定的因素(或取最高阶的项)
取决定的因素为,因此Func1的时间复杂度为。
例二
此函数的表达式可以表示为:
但是Func2的时间复杂度为,这是因为当N取无穷时2N和N并无区别。
常见的时间复杂度量级
常数阶O(1)
对数阶O(logN)
线性阶O(n)
线性对数阶O(nlogN)
平方阶O(n2)
立方阶O(n3)
K次方阶O(nk)
指数阶(2n)
上面从上至下依次的时间复杂度越来越大,执行的效率越来越低。