算法时间复杂度是描述一个算法在一定问题规模(n)内运行效率的高低的一个标准之一。那如何计算算法的时间复杂度呢?我们首先需要得到算法的时间频度。
时间频度
例如
以下两个算法用来计算1,2,3,4…99,100的和
int sum = 0; // 执行了1次
int i; // 执行了1次
for (i = 1; i <= 100; i++) { // 执行了100+1次
sum += i; // 执行了100次
}
printf("和为:%d", sum); // 执行了1次
总共执行了1+1+(100+1)+100+1=204次
如果现在将算法改成计算1,2,3,4…n的和
int sum = 0; // 执行了1次
int i; // 执行了1次
for (i = 1; i <= n; i++) { // 执行了n+1次
sum += i; // 执行了n0次
}
printf("和为:%d", sum); // 执行了1次
那代码将执行1+1+(n+1)+n+1=2n+4次
这就是算法的时间频度了。现在我们还要来解决一个问题“函数的渐近增长”
次数 | 算法A(2n+4) | 算法A`(2n) | 算法B(4n+2) | 算法B`(4n) |
---|---|---|---|---|
n=1 | 6 | 2 | 6 | 4 |
n=2 | 8 | 4 | 10 | 8 |
n=3 | 10 | 6 | 14 | 12 |
n=10 | 24 | 20 | 42 | 40 |
n=100 | 204 | 200 | 402 | 400 |
n=1000 | 2004 | 2000 | 4002 | 4000 |
从这个表中可看出随着问题规模(n)的增长时间频度中的常数对算法造成的影响不大,例如算法A和算法A`,算法B和算法B`,当n越来越大时时间频度也越来越接近,所以我们可以忽略这些加法常数项。
次数 | 算法C(2n+4) | 算法C`(n) | 算法D ( 2 n 2 + 8 ) (2n^2+8) (2n2+8) | 算法D` ( n 2 ) (n^2) (n2) |
---|---|---|---|---|
n=1 | 6 | 1 | 10 | 1 |
n=2 | 8 | 2 | 16 | 4 |
n=3 | 10 | 3 | 26 | 9 |
n=10 | 24 | 10 | 208 | 100 |
n=100 | 204 | 100 | 20008 | 10000 |
n=1000 | 2004 | 1000 | 2000008 | 1000000 |
观察这个表可发现随着问题规模(n)的增长可以发现一次项的时间频度远远小于二次项的时间频度,算法C的的时间频度远远小于算法D,算法C`远远小于算法D`,所以最高次项的系数并不重要。
次数 | 算法E(2n+4) | 算法F ( 2 n 2 + 8 ) (2n^2+8) (2n2+8) | 算法G ( 2 n 3 + 3 ) (2n^3+3) (2n3+3) |
---|---|---|---|
n=1 | 6 | 10 | 5 |
n=2 | 8 | 16 | 19 |
n=3 | 10 | 26 | 57 |
n=10 | 24 | 208 | 2003 |
n=100 | 204 | 20008 | 2000003 |
n=1000 | 2004 | 2000008 | 2000000003 |
观察这个表可发现随着问题规模(n)的增长最高次项的指数越大增长越快
次数 | 算法H ( 2 n 2 + 3 ) (2n^2+3) (2n2+3) | 算法I ( 2 n 3 + 2 n 2 + 6 ) (2n^3+2n^2+6) (2n3+2n2+6) |
---|---|---|
n=1 | 5 | 10 |
n=2 | 11 | 30 |
n=3 | 21 | 78 |
n=10 | 203 | 2206 |
n=100 | 20003 | 2020003 |
n=1000 | 2000003 | 2002000003 |
观察这个表发现当问题规模(n)越来越大时,算法H与算法I相比较算法H几乎可以忽略不记,也就是说我们去最高次项就好。
总结
判断一个算法执行效率时可以忽略时间频度的常数项最高次项的系数和低次项,只需要关注最高次项即可。
算法时间复杂度的定义
在进行算法分析时,语句执行的次数T(n)是关于问题规模的n的函数,进而分析T(n)随n变化情况并确定T(n)的数量级。算法的时间复杂度,也就是算法的时间量度,记作O(n)=T(f(n))。它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称为时间复杂度。其中f(n)是问题规模的某个函数。
用大写O()来体现算法时间复杂的记法,称作大O记法。
推导大O阶:
1.用常数1取代运行时间中所有加法常数。
2.在修改后的运行次数函数中,只保留最高项次。
3.如果最高项次存在且不是1,则去除与这个项相乘的系数。
得到的结果就是大O阶
常数阶
int sum; // 执行1次
int n = 100; // 执行1次
sum = n*(1 + n) / 2; //执行1次
printf("%d", sum); // 执行1次
时间复杂度 O ( 1 ) 时间复杂度O(1) 时间复杂度O(1)
线性阶
int sum = 0; // 执行了1次
int i; // 执行了1次
for (i = 1; i <= n; i++) { // 执行了n+1次
sum += i; // 执行了n0次
}
printf("和为:%d", sum); // 执行了1次
时间复杂度 O ( n 2 ) 时间复杂度O(n^2) 时间复杂度O(n2)
对数阶
int count = 1;
int n = 100;
int i = 0;
while (count < n) {
count *= 2;
++i;
}
printf("%d", i);
上面代码输出i的值为7,也就循环执行了7次,即2的7次方等于128大于n=100。
当n不确定多大的时候,我们可以设执行次数i=x,所以有:
2
x
=
n
2^x=n
2x=n,
x
=
l
o
g
2
n
x=log_2n
x=log2n,
时间复杂度
O
(
l
o
g
n
)
时间复杂度O(logn)
时间复杂度O(logn)
平方阶
int i;
int j;
for (i = 1; i <= n; i++) {
for (j = 1; j <= n; j++) {
// 语句 /*执行n的平方次*/
}
}
时间复杂度 O ( n 2 ) 时间复杂度O(n^2) 时间复杂度O(n2)
总结:主要就是计算语句的执行次数与问题规模n的关系
参考书籍:《大话数据结构》