算法效率的度量方法

算法效率的度量方法

在设计算法的时候要想方设法的提高效率。这里的效率大都指算法的执行时间,那么我们如何度量一个算法的执行时间呢?

事后统计方法

这种方法主要是通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序的运行时间进行比较,从而确定算法效率的高低。

但这种方法是有很大的缺陷的:必须依据算法事先编制好程序,这通常要花费大量的时间和精力。如果编制出来发现它根本是很糟糕的算法,相当于白费力气!时间的比较依赖计算机硬件和软件等环境因素,有时会掩盖算法本身的优劣。算法的测试数据设计困难,并且程序的运行时间往往还与测试数据的规模有很大关系,效率高的算法在小的测试数据面前往往得不到体现,比如19个数字 的排序,不管用什么算法,差异几乎是零,而如果有一百万个随机数字排序,那不同算法差异就非常大了,到底用多少个数据来测试,这是很难判断的问题。

基于事后统计方法的这样那样的缺陷,我们考虑不予采纳。

事前分析估算方法

事前分析估算方法:在计算机程序编制前,依据统计方法对算法进行估算。

经过人们的分析发现,一个用高级程序语言编写的程序在计算机上运行时所消耗的时间取决于下列因素:1.算法采用的策略、方法。2.编译产生的代码质量。3.问题的输入规模。4.机器执行指令的速度。其中,第1条是算法好怀的根本,第2条要有软件来支持,第4条要看硬件性能。也就是说,抛开计算机硬件、软件有关的因素,一个程序的运行时间,依赖于算法的好坏和问题的输入规模。

下面,我们来举个例子:

两种求和的方法
第一种算法:

int i,sum = 0,n = 100;		/* 执行1次 */
for ( i = 1; i <= n; i++ )	/* 执行了n+1次 */
{
     
	sum = sum + i;			/* 执行n次 */
}
printf("%d",sum);			/* 执行1次 */

第二种算法:

int sum = 0,n = 100;		/* 执行1次 */
sum = ( 1 + n ) * n / 2;	/* 执行1次 */
printf("%d",sum);			/* 执行1次 */

显然,第一种算法,执行了1+(n+1)+n+1 = 2n+3次;而第二种算法,是1+1+1 = 3次。通过观察发现两个算法第一条和最后一条语句时一样的,所以我们关注的代码其实是中间的那部分,我们把循环看作是一个整体,忽略头尾循环判断的开销,那么这两个算法其实就是n次与1次的差距,算法好坏显而易见。

接下来吧上面的例子做一个延伸:

int i, j, x = 0, sum = 0, n = 100;		/* 执行1次 */
for (i = 1; i <= n; i++) 
{
     
	for (j = 1; j <= n; j++)
	{
     
		x++;		/* 执行n*n次 */
		sum = sum + x;
	}
}
printf("%d",sum);	/* 执行1次 */

这个例子中,i从1到100,每次都要让j循环100次,而当中的x++和sum = sum + x;其实就是1+2+3+…+10000,即10000次,所以这个算法当中,循环部分的代码整体需要执行n的平方次。显然这个算法对于同样的输入规模n = 100,要多于前面两种算法,这个算法的执行时间随着n的增加也将远远多于前面两个。

综上可以看出:测试运行时间最可靠的方法就是计算对运行时间有消耗的基本操作的执行次数,运行时间与这个成正比。

再进一步的思考:我们不关心编写程序的语言是什么,也不关心这些程序将跑在什么样的计算机中,我们只关心它所实现的算法。这样,不计那些循环索引的递增和循环终止条件、变量声明、打印结果等操作,最终,在分析程序的运行时间时,最重要的是把程序看成是独立于程序设计语言的算法或一系列步骤。

可以从问题描述中得到启示,同样问题的输入规模是n,求和算法的第一种,求1+2…+n需要一段代码运行n次,那么这个问题的输入规模使得操作数量是f(n) = n,显然运行100次的同一段代码规模是运算10次的10倍;而第二种,无论n为多少,运行次数都为1,即f(n) = 1;第三种,运算100次是运算10次的1000倍,因为它是f(n) = n2(平方)。

我们在分析一个算法的运行时间时,重要的是把基本操作的数量与输入规模关联起来,即基本操作的数量必须表示成输入规模的函数,如f(n) = n。

你可能感兴趣的:(数据结构与算法,算法)