但是这种方法计算的是执行算法的实际时间,有两个明显的缺陷:1) 必须先运行依据算法编制的程序;2) 依赖于特定的计算机、编译器与编程语言等软硬件环境,容易掩盖算法本身的优劣。因此,我们希望找到一个独立于程序或计算机的指标,以用来比较不同实现下的算法。
2.1 大 O O O 表示法
为了摆脱与计算机硬件、软件有关的因素,我们需要一种事前分析估算的方法。可以认为特定算法的“运行工作量”大小取决于问题的规模,或者说,它是问题规模的函数,这时我们就需要量化算法的操作或步骤。一个算法是由控制结构和基本操作构成的,因此可以将算法的执行时间描述成解决问题所需重复执行的基本操作数。需要注意的是,确定合适的基本操作取决于不同的算法。例如在计算斐波那契数列第 n 项时,赋值语句就是一个基本操作,而在计算矩阵乘法时,乘法运算则是其基本操作。
在上一节的 fibo
函数中,整个算法的执行时间与基本操作(赋值)重复执行的次数n 成正比,具体而言是 1 加上 n-2 个赋值语句,如果使用将其定义为函数可以表示为 T ( n ) = n − 1 T(n)=n-1 T(n)=n−1,其中 n n n 为大于 2 的正整数。 n n n 常用于表示问题规模,我们可以使用问题规模 n n n 的某个函数 f ( n ) f(n) f(n) 表示算法中基本操作重复执行的次数,算法的时间量度可以表示如下:
T ( n ) = O ( f ( n ) ) T(n)=O(f(n)) T(n)=O(f(n))
随问题规模 n n n 的增大, T ( n ) T(n) T(n) 函数的某一部分会比其余部分增长得更快,算法间进行比较时这一起部分起决定性作用, T ( n ) T(n) T(n) 增长最快的部分也称为数量级函数。算法执行时间的增长率和 f ( n ) f(n) f(n) 的增长率相同,称作算法的渐近时间复杂度 (asymptotic time complexity),简称时间复杂度。数量级 (order of magnitude) 常被称作大 O O O 记法或大 O O O 表示法。
通过以上分析,我们可以将算法的渐近复杂度规则描述如下:
-
如果运行时间是一个多项式的和,那么仅保留增长速度最快的项,去掉其他各项;
-
如果剩下的项是个乘积,那么去掉所有常数。
假设某一算法的基本步骤数为 T ( n ) = 3 n 2 + 50 n + 2000 T(n)=3n^2+50n+2000 T(n)=3n2+50n+2000,当 n n n 很小时 2000 对于函数的影响最大,但是随着 n n n 的增长 n 2 n^2 n2 将逐渐变得更重要,以至于可以忽略其他两项以及 n 2 n^2 n2 的系数 3,因此可以说 T ( n ) T(n) T(n) 的数量级是 n 2 n^2 n2 或写为 O ( n 2 ) O(n^2) O(n2)。
算法的性能有时不仅依赖问题的规模,还取决于算法的输入值,输入令算法运行最慢的情况称为最坏情况,输入令算法运行最快的情况称为最好情况,随机输入的情况下算法的性能介于两种极端情况之间,称为平均情况。
2.2 常见算法复杂度
下表列出了一些常见的大 O O O 表示法实例:
| 复杂度 | 解释 | 示例 |
| — | — | — |
| O ( 1 ) O(1) O(1) | 常数复杂度 | 100, 500, 1, 30, … |
| O ( l o g n ) O(logn) O(logn) | 对数复杂度 | l o g 2 n log_2n log2n, l o g 10 n log_{10}n log10n, 2 l o g 2 n 2log_2n 2log2n, … |
| O ( n ) O(n) O(n) | 线性复杂度 | 8 n + 10 8n+10 8n+10, n n n, 100 n 100n 100n, … |
| O ( n l o g n ) O(nlogn) O(nlogn) | 对数线性复杂度 | 10 n l o g n + 50 10nlogn+50 10nlogn+50, 5 n l o g n + 30 n 5nlogn+30n 5nlogn+30n, … |
| O ( n k ) O(n^k) O(nk