一.算法分析的必要性及其采用的分析方法
必要性:如果我们不能再写程序之前就能考察其是否高效,很大概率上会写出低效的程序,极大地浪费了人力资源。
算法分析主要分析程序所占空间和其运行时间,但随着硬件行业的发展,空间资源相对充足,时间成了考察的重点。
分析其时间我们很容易想到考察具体程序的运行时间,但对于一个具体的算法我们 不能片面地通过运行时间来考察它是否高效,原因如下:
1.一个高明的程序员可能会通过剪枝等方法,让它看起来不那么低效;
2.电脑的性能的不同,同样的程序如果在一台性能很好的电脑上跑可能运行时间会在可接受的范围之内,但这并不能说明这是一个高效的算法;
3.程序运行有最好结果,最坏结果。在运行时有可能得到的是最好结果,掩盖了程序的低效。
那怎么办呢?答案是渐进分析法
渐进分析法:估算出当问题规模变大时,一种算法及实现它的程序的效率和开销。
基本操作:一个基本操作必须满足“完成该操作所需要的时间与操作数的具体取值无关”的特点。(例如两个数相加或者比较两个数的大小)
基本操作数:
我们需要一个标准量度来表达运行时间的长短,由于电脑性能差异,编译器不同等原因,渐进分析法采用了另一种尺度来代替运行时间——基本操作数
这样一个程序运行所需要的运行时间则由其运行的基本操作数代替。
判断算法性能的一个基本考虑是处理一定规模的输入时所需要执行的基本操作数
举个简单的栗子:
sum=0;
for(int i=1;i<=n;i++)
for(j=1;j<=n;j++)
sum++;
在上面的程序中问题的规模是n*n,基本操作值是sum加1,所以其基本操作数是n*n,我们用基本操作数替代了运行时间。
渐进分析中算法的增长率:指当输入的值增加时,算法代价的增长速率。
最佳,最差,平均情况:
对于计算N的阶乘的问题,只有一个输入,即给定的“大小”(也就是说,对于不同的N,都对应一个唯一的基本操作数)但现在考虑在一个数组中查找一个数,对于一个特定的N维数组,我们要查找的数即输入的数不同,其对应的运行时间,即基本操作数就很可能不同,如果我们要查找的数就在数组开头那么基本操作数就为1,这显然是最好情况,当我们要查找的数在数组的尾部,基本操作数就为N,这显然是最坏情况。而对于平均情况,只有当我们知道输入不同数字的概率分布后,才能求出平均情况(这点增加了考察其的难度)
所以这三种情况中,一般考虑地最多的是最差情况(最佳情况过于乐观,平均情况不好求得,最坏情况至少可以防止黑天鹅事件发生(抖个机灵))
上限:算法运行时间的上限,表示该算法可能有的最高增长率。(简称上限)
表示方法(大O表示法):O(f(n))
精确定义:对于非负函数T(n),如果存在两个正常数c和no,对任意n>no,有T(n)小于cf(n),则称T(n)在集合O(f(n))中
no:使上限成立的n的最小值。
某算法平均情况下T(n)=c1xnxn+c2xn;c1,c2为正数。若n>1,c1xnxn+c2xn<=c1xnxn+c2xnxn<=(c1+c2)xnxn.因此取c=c1+c2,no=1,有T(n)<=cxnxn.根据定义,T(n)在O(nxn)中
下限:算法在某类数据输入时所需要的最少资源。
表示方法:用符号Ω表示,读作”大欧米伽“
例如在数组中寻找值为K的元素的顺序搜索法,在平均情况和最差情况下,这个算法在Ω(n)中,因为在这两种情况下,都至少要坚持cn个元素(平均情况下c=1/2,最差情况下为c=1)
Θ表示法:当上限和下限相同时,可以用Θ来表示。
化简法则:
1.若f(n)在O(g(n))中,且g(n)在O(h(n))中,则f(n)在O(h(n))中。
对应的物理表现为:上限的上限,还是开始T(n)的上限
2.若f(n)在O(kg(n))中,对于任意的常数K>0成立,则f(n)在O(g(n))中。
对应的物理表现为:对应一定规模的问题,前面的常数可忽略。
3.f1(n)在O(g1(n))中,且f2(n)在O(g2(n))中,则f1(n)+f2(n)在O(max(g1(n),g2(n)))中;
对应物理意义:在程序中只需要考虑开销较大的部分。
4.f1(n)在O(g1(n))中,且f2(n)在O(g2(n))中,则f1(n)f2(n)在O(g1(n)g2(n))中
对应物理意义:循环的总开销为每次开销与重复次数之积