算法效率的度量 时间复杂度 空间复杂度

算法效率的度量是通过时间复杂度和空间复杂度来描述的。

1.时间复杂度

一个语句的频度是指该语句在算法中被重复执行的次数

算法中所有语句的频度之和记为T(n),是该算法问题规模 n 的函数。

时间复杂度主要分析 T(n) 的数量级

算法中基本运算(最深层循环内的语句)的频度与 T(n) 同数量级,因此通常采用算法中基本运算的频度 f(n) 来分析算法的时间复杂度。

算法的时间复杂度记为:

T(n)=O(f(n))

例如 f(n)=an^3+bn^2+cn,取其中随 n 增长最快的项,也就是an^3这一项,将其系数置为1作为时间复杂度的度量,也就是时间复杂度为O(n^3)

其中O的含义是 T(n) 的数量级。这里就不展开定义了。

算法的时间复杂度不仅依赖于问题规模 n ,也取决于待输入数据的性质。比如有些循环有判断条件,所以最深层循环内的语句不一定在所有 n 都执行一遍,实际的频度很可能小于 n 甚至是常数0。

时间复杂度通常考虑三种:最坏、最好和平均时间复杂度。

最坏时间复杂度是指在最坏的情况下,算法的时间复杂度。这个最坏情况可能是要查找一个数,结果在所有待查找中的数全找了一遍结果也没找到。

最好时间复杂度是指在最好的情况下,算法的时间复杂度。这里的最好指的是类似按照某种规则查找一个数,结果在一堆数里面第一次就找到了,后面就不用再找了的情况。

平均时间复杂度是指所有可能输入实例在等概率出现的情况下,算法的期望运行时间。比如要在10个数里面找到一个奇数,那么平均时间复杂度就是这10个数里面一半是奇数一半是偶数时的情况。

一般情况下通常要考虑最坏情况下的时间复杂度,来保证算法的运行时间不会比它更长。

分析一个程序的时间复杂度时,有以下两条规则:

(1)加法规则

T(n)=T_1(n)+T_2(n)=O(f(n))+O(g(n))=O(max(f(n),g(n)))

(2)乘法规则

T(n)=T_1(n) \times T_2(n)=O(f(n)) \times O(g(n))=O(f(n) \times g(n))

常见的渐进时间复杂度为:

O(1)<O(log_2n)<O(n)<O(nlog_2n)<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n)

这个大小排序还是比较重要的,可以记为“常对幂指阶”。后面有一些例题可以再体会一下时间复杂度怎么看。

2.空间复杂度

算法的空间复杂度 S(n) 定义为该算法所耗费的存储空间,是问题规模 n 的函数。记为:

S(n)=O(g(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^{k-1}, 循环结束的条件就是2^{k-1}>n,也就是k>long_2n+1,这个次数的数量级是log_2n,所以时间复杂度就是O(log_2n)

例2 以下算法的时间复杂度为?

void fun(int n){
    int i=0;
    while(i*i*i<=n)
        i++;
}

该题和上面例1类似,同样是普通循环。思路和上题完全相同,本质上就是while循环内执行次数,假设执行次数是 k ,到第 k 次 i 就是 k ,循环结束条件是k^3>n,也就是k>\sqrt[3]{n},所以时间复杂度就是O(\sqrt[3]{n})

例3 以下算法的时间复杂度为?

x=2;
while(x<n/2)
    x=2*x;

这道题依然是普通循环。设次数为 k ,那么第 k 次 x 应该为 2^k,和例1非常相似,循环结束条件就是2^k>=n/2,也就是k>log_2({n-2}),这个数量级还是log_2n,时间复杂度为O(log_2n)

例4 以下算法的时间复杂度为?

for(i=0;i<n;i++){
    for(j=0;j<m;j++)
        a[i][j]=0
}

该题是最基本的嵌套循环类型,且循环上限不同。外循环执行 n 次,内循环执行 m 次,所以基本运算一共执行 n×m次,时间复杂度就是O(nm)。注意如果这里 n 或 m 中有一个是常数,不管这个数多大,在最后的时间复杂度中都不会有体现,因为时间复杂度考虑的是数量级,不会有常数。如改为 i<1000 ,j<m ,那最后的时间复杂度就是O(m)

例5 以下算法的时间复杂度为?

count=0;
for(k=1;k<=n;k*=2){
    for(j=1;j<=n;j++)
        count++;
}

该题是嵌套循环,且是循环和对数混合。此类题的基本运算是嵌套循环里最里面的运算,该题就是count++这一句。要计算执行次数同样也是看循环执行的次数,只不过要考虑两个循环。

在每一次外循环里,内循环执行的次数都是 n ,所以我们就要看外循环有几次,这样基本运算的次数就是几个 n 。恰好外循环和例1中的情况相同,我们知道这个数量级是O(log_2n),再考虑内循环每次执行 n 次,最后的结果就是O(nlog_2n)

循环这里还会有三次循环,或者每次循环变量改变的

例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)。

还可以通过递归树或主定理求时间复杂度,这里就不分析复杂例题了。另外空间复杂度很少有直接分析的题,一般是和算法结合起来考虑,这里就不放例题了。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值