如何计算算法的时间复杂度

算法的时间复杂度定义为:

在进行算法分析时,语句总的执行次数 T(n) 是关于问题规模 n 的函数,进而分析 T(n) 随 n 的变化情况并确定 T(n) 的数量级。算法的时间复杂度,也就是算法的时间量度,记作:T(n}=0(f(n))。它表示随问题规模 n 的增大,算法执行时间的埔长率和 f(n) 的埔长率相同,称作算法的渐近时间复杂度,简称为时间复杂度。其中 f( n) 是问题规横 n 的某个函数。

  • 这样用大写 O() 来体现算法时间复杂度的记法,我们称之为大 O 记法。一般情况下,随着 n 的增大,T(n) 增长最慢的算法为最优算法。
  • 之前我们说的三个求和算法的时间复杂度分别为 0(n),0(1),0(n2)。我就推一下吧。

计算 1 + 2 + 3 + 4 + …… + 100。代码如下,之前也有讲过:

1
2
3
4
5
6
7
8
9
10
11
12
#include "stdio.h"
int main()
{
int i, sum = 0, n = 100; /* 执行1次 */
for( i = 1; i <= n; i++) /* 执行 n+1 次 */
{
sum = sum + i; /* 执行n次 */
//printf("%d \n", sum);
}
printf("%d", sum); /* 执行1次 */
}

从代码附加的注释可以看到所有代码都执行了多少次。那么这写代码语句执行次数的总和就可以理解为是该算法计算出结果所需要的时间。该算法所用的时间(算法语句执行的总次数)为: 1 + (n + 1) + n + 1 = 2n + 3

而当 n 不断增大,比如我们这次所要计算的不是 1 + 2 + 3 + 4 + …… + 100 = ? 而是 1 + 2 + 3 + 4 + …… + n = ?其中 n 是一个十分大的数字,那么由此可见,上述算法的执行总次数(所需时间)会随着 n 的增大而增加,但是在 for 循环以外的语句并不受 n 的规模影响(永远都只执行一次)。所以我们可以将上述算法的执行总次数简单的记做: 2n 或者简记 n

这样我们就得到了我们设计的算法的时间复杂度,我们把它记作: O(n)

再来看看高斯的算法:

1
2
3
4
5
6
7
8
9
#include "stdio.h"
int main()
{
int sum = 0, n = 100; /* 执行1次 */
sum = (1 + n) * n/2; /* 执行1次 */
printf("%d", sum); /* 执行1次 */
}

这个算法的时间复杂度: O(3),但一般记作 O(1)。

从感官上我们就不难看出,从算法的效率上看,O(3) < O(n) 的,所以高斯的算法更快,更优秀。

下面再来一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "stdio.h"
int main()
{
int i, j, x = 0, sum = 0, n = 100; /* 执行1次 */
for( i = 1; i <= n; i++)
{
sum = sum + i;
//printf("%d \n", sum);
for( j = 1; j <= n; j++)
{
x++; /* 执行n*n次 */
sum = sum + x;
}
}
printf("%d", sum); /* 执行1次 */
}

上面的代码严格的说不能称之为一个算法,毕竟它很 “无聊而且莫名其妙”(毕竟算法是为了解决问题而设计的嘛),先不论这个“算法” 能解决什么问题,我们看一下它的 “大 O 阶” 如何推导,还是先计算一下它的执行总次数:

执行总次数 = 1 + (n + 1) + n(n + 1) + nn + (n + 1) + 1 = 2n2 + 3n + 3

如何推导大 o 阶呢?我们给出了下面 的推导方法:

  1. 用常数 1 取代运行时间中的所有加法常数。
  2. 在修改后的运行次数函数中,只保留最髙阶项。
  3. 如果最高阶项存在且不是 1, 则去除与这个项相乘的常数。

按照上面推导 “大 O 阶” 的步骤我们先来第一步:“用常数 1 取代运行时间中的所有加法常数”,则上面的算式变为:执行总次数 = 2n^2 + 3n + 1

第二步:“在修改后的运行次数函数中,只保留最高阶项”。这里的最高阶是 n 的二次方,所以算式变为:执行总次数 = 2n^2

第三步:“如果最高阶项存在且不是 1 ,则去除与这个项相乘的常数”。这里 n 的二次方不是 1 所以要去除这个项的相乘常数,算式变为:执行总次数 = n^2

因此最后我们得到上面那段代码的算法时间复杂度表示为: O(n^2)

  • 最后我们在把常见的算法时间复杂度以及他们在效率上的高低顺序记录在这里,是大家对算法的效率有个直观的认识。

O(1) 常数阶 < O(logn) 对数阶 < O(n) 线性阶 < O(nlogn) < O(n^2) 平方阶 < O(n^3) < { O(2^n) < O(n!) < O(n^n) }

  • 最后三项用大括号把他们括起来是想要告诉大家,如果日后大家设计的算法推导出的 “大 O 阶” 是大括号中的这几位,那么趁早放弃这个算法,在去研究新的算法出来吧。因为大括号中的这几位即便是在 n 的规模比较小的情况下仍然要耗费大量的时间,算法的时间复杂度大的离谱,基本上就是“不可用状态”。

参考资料

本文地址:http://www.nowamagic.net/librarys/veda/detail/2195,欢迎访问原出处。