您的位置:首页 > 其它

如何评价程序的好坏(理论部分)

2006-05-10 20:10 330 查看
我们经常看到一些人说别人的程序如何如何的好或者是如何如何的差,那么到底什么样的程序就算好呢,有没有什么标准呢,今天就说说如何去评价一个程序!!!
1. 任何一个程序都有空间和时间方面的问题,如何处理好这两者之间的关系是关系程序好坏的一个重要方面.
所谓程序性能( program performance),是指运行一个程序所需要的内存大小和时间。
可以采用两种方法来确定一个程序的性能,一个是分析的方法,一个是实验的方法。在进行
性能分析( performance analysis )时,采用分析的方法,而在进行性能测量( p e r f o r m a n c e
m e a s u r e m e n t)时,借助于实验的方法。
程序的空间复杂性(space complexity)是指运行完一个程序所需要的内存大小。对一个程
序的空间复杂性感兴趣的主要原因如下:
• 如果程序将要运行在一个多用户计算机系统中,可能需要指明分配给该程序的内存大小。
• 对任何一个计算机系统,想提前知道是否有足够可用的内存来运行该程序。
• 一个问题可能有若干个内存需求各不相同的解决方案。比如,对于你的计算机来说,某
个C + +编译器仅需要1 M B的空间,而另一个C + +编译器可能需要4 M B的空间。如果你的计算机
中内存少于4 M B,你只能选择1 M B的编译器。如果较小编译器的性能比得上较大的编译器,即
使用户的计算机中有额外的内存,也宁愿使用较小的编译器。
• 可以利用空间复杂性来估算一个程序所能解决的问题的最大规模。例如,有一个电路模
拟程序,用它模拟一个有c个元件、w个连线的电路需要2 8 0 K + 1 0 *(c+w)字节的内存。如果
可利用的内存总量为6 4 0 K字节,那么最大可以模拟c+w≤3 6 K的电路。
程序的时间复杂性( time complexity)是指运行完该程序所需要的时间。对一个程序的时
间复杂性感兴趣的主要原因如下:
• 有些计算机需要用户提供程序运行时间的上限,一旦达到这个上限,程序将被强制结束。
一种简易的办法是简单地指定时间上限为几千年。然而这种办法可能会造成严重的财政问题,
因为如果由于数据问题导致你的程序进入一个死循环,你可能需要为你所使用的机时付出巨额
资金。因此我们希望能提供一个稍大于所期望运行时间的时间上
正在开发的程序可能需要提供一个满意的实时响应。例如,所有交互式程序都必须提
供实时响应。一个需要1分钟才能把光标上移一页或下移一页的文本编辑器不可能被众多的
用户接受;一个电子表格程序需要花费几分钟才能对一个表单中的单元进行重新计值,那
么只有非常耐心的用户才会乐意使用它;如果一个数据库管理系统在对一个关系进行排序
时,用户可以有时间去喝两杯咖啡,那么它也很难被用户接受。为交互式应用所设计的程
序必须提供满意的实时响应。根据程序或程序模块的时间复杂性,可以决定其响应时间是
否可以接受,如果不能接受,要么重新设计正在使用的算法,要么为用户提供一台更快的
计算机。
• 如果有多种可选的方案来解决一个问题,那么具体决定采用哪一个主要基于这些方案之
间的性能差异。对于各种解决方案的时间及空间复杂性将采用加权的方式进行评价。
2.空间复杂性
程序所需要的空间主要由以下部分构成:
• 指令空间(instruction space) 指令空间是指用来存储经过编译之后的程序指令所需的
空间。
• 数据空间( data space) 数据空间是指用来存储所有常量和所有变量值所需的空间。数
据空间由两个部分构成:
1) 存储常量和简单变量所需要的空间。
2) 存储复合变量所需要的空间。这一类空间包括数据结构所需的空间及动态分配的空间。
• 环境栈空间(environment stack space) 环境栈用来保存函数调用返回时恢复运行所需要的信息。例如,如果函数f u n 1调用了函数f u n 2,那么至少必须保存f u n 2结束时f u n 1将要继续执行的指令的地址。

