事后统计方法
事后统计方法:这种方法主要是通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序的运行时间进行比较,从而确定算法效率的高低。
但这种方法显然是有很大缺陷的:
必须依据算法事先编制好程序,这通常需要花费大量的时间和精力。如果编制出来发现它根本是很糟糕的算法,不是竹篮打水一场空吗?
事前分析估算方法
事前分析估算方法:在计算机程序编写前,依据统计方法对算法进行估算。
经过分析,我们发现,一个用高级程序语言编写的程序在计算机上运行时所消耗的时间取决于下列因素:
1.编译产生的代码质量。
2.算法采用的策略、方法。
3.问题的输入规模。
4.机器执行指令的速度。
由此可见,抛开这些与计算机、软件有关的因素,一个程序的运行时间依赖于算法的好坏和问题的输入规模。
简单算法比较
计算1+2+3+...+100的值
第一种算法:
int i, sum = 0,n = 100; /*执行1次*/
for(i = 1; i < = n; i++) /*执行了n+1次*/
{
sumsum = 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次的差距
为什么在这里我们要看成n次与1次的差距,而不是精确到2n+1 与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次*/
sumsum = sum + x;
}
}
printf("%d", sum); /*执行1次*/
这个例子中,i从1到100,每次都要让j循环100次,而当中的x++和sum = sum + x;其实就是1+2+3+…+10000,也就是1002次,所以这个算法当中,循环部分的代码整体需要执行n2(忽略循环体头尾的开销)次。显然这个算法的执行次数对于同样的输入规模n = 100,要多于前面两种算法,这个算法的执行时间随着n的增加也将远远多于前面两个。
此时你会看到,测定运行时间最可靠的方法就是计算对运行时间有消耗的基本操作的执行次数。运行时间与这个计数成正比。
我们不关心编写程序所用的程序设计语言是什么,也不关心这些程序将跑在什么样的计算机中,我们只关心它所实现的算法。这样,不计那些循环索引的递增和循环终止条件、变量声明、打印结果等操作,最终,在分析程序的运行时间时,最重要的是把程序看成是独立于程序设计语言的算法或一系列步骤。
可以从问题描述中得到启示,同样问题的输入规模是n,求和算法的第一种,求1+2+…+n需要一段代码运行n次。那么这个问题的输入规模使得操作数量是f(n) = n,显然运行100次的同一段代码规模是运算10次的10倍。而第二种,无论n为多少,运行次数都为1,即f(n) = 1;第三种,运算100次是运算10次的100倍。因为它是f(n) = n2。
我们在分析一个算法的运行时间时,重要的是把基本操作的数量与输入规模关联起来,即基本操作的数量必须表示成输入规模的函数。
函数的渐近增长在说函数的渐近增长的例子前,先说说概念,
函数的渐近增长:给定两个函数f(n)和g(n),如果存在一个整数N,使得对于所有的n > N,f(n)总是比g(n)大,那么,我们说f(n)的渐近增长快于g(n)。
文字说明,比较难理解,我们利用下面的表格来说明
注意:n^2代表n 的平方,n^3代表n的立方
数值\函数 | n | 2n | 2n+1 | 3n+8 | n^2 | 2n^2 | 2n^2+2n+1 | n^3 |
---|---|---|---|---|---|---|---|---|
1 | 1 | 2 | 3 | 11 | 1 | 2 | 5 | 1 |
2 | 2 | 4 | 5 | 14 | 4 | 8 | 13 | 8 |
3 | 3 | 6 | 7 | 17 | 9 | 18 | 25 | 27 |
5 | 5 | 10 | 11 | 23 | 25 | 50 | 61 | 125 |
9 | 9 | 18 | 19 | 35 | 81 | 162 | 181 | 729 |
10 | 10 | 20 | 21 | 38 | 100 | 200 | 221 | 1000 |
100 | 100 | 200 | 201 | 308 | 10000 | 20000 | 20201 | 1000000 |
1000 | 1000 | 2000 | 2001 | 3008 | 1000000 | 2000000 | 2002001 | 1000000000 |
10000 | 10000 | 20000 | 20001 | 30008 | 100000000 | 200000000 | 200020001 | 1000000000000 |
100000 | 100000 | 200000 | 200001 | 300008 | 10000000000 | 20000000000 | 20000200001 | 1000000000000000 |
例如:f(n)=2n^2+1,g(n)=2n+1
当n=1是f(n)=g(n),这个时候对应上面的概念,N=1,当n>N,也就是当n>1时,f(n)>g(n),所以,我们说f(n)的渐近增加快于g(n)
为了更好理解这些特点,我做了一些图表,以便更加清楚的知道为什么
当输入数值非常大的时候,两条曲线基本重叠,或者可以说看作重叠,所以得出结论是,2n+1其中里面作为常数的1,在输入数值大到一定程度,他对于函数的影响可以忽略不计,这时候的1就被看作是次要项
同样,2n^2+2n+1
由于渐近增长是可以看作是一种抽象,所以他的对比具有一些特点:
1.注意关注函数最高次幂的变化
2.忽略次要项与乘数
特别感谢:
raylee2007的专栏