算法效率的度量是通过时间复杂度和空间复杂度来描述的。
1.时间复杂度
一个语句的频度是指该语句在算法中被重复执行的次数。
算法中所有语句的频度之和记为T(n),是该算法问题规模 n 的函数。
时间复杂度主要分析 T(n) 的数量级。
算法中基本运算(最深层循环内的语句)的频度与 T(n) 同数量级,因此通常采用算法中基本运算的频度 f(n) 来分析算法的时间复杂度。
算法的时间复杂度记为:
例如 ,取其中随 n 增长最快的项,也就是
这一项,将其系数置为1作为时间复杂度的度量,也就是时间复杂度为
。
其中O的含义是 T(n) 的数量级。这里就不展开定义了。
算法的时间复杂度不仅依赖于问题规模 n ,也取决于待输入数据的性质。比如有些循环有判断条件,所以最深层循环内的语句不一定在所有 n 都执行一遍,实际的频度很可能小于 n 甚至是常数0。
时间复杂度通常考虑三种:最坏、最好和平均时间复杂度。
最坏时间复杂度是指在最坏的情况下,算法的时间复杂度。这个最坏情况可能是要查找一个数,结果在所有待查找中的数全找了一遍结果也没找到。
最好时间复杂度是指在最好的情况下,算法的时间复杂度。这里的最好指的是类似按照某种规则查找一个数,结果在一堆数里面第一次就找到了,后面就不用再找了的情况。
平均时间复杂度是指所有可能输入实例在等概率出现的情况下,算法的期望运行时间。比如要在10个数里面找到一个奇数,那么平均时间复杂度就是这10个数里面一半是奇数一半是偶数时的情况。
一般情况下通常要考虑最坏情况下的时间复杂度,来保证算法的运行时间不会比它更长。
分析一个程序的时间复杂度时,有以下两条规则:
(1)加法规则
(2)乘法规则
常见的渐进时间复杂度为:
这个大小排序还是比较重要的,可以记为“常对幂指阶”。后面有一些例题可以再体会一下时间复杂度怎么看。
2.空间复杂度
算法的空间复杂度 S(n) 定义为该算法所耗费的存储空间,是问题规模 n 的函数。记为:
一个程序在执行时除了需要在存储空间存放本身所用的指令、常数、变量和输入数据外,还需要一些对数据进行操作的工作单元和一些辅助空间。若输入数据所占空间只取决于问题本身。
算法原地工作是指算法所需的辅助空间为常量,即O(1)。注意这里的常量包括1,2,3,...甚至是100,1000,不管多大,只要是常数就叫原地工作。另一种情况是和算法规模 n 相关的,比如 n 越大需要的辅助空间越大,这种情况就不叫原地工作。
3.时间复杂度例题
下面讲几个经典的复杂度分析的例题。这类题的思路就是找到最基本运算的执行次数,这个执行次数通常和循环联系在一起,考虑的是结束条件。
例1 以下算法的时间复杂度为?
void fun(int n){
int i=1;
while(i<=n)
i=i*2;
}
该题为普通循环。先找到基本运算,也就是while里的 i=i*2 ,这个基本运算的执行次数其实就是这个while循环内执行次数。循环变量从 1 开始,每次都乘 2,直到当前的数大于 n 循环才停止。假设次数是 k ,那么到第 k 次 i 应该为 , 循环结束的条件就是
,也就是
,这个次数的数量级是
,所以时间复杂度就是
。
例2 以下算法的时间复杂度为?
void fun(int n){
int i=0;
while(i*i*i<=n)
i++;
}
该题和上面例1类似,同样是普通循环。思路和上题完全相同,本质上就是while循环内执行次数,假设执行次数是 k ,到第 k 次 i 就是 k ,循环结束条件是,也就是
,所以时间复杂度就是
。
例3 以下算法的时间复杂度为?
x=2;
while(x<n/2)
x=2*x;
这道题依然是普通循环。设次数为 k ,那么第 k 次 x 应该为 ,和例1非常相似,循环结束条件就是
,也就是
,这个数量级还是
,时间复杂度为
。
例4 以下算法的时间复杂度为?
for(i=0;i<n;i++){
for(j=0;j<m;j++)
a[i][j]=0
}
该题是最基本的嵌套循环类型,且循环上限不同。外循环执行 n 次,内循环执行 m 次,所以基本运算一共执行 n×m次,时间复杂度就是。注意如果这里 n 或 m 中有一个是常数,不管这个数多大,在最后的时间复杂度中都不会有体现,因为时间复杂度考虑的是数量级,不会有常数。如改为 i<1000 ,j<m ,那最后的时间复杂度就是
。
例5 以下算法的时间复杂度为?
count=0;
for(k=1;k<=n;k*=2){
for(j=1;j<=n;j++)
count++;
}
该题是嵌套循环,且是循环和对数混合。此类题的基本运算是嵌套循环里最里面的运算,该题就是count++这一句。要计算执行次数同样也是看循环执行的次数,只不过要考虑两个循环。
在每一次外循环里,内循环执行的次数都是 n ,所以我们就要看外循环有几次,这样基本运算的次数就是几个 n 。恰好外循环和例1中的情况相同,我们知道这个数量级是,再考虑内循环每次执行 n 次,最后的结果就是
。
循环这里还会有三次循环,或者每次循环变量改变的
例6 以下算法的时间复杂度为?
int func(int n) {
if (n <= 1) return 1;
return n * func(n - 1);
}
该题考察的是递归类型。递归类型的时间复杂度首先需要明确一个问题:除了递归结束时的那一次(该题对应的就是 n<=1 的时候),其他每次递归其实做了两个操作,一个就是 n 和 func(n-1)相乘的这个乘法,另一个就是递归调用 func(n-1),所以递归式 T(n)=T(n-1)+O(1),这里忘了 T 是什么的往上翻,T(n-1)对应的是递归调用 func(n-1),O(1)对应的就是 n 和 func(n-1)相乘的这个乘法操作。
一种方法是直接展开,这里我们用 c 表示上面递归式的O(1),这个常数时间用什么表示都可以:
T(n) = T(n-1)+c = [T(n-2)+c]+c = ... = T(1) + (n-1)c = c+(n-1)c = nc
注意这里 T(1)对应的就是递归结束 return 1 的情况,所以就是 c 。时间一共是 nc ,最后的时间复杂度就是O(n)。
还可以通过递归树或主定理求时间复杂度,这里就不分析复杂例题了。另外空间复杂度很少有直接分析的题,一般是和算法结合起来考虑,这里就不放例题了。

4300

被折叠的 条评论
为什么被折叠?