一个程序所需要的空间可以分成两部分:
• 固定部分,它独立于实例的特征。一般来说,这一部分包含指令空间(即代码空间)、简
单变量及定长复合变量所占用空间、常量所占用空间等等。
• 可变部分,它由以下部分构成:复合变量所需的空间(这些变量的大小依赖于所解决的
具体问题),动态分配的空间(这种空间一般都依赖于实例的特征),以及递归栈所需的空间
(该空间也依赖于实例的特征)。
任意程序P 所需要的空间S(P)可以表示为:
S (P) = c + Sp(实例特征)
其中c 是一个常量,表示固定部分所需要的空间, Sp 表示可变部分所需要的空间。一个精确的分析还应当包括在编译期间所产生的临时变量所需要的空间(如图2 - 1所示),这种空间是与编译器直接相关的,除依赖于递归函数外,它还依赖于实例的特征。在分析程序的空间复杂性时,我们将把注意力集中在估算Sp(实例特征)上。对于任意给定的问题,首先需要确定实例的特征以便于估算空间需求。实例特征的选择是一个很具体的问题,我们将求助于介绍各种可能性的实际例子。一般来说,我们的选择受到相关数的数量以及程序输入和输出的规模的限制。有时还会使用更复杂的估算数据,这些数据来自于数据项之间的相互关系
实例分析:
template<class T>
T Abc(T& a, T& b, T& c)
{
return a+b+b*c+(a+b-c)/(a+b)+4;
}
在估算Sp 之前,必须选择分析所使用的实例特征。两种可能性是:
( 1 )数据类型T;(2) a, b 和c 的大小。
假定使用T作为实例特征。由于a, b和c是引用参数,所以在函数中不需要为它们的值分配空间,但是必须保存指向这些参数的指针。如果每个指针需要4个字节,那么共需要12个字节的指针空间。因此函数所需要的总空间是一个常量,SA b c (实例特征) = 0。
如果函数Abc 的参数是传值参数,那么每个参数需要分配大小为sizeof (T) 的空间。在本例中,a, b 和c 所需要的空间为3*sizeof (T) 。所需要的其他空间都独立于T。因此SA b c (实例特征) =3*sizeof (T)。
如果使用a, b 和c 的大小作为实例特征,则不管使用引用参数还是使用传值
参数,都有SA b c (实例特征) = 0。注意在传值参数的情形下,分配给每个a , b和c的空间均为s i z e o f ( T ),而不考虑存储在这些变量中的实际值是多大。例如,如果T是d o u b l e类型,那么每个字节将被分配8个字节的空间。
说明一下计算空间复杂度的一般方法:
计算程序占的总的空间的大小,如果与想要的变量没有关系,那么S(变量)=0;
3.时间复杂性
一个程序P所占用的时间T (P)=编译时间+运行时间。编译时间与实例的特征无关。另外,
可以假定一个编译过的程序可以运行若干次而不需要重新编译。因此我们将主要关注程序的运
行时间。运行时间通常用“t p (实例特征)”来表示。
分析时间的复杂性比较麻烦的,一般用两个方法来估算运行时间:
(1)找出一个或多个关键操作,确定这些关键操作所需要的执行时间;
(2).确定程序总的执行步数

令P 是一个程序。假定把操作计数OP (n1, n2, ..., nk ) 视为实例特征n1, n2, ..., nk 的函数。对于任意程序实例I, 令o p e r a t i o nP (I) 为该实例的操作计数,令S(n1, n2, ..., nk ) 为集合{I |I 具有特征n1, n2, ..., nk }。则P最好的操作计数为:
OPB C (n1, n2 ..., nk ) = m i n { o p e r a t i on P (I ) | I∈S(n1, n2, ..., nk ) }
P最坏的操作计数为:
OPW C (n1, n2 ..., nk ) =m a x { o p e r a t i o nP (I ) | I∈S(n1, n2, ..., nk ) }
4.符号描述程序的时间和空间复杂性
(1).大写O符号
大写O 符号给出了函数f的一个上限。
定义[大写O符号] f (n)=O (g (n)) 当且仅当存在正的常数c 和n0,使得对于所有的n≥n0 , 有
f (n) ≤c g(n)。
上述定义表明,函数f 顶多是函数g 的c 倍,除非n 小于n0 。因此对于足够大的n (如n≥n0 ),
g 是f 的一个上限(不考虑常数因子c)。在为函数f 提供一个上限函数g 时,通常使用比较简单的函数形式。比较典型的形式是含有n 的单个项(带一个常数系数)。列出了一些常用的g 函数及其名称。对于图中的对数函数l o g n,没有给出对数基,原因是对于任何大于1的常数a 和b 都有l o ga n = logb n / l o gb a, 所以l o ga n 和l o gb n 都有一个相对的乘法系数1/ l o gb a ,其中a是一个常量。



