算法的复杂度分析
算法的复杂度分析
在讨论数据结构和算法时,我们通常用算法的复杂度来描述一个算法的好坏,复杂度包括:时间复杂度 和 空间复杂度。计算机本质上是一个状态机,内存里的数据构成了当前的状态,CPU利用当前的状态计算出下一个状态。所谓的空间复杂度就是为了支持你的计算所必需存储的状态最多有多少,所谓时间复杂度就是从初始状态到达最终状态中间需要多少步!
时间复杂度
算法的时间复杂度,也就是算法的时间量度,记作:T(n) = O(f(n))。它表示随问题规模 n 的增大,算法执行时间的增长率和 f(n) 的增长率相同,称为算法的渐近时间复杂度,简称为时间复杂度。其中 f(n) 是规模 n 的某个函数。
如何推导时间复杂度
- 用常数 1 取代运行时间中的所有加法常数。
- 在修改后的运行次数函数中,只保留最高阶项。
- 如果最高阶项存在,且不是 1 ,则去除与这个项相乘的常数。
例如:
int sum = 0, n = 100; // 执行 1 次
sum = (1 + n) * n / 2; // 执行 1 次
printf("%d", sum); // 执行 1 次这个算法的运行次数函数是 f(n) = 3。根据推导规则,第一步把常数项 3 改为 1。第二步保留最高阶项,它没有最高阶项,所以这个算法的时间复杂度为 T(n) = O(1)。
常数阶
当 n = 1 时,算法执行次数为 3, 当 n = 100 时,算法的执行次数还是 3,所以我们可以看出这个算法的执行次数与 n 的规模没关系。我们把这种与问题的大小(n 的大小)无关,执行时间恒定的算法,叫作常数阶。
线性阶
下面这段代码的时间复杂度为 T(n) = O(n),因为循环体中的代码必须要执行 n 次。
for (i = 0; i < n; i++) {
/* 时间复杂度为 O(1) 的程序步骤序列 */
}对数阶
int count = 1;
while (count < n) {
count = count * 2;
/* 时间复杂度为 O(1) 的程序步骤序列 */
}由于每次 count 乘以 2 以后,就越来越接近于 n,也就是说有多少个 2 相乘后大于 n,则会退出循环。由 2^x = n 得到 x = log2n。所以这个算法的时间复杂度为 T(n) = O(logn)。
平方阶
// 例1
int i, j;
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
/* 时间复杂度为 O(1) 的程序步骤序列 */
}
}
// 例2
int i, j, m;
for (i = 0; i < m; i++) {
for (j = 0; j < n; j++) {
/* 时间复杂度为 O(1) 的程序步骤序列 */
}
}在 例1 中内循环时间复杂度为O(n),而对于外层的循环,不过是这个内循环再循环 n 次。所以这段代码的时间复杂度为 O(n^2)。
在 例2 中,时间复杂度就变为 O(m^n)
常见的时间复杂度耗费时间
从小到大依次是:
O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)
空间复杂度
算法的空间复杂度通过计算算法所需的存储空间实现,算法空间复杂度的计算公式:
S(n) = O(f(n))其中 n 为问题的规模,f(n)为语句关于 n 所占存储空间的函数。
复杂度分析的主要方法
- 迭代:级数求和
- 递归:递归跟踪 + 递推方程
猜测 + 验证
