算法的效率
我们度量算法效率方式:事前分析估算方法 在计算机程序编写前,依据统计方法对算法进行估算
算法的效率的度量是抽象的,而不是进行精确的测量,忽略硬件方面、程序编译优化,代码循环终止条件和变量声明等因素
下面把函数当成一般的算法进行效率的判断
例子:
函数 | n=1 | 2 | 3 | 100 | 省略后 |
n+2 | 3 | 4 | 5 | 102 | 100 |
2n+1 | 3 | 5 | 7 | 201 | 200 |
2n+2 | 4 | 6 | 8 | 202 | 100 |
2n^2+2 | 4 | 10 | 20 | 20002 | 10000 |
2n^2+n+2 | 5 | 12 | 23 | 20102 | 10000 |
2n^3+n+2 | 5 | 20 | 59 | 2000102 | 1000000 |
比较2n+1和n+2的效率时,当n的值越来越大时,常数是可以省略的掉的,不影响效率的判断;
比较2n+2和2n^2+2的效率时,当n的值越来越大时,常数、最高项的系数是可以省略的掉的,不影响效率的判断;
比较2n^2+n+2和2n^3+n+2的效率时,当n的值越来越大时,常数、除最高项之外的次要项、最高项的常数是可以省略的掉的,不影响效率的判断;
总结: 当判断一个算法的效率时,函数中的常数和其他次要项常常可以省略,只需要关注最高项的阶数。
判断算法好不好时,要通过大量的数据做出准确判断,当数据较少时可能会出现以偏概全的情况。
时间复杂度
用大写O()来表示算法的时间复杂度
通过对上面的函数效率的判断, 我们可以明白计算时间复杂度也是一样的,分为以下步骤:
1.忽略掉与输入无关的代码段,如果运行函数只是加法,可以直接用常数1取代;
2.在改过的运行函数中如果存在最高阶项,则可以将次要项省略;
3.如果最高阶项存在且不是1,则去除最高阶项的常数;
4.最后再将得到的结果进行整理(去除常数1等),就是大O()。
例: 求以下代码段的时间复杂度。
n = n + 100; 执行1次 function(n); 执行n次 for(int i = 0; i < n; i++) 执行n*n次 { function(n); } for(int i = 0; i < n; i++) 执行n*(n+1)/2次 { for(int j = i; j < n; j++) { printf("Girl Generation!!!"); } } void function(int value) { for(int i = 0; i < value; i++) { printf("I LOVE YOU~"); } }
将以上执行次数加在一起就是3/2n^2+3/2n+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)
最坏运行时间: 在有n个随机数的数组中抽出一个想要的数字,时间复杂度可以是O(1),但也可以是O(n),
因此最坏运行时间是一种保证,一般我们提到运行时间都是指最坏情况的运行时间。
空间复杂度
一般我们叫我们求“复杂度”,都是指时间复杂度。
在一些存储空间较大的情况下,可以用空间复杂度换时间复杂度。
例: 判断21世纪中那些年是闰年?
第一种方法是对每一年是否是闰年进行判断,写出一些代码
第二种方法是创建一个数组,从2000到2100年按顺序排列,并把对应闰年的数组元素置为1,不是为0
第一种方式比较耗时间,第二种方式占用内存较大,比较耗空间,但是省时间。
所以在有的情况下我们可以用类似第二种方式以空间的开销换取时间的开销。