Copyright
本页面贡献者:水之幡米拉。 本页面内容遵循 MIT 协议,转载请附上原文出处链接和本声明。
时间复杂度¶
我们为什么需要计算时间复杂度¶
现代计算机每秒可以处理数亿乃至更多次基本运算,因此我们处理的数据规模通常很大。但如果计算的量很大,那么计算量可能会是个天文数字,那么即便是超级计算机,也需要耗费极大的时间去解决。
比如,假设数据量为n(可以理解为有n个数需要进行处理),算法A的计算量为2^n,而算法B的所需计算量为n^2。假设超级计算机每秒可进行10^{12}次计算。如果n\le 80,那么计算机都可以在极短时间内计算出结果,但如果n很大,比如n = 10^5,那么显然,计算机无法在短时内计算完算法A。
如何计算时间复杂度¶
首先我们需要了解几个概念 基本操作数, T(n),O(n)
基本操作数¶
在普通的计算机上,加减乘除、访问变量(基本数据类型的变量)、给变量赋值等都可以看作基本操作。 比如:
1 2 3 4 5 |
|
函数T¶
当数据规模为n时,T(n)表示算法所需的计算量 在此过程中,基本操作算作一次运算
比如这个程序:
1 2 3 4 5 |
|
在单次循环中,执行了四次个基本操作:
1 2 3 4 |
|
1 2 3 |
|
渐进符号 O¶
定义:对于两个函数,f(n),g(n) 设f(n) = O(g(n)),则有: \exists c \in R^*, n_0 \in N^*,使得\forall n \ge n_0,有0 \le f(n) \le c * g(n)。
人话版本
当n很大的时候,f(n)始终小于等于g(n)乘上一个常数c。
如何计算 O¶
对于T(n) = 4 * n + 3,那么O(T(n)) = n 为什么呢? 因为n \ge 1时,4 * n + 3 <= 5 * n 此时,c = 5, n_0 = 1
那么对于T(n) = 3*n^2 + 100 * n + 114514,O(T(n))是多少呢?
实际上对于T(n) = 3*n^2 + 100 * n + 114514 O(T(n)) = n^2
为什么呢?因为当n足够大的时候,n^2的增长速度会远超100 * n,其大小也会远超114514,所以我们只需要找到n^2的上限即可,此时那么显然4 * n ^ 2可以满足。(当然3.01 * n ^ 2也可以,只要n足够大)
综上,只要我们知道T(n)的表达式,那么我们只需找到它在数据范围为n时,增长最快的一项即可。 实际上,我们也不需要完整的求出T(n),因为我们只关注它增长速度最大的那一项。
所谓的 O() 就是我们平时所说时间复杂度。
比如当n <= 10^5时, 对于T(n) = 100 * n^2 + 114514 * n + n! + log_2(n) 那么显然有:O(T(n)) = n! 记为O(n!)
计算时间复杂度时需要注意的细节¶
那么它们的时间复杂度显然都是O(n), 但是这两个算法在运行时,所消耗时间肯定有很大区别,因为在T(n)的表达式中,算法A中n的前面乘上了一个很大的常数,它的大小甚至大于了n的范围,所以此时,虽然它是个常数,但它对计算量的大小影响是十分显著的,我们需要考虑它的影响。
O的定义是:\exists c \in R^*, n_0 \in N^*,使得\forall n \ge n_0,有0 \le f(n) \le c * g(n)。 这个 c 其实就是我们刚才说的常数。
通常情况下,基本操作对时间复杂度的影响我们是可以不考虑的,除非我们在一个循环,或者递归中,加入了很多次的运算。
简单来说,只要 c \le 50,那么通常来说,问题不大(更严谨的来说,要根据n的范围)。
通过时间复杂度,我们可以判断出我们程序是否可以在规定时间内运行完。
一般来说,比赛的评测姬一秒能执行5 * 10 ^ 8次左右的运算,精确数值通常无从所知。
所以,对于n \le 2 * 10 ^ 4的数据量,O(n^2)就是极限了,此时常数过大就有可能TLE。
计算时间复杂度 - 循环¶
1 2 3 4 |
|
1 2 3 |
|
1 2 3 |
|
1 2 3 4 |
|
计算时间复杂度 - 位运算¶
1 2 3 4 |
|
那么我们怎么才能得出它二进制下有多少位呢?其实很简单sum = log_{2}(n) 那么它的时间复杂度就是O(log_2(n))。
由于在算法竞赛中,时间复杂度如果有对数的,那么基本都是以2为底数的,所以默认情况下log(n),就是以2为底数的。
计算时间复杂度 - 递归¶
1 2 3 4 5 6 7 8 9 10 |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
我们用[l, r1]来表示每次递归的范围,并假设n = 8
第0次递归: [1, 8] 第1次递归: [1, 4], [5, 8] 第2次递归: [1, 2], [3, 4], [5, 6] , [7, 8] 第3次递归: [1, 1], [2, 2], [3,3], [4,4],[5,5],[6,6],[7,7],[8,8]
显然,它的递归边界范围(r - l + 1)在每次递归中都会一分为二,所以递归深度为\lceil log_2(n) \rceil。在第i层递归中,共有2^i个分支。那么总次数 sum = \sum_{i = 0}^{\lceil log_2(n) \rceil} 2^i = 2^{\lceil log_2(n) \rceil + 1} - 1 = 2 * n - 1 所以时间复杂度为O(n)