算法分析
时间复杂度
问题规模:问题规模是算法求解问题输入量的多少,是问题大小的本质表示,一般用整数n表示
语句频度:一条语句的重复执行次数称作语句频度
设每条语句执行一次所需的时间均是单位时间,则一个算法的执行时间可用该算法中所有语句频度之和来度量。
如下算法频度之和可以记为$f(n)$,$f(n)=n+1+n^2+n+n^2+1$,该算法的执行时间与$f(n)$成正比
1 | for (int i = 0; i < n; i++) { // 执行频度为 n+1 |
当$n \to \infty$时,$f(n)$与$n^2$同阶,或者说数量级相同,用$O$来表示这个数量级,记作$T(n) = O(f(n)) = O(n^2)$
算法的时间复杂度分析
常量阶
如下代码,语句频度$f(n) = 2$,算法的执行时间是一个与问题规模n无关的常数,所以算法的时间复杂度$T(n) = O(1)$,称为常量阶
1 | if (i < j) { // 执行频度为1 |
线性阶
如下代码,语句频度$f(n) = 3+3n+3+3n$,算法的时间复杂度$T(n) = O(n)$,称为线性阶
1 | for (int i = 0; i < 3; i++) { // 执行频度为 4 |
平方阶
如下代码,语句频度$f(n) = n+1+n^2+n+n^2$,算法的时间复杂度$T(n) = O(n^2)$,称为平方阶
1 | for (int i = 0; i < n; i++) { // 执行频度为 n+1 |
立方阶
如下代码,语句频度$f(n) = n+1+n^2+n+n^3+n^2+n^3$,算法的时间复杂度$T(n) = O(n^3)$,称为立方阶
1 | for (int i = 0; i < n; i++) { // 执行频度为 n+1 |
对数阶
对数阶的时间复杂度计算是比较麻烦的,详细说一下
如下代码,假设在$n$足够大的前提下,
1 | for (int i = 1; i < n; i = i*2) { |
当循环中的printf
第一次被执行时,将会输出1
,然后是2
4
8
16
…,第$f(n)$次执行时,$i = 2^{f(n)-1}$,由于$i \lt n$,所以$2^{f(n)-1} \lt n$,那么这一行的执行频度$f(n) \lt \log_2 n + 1$,即$f(n)$是一个小于$\log_2 n + 1$的整数,
循环轮次 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 第$f(n)$次 |
i的值 | 1 | 2 | 4 | 8 | 16 | $2^{f(n)-1}$ |
那么当$n=8$时,$\log_2 8 + 1 = 4$,printf
所在行的执行频度$f(n) \lt 4$,得到$f(n) = 3$;而当$n=9$时,$\log_2 9 + 1 \gt 4$,printf
所在行的执行频度$f(n) \lt \log_2 9 + 1$,得到$f(n) = 4$
由上可知代码中的语句频度$f(n) = \log_2 n + 2 + \log_2 n + 1$,所以时间复杂度$T(n) = O(\log_2 n)$
空间复杂度
空间复杂度作为算法所需储存空间的量度,它也是问题规模n的函数,记作$S(n) = O(f(n))$
空间复杂度只需要分析算法在实现时所需要的辅助空间,若算法执行时所需要的辅助空间相对于输入数据量而言是个常数,则称这个算法为原地工作,辅助空间为$O(1)$
如下两种数组逆序算法,第一种算法的空间复杂度为$O(1)$,因为它只借助了一个变量t,与问题规模n大小无关;
1 | for (int i = 0; i < len/2; i++) { |
第二种算法空间复杂度为$O(n)$,因为额外借助了一个与原数组等长的辅助数组
1 | int temp[len]; |