渐进分析
对于y=C1n和y=C2n2这两个式子。参数的不同只会导致两条曲线相交点的坐标不同,而不会改变他们是否相交,以及相交后的趋势。类似地,两个增长率不同的算法的时间曲线总会相交,而与系数无关。因此,当我们在估算一种算法的时间或者其他代价的时候,会经常忽略其参数。这样能简化算法分析,并使注意力集中在最重要的一点上,即增长率。这称为渐进算法分析。准确的说,渐进分析是指当输入规模很大,或者说达到极限(微积分意义上)时,对一种算法的研究。实践证明忽略这些系数很有用,因此渐进分析也广泛用于算法比较。
但是并不是任何情况都能忽略常数。当算法要解决问题的规模很小时,系数就会起到举足轻重的作用。例如,要对5个数排序,那么用来给成千上万个数排序的算法可能就很不适合,尽管它的渐进分析表明该算法的性能良好。
渐进分析是对算法资源开销的一种不精确的估算,千万不要忘记渐进分析的局限性,尤其是在那些系数也起到重要作用的少数情况下。
大O表示法
如果某种算法的增长率上限(最差情况下)是f(n),那么就说这种算法在集合O(f(n))中,或者直接说在O(f(n))中,例如,如果在最差情况下T(n)增长速度与n2相同,则称算法最差情况在O(n2)中。下面是精确的定义:
对非负函数T(n),若存在两个正常数c和n0,对任意n>n0,有T(n)<=cf(n),则称,T(n)在集合O(f(n))中。
要知道,某种算法在O(f(n))中只是说明事情顶多能坏到某种程度。事实上并不是那么糟。因此我们总是试图给算法的时间代价找到一个最紧的上限。比如说顺序搜索法在O(n)中,那么根据定义,我们也可以说它也在 O(n2)中。但是顺序搜索法对于很大的n也是可行的。其他在O(n2)中的算法就未必如此。
例1 比较下面两段程序的算法分析:
sum1=0;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
sum1++;
sum2=0;
for(i=1;i<=n;i++)
for(j=1;j<=i;j++)
sum2++;
在第一个双重熏黄中,内层for循环总是执行n次。因为外层循环执行n次,所以sum1++正好执行n2次。而第二个例子,时间代价为j从1加到n,近似于0.5n2。
例2 并非所有嵌套for循环的时间代价都是n2,以下面的两个嵌套为例。
sum1=0;
for(k=1;k<=n;k*=2)
for(j=1;j<=n;j++)
sum1++;
sum2=0;
for(k=1;k<=n;k*=2)
for(j=1;j<=k;j++)
sum2++;
为了便于分析,我们假定n是2的幂。第一段程序的外层for循环执行log(n)次。由于内层循环执行次数为n,所以程序的总时间为i从0加到log(n),即为O(nlog(n))。第二段程序的外层循环执行执行log(n)次,内层循环重复k次,并随这外层循环的增长而倍增。时间开销为2i,其中i从0增到log(n),其合为O(n).