时间复杂度的解释与例题
声明:本人也在学习过程中,难免出现错误,所以如果大家发现错误,请不吝赐教,谢谢指正。
在数据结构考研大纲的基础知识中,对算法的时间复杂度的计算一直是考察的重点。这一部分如果说简单,但是又容易出错;如果说难,又是有规律可循的。因此,只要按照特定的步骤对程序进行逐步分析,就能准确无误的得到答案。
让我们先来看一看时间复杂度的定义。
01定义
时间复杂度是对算法效率的衡量标准,这是对其定性的描述。对于其定量描述,要引入以下概念:
频度: 一个语句的频度是指该语句在算法中被执行的次数。
比如下列程序中:
# 例
a = 0
for i in range(10):
a += i
其中 a += i
语句的被执行次数是10,频度也就是10。
我们设算法中所有语句的执行次数之和为
T
(
n
)
T(n)
T(n) ,
n
n
n 为问题规模,那么可以知道,算法最深层次的语句(例如例中的a+=1
)的执行次数与
T
(
n
)
T(n)
T(n)是同数量级的,计算
T
(
n
)
T(n)
T(n) 时比较复杂,所以我们可以通过最深层次语句的频度
f
(
n
)
f(n)
f(n)来衡量算法的时间复杂度,记为:
T
(
n
)
=
O
(
f
(
n
)
)
T(n) = O(f(n))
T(n)=O(f(n))
其中,
O
O
O 的含义是
T
(
n
)
T(n)
T(n) 的数量级,表达的意思是,
T
(
n
)
T(n)
T(n) 的数量级是
f
(
n
)
f(n)
f(n) 的函数,即与最深层次的语句频度有关。
由此一来,我们在计算的时候就有了目的,只要计算出 f ( n ) f(n) f(n),就相当于找到了时间复杂度。
02分类以及规则
021分类
虽然对于一个算法来说,程序的执行步骤是确定的,但是面对不同的输入数据,时间复杂度也会有所区别,因此按照结果的不同,可以将复杂度分为以下几类:
- 最坏时间复杂度
- 平均时间复杂度
- 最好时间复杂度
举一个例子,在后序查找算法中,如果目标元素在最后一个位置,那么第一次比较就可以得到答案,此时底层移位语句的执行次数 f ( n ) f(n) f(n)为常数,则时间复杂度为 O ( 1 ) O(1) O(1);如果目标元素在第一个位置,那么底层移语句执行次数为 n n n ,则时间复杂度为 O ( n ) O(n) O(n)。
为了衡量一个算法的整体效率,我们一般使用最坏时间复杂度来表示算法的效率下限。如果没有特殊说明,时间复杂度指的都是最坏时间复杂度。
022规则
加法规则
当要衡量的算法由几个底层语句并列时,我们遵守加法规则,即:
T ( n ) = T 1 ( n ) + T 2 ( n ) = O ( g ( n ) ) + O ( f ( n ) ) = O ( max ( g ( n ) , f ( n ) ) ) T(n) = T_1(n)+T_2(n) = O(g(n))+O(f(n)) = O(\max(g(n),f(n))) T(n)=T1(n)+T2(n)=O(g(n))+O(f(n))=O(max(g(n),f(n)))
乘法规则
当算法的底层语句出现与上层的嵌套时,遵守乘法规则,即:
T ( n ) = T 1 ( n ) × T 2 ( n ) = O ( g ( n ) ) × O ( f ( n ) ) = O ( g ( n ) × f ( n ) ) T(n) = T_1(n)\times T_2(n) = O(g(n))\times O(f(n)) = O(g(n)\times f(n)) T(n)=T1(n)×T2(n)=O(g(n))×O(f(n))=O(g(n)×f(n))
常见的时间复杂度的比较
O ( 1 ) < O ( log 2 n ) < O ( n ) < O ( n log 2 n ) < O ( n 2 ) < O ( n 3 ) < O ( 2 n ) < O ( n ! ) < O ( n n ) O(1)<O(\log_2n)<O(n)<O(n\log_2n)<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n) O(1)<O(log2n)<O(n)<O(nlog2n)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
03例题解析
开头已经说过,时间复杂度这一块虽然算起来很复杂,但是计算有章法,只要按照步骤计算,总能得出正确答案,接下来把我总结的方法结合几个例题进行详细解释。
方法:
一般的程序都是套在循环当中的,所以我们先设置一个变量 t t t 来存放循环的次数,其次,程序中会有一个基本计数单位 i i i ,每一次循环都会判断计数单位 i i i 是否满足特定条件,最底层程序的执行次数其实就是循环判断条件满足的次数,最终我们根据循环次数 t t t 与计数单位 i i i 的关系,代入循环条件,就能得到程序的时间复杂度。
请看下列例题的详细解释。
例1
设 n n n 是描述问题规模的非负整数,那么下列程序片段的时间复杂度是多少?
x = 2;
while(x<n/2)
x = 2*x;
解:
设 t t t 为循环执行次数,程序中的 x x x 为基本计数单位,可以得到下表:
t | x |
---|---|
1 | 2 |
2 | 4 |
3 | 8 |
4 | 16 |
[注意,这里的 x x x 值是对应第 t t t 次的循环判断语句结束后的状态,并未执行底层语句]
经过几次推理,我们可以得到 t t t 与 x x x 的关系为: x = 2 t x = 2^t x=2t 。
根据我们的循环条件 x < n / 2 x<n/2 x<n/2 ,有 2 t < n / 2 2^t<n/2 2t<n/2 。
则可以得到 t < log 2 ( n / 2 ) = log 2 n − 1 < log 2 n t<\log_2(n/2) = \log_2n-1<\log_2n t<log2(n/2)=log2n−1<log2n 。
因此该程序片段的时间复杂度为 O ( l o g 2 n ) O(log_2n) O(log2n)。
例2
下列函数的时间复杂度是多少?
int func(int n){
int i = 0,sum = 0;
while(sum<n)
sum += ++i;
return i;
}
解:
设循环次数为 t t t ,基本计数单位是 s u m sum sum,但是 s u m sum sum 与 i i i 有关,所以我们将 i i i 也考虑在内,方便计算,于是可以得到下表
t | i | sum |
---|---|---|
1 | 0 | 1 |
2 | 1 | 1+2 = 3 |
3 | 2 | 1+2+3 = 6 |
4 | 3 | 1+2+3+4 = 10 |
好了,我们可以看出, s u m sum sum 与 t t t 的关系为 s u m = t ( 1 + t ) 2 sum = \frac{t(1+t)}{2} sum=2t(1+t) ,则由判断条件中 s u m < n sum<n sum<n 可以得到 t ( t + 1 ) 2 < n \frac{t(t+1)}{2}<n 2t(t+1)<n ,则 t t t 与 n n n 的关系为 t < 2 n t<\sqrt{2n} t<2n ,因为时间复杂度是一个数量级,所以去掉系数此函数的时间复杂度为 O ( n ) O(\sqrt{n}) O(n) 。
例3
下列程序段的时间复杂度是多少?
count = 0;
for(k=1;k<n;k*=2)
for(j=1;j<n;j++)
count++
解:
这道题的最底层语句是两层循环嵌套实现的,很明显要用乘法规则计算,我们可以分开算。
对于最外层,设执行次数为 t 1 t_1 t1 ,基本计数单位是 k k k ,则可得下表:
t 1 t1 t1 | k k k |
---|---|
1 | 1 |
2 | 2 |
3 | 4 |
4 | 8 |
则可以得到 k = 2 t 1 − 1 < n k = 2^{t_1-1} <n k=2t1−1<n ,即 t 1 < log 2 n + 1 t_1<\log_2n+1 t1<log2n+1 ,去掉杂项,则最外层的时间复杂度为 O ( log 2 n ) O(\log_2n) O(log2n) 。
对于最内层,可以直观的看出时间复杂度为 O ( n ) O(n) O(n).
所以由乘法规则有,整个程序的时间复杂度为 O ( n log 2 n ) O(n\log_2n) O(nlog2n)。
04总结
这一块知识考察的很单一,所以很有可能用其他的方式综合考察,比如说搭配排序,查找,甚至一些更复杂的存储结构的增删改查。但是万变不离其宗,只要抓住最主要的那一个点,分析清楚最底层语句的执行逻辑,就能很快的解出答案。
作者:Ap01lo
首发网站:https://ap01lo.github.io/
转载请注明出处