示例:
[线性函数] 考察f (n) = 3n+ 2。当n≥2时,3n+ 2≤3n+n=4n,所以f (n)=O (n),f (n)是一
个线性变化的函数;
[平方函数] 假定f (n) = 10n2 + 4n + 2 。对于n≥2, 有f (n)≤1 0n2 + 5 n。由于当n≥5时有
5n≤n2 ,因此对于n≥n0 = 5,f (n) ≤1 0n2 + n2 = 11n2 ,所以f (n)= O (n2 )。
[指数函数] 考察一个具有指数复杂性的例子f (n)= 6 * 2n + n2 。可以观察到对于n≥4,
有n2 ≤2n,所以对于n≥4,有f (n)≤ 6 * 2n + 2n = 7 * 2 n ,因此6 * 2n+ n2 = O (2n )
[常数函数] 当f (n)是一个常数时,比如f (n) = 9或f (n) = 2 0 3 3,可以记为f (n)=O (1)。这种写法的正确性很容易证明。例如,对于f (n) = 9≤9 * 1,只要令c=9 以及n0 =0 即可得f (n)=O (1)。同样地,对于f (n) = 2033≤2 0 3 3 * 1,只要令c= 2 0 3 3以及n0 = 0即可;

注意f (n) = O (g (n)) 并不等价于O (g (n)) = f (n)。实际上,O (g (n)) = f (n) 是无意义的。在这里,使用符号=是不确切的,因为=通常表示相等关系。可以通过把符号=读作“是”而不是“等于”来避免这种矛盾。
为了方便大家得到g(n),给出下面几个定理:

定理2-2 [大O比率定理] 对于函数f (n)和g (n),若limn→∞f (n) / g (n) 存在,则f (n)=O (g (n) )当且仅存在确定的常数c,有limn→∞f (n) /g(n)≤c。
证明如果f (n)=O (g (n) ),则存在c> 0及某个n0,使得对于所有的n≥n0,有f (n) /g(n)≤c,因此limn→∞f (n) /g(n)≤c。接下来假定limn→∞f (n) /g(n)≤c,它表明存在一个n0,使得对于所有的n≥n0,有f (n)≤m a x{1, c} *g (n)。
例2-31 因为limn→∞( 3n+ 2 ) /n = 3,所以3n+2=O (n);因为limn→∞( 1 0n2+ 4n+ 2 ) /n2 = 1 0,所以1 0n2
+ 4n+2= O(n2 );因为limn→∞( 6 * 2n + 2n ) / 2n = 6,所以6 * 2n + n2 =O (2n );因为limn→∞( 2n2-3 ) /n4 = 0,所以2n2-3=O (n4 );因为limn→∞( 3n2+ 5 ) /n=∞,所以3n2 + 5≠O (n)。
(2).W符号
W符号与大O 符号类似,它用来估算函数f 的下限值。
定义[符号]f(n)=(g (n)) 当且仅当存在正的常数c 和n0,使得对于所有的n≥n0,有f (n)≥cg(n)。
使用定义f (n) =(g (n)) 是为了表明,函数f 至少是函数g 的c 倍,除非n 小于n0。因此对于足够大的n (如n≥n0 ),g 是f 的一个下限(不考虑常数因子c)。与大O 定义的应用一样,通常
仅使用单项形式的g 函数。
(3).小写o符号
定义[小写o] f(n)=o(g(n))当且仅当f(n)=O(g(n))且f(n)≠(g(n))。
例2-38 [小写o] 因为3n+ 2 = O (n2 )且3n+ 2≠(n2 ),所以3n+ 2 = o (n2 ),但3n+ 2≠o (n)。类似地,10n2 + 4n+ 2 = o(n3 ),但10n2 +4n+ 2≠o(n2 )。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