|
|
51CTO旗下网站
|
|
移动端

2.4.2 一般法则

《数据结构与算法分析:Java语言描述(原书第3版)》第2章算法分析,本章对如何分析程序的复杂性给出一些提示。遗憾的是, 它并不是完善的分析指南。简单的程序通常给出简单的分析, 但是情况也并不总是如此。本节为大家介绍一般法则。

作者:冯舜玺/陈越 译来源:机械工业出版社|2016-04-13 11:33

2.4.2 一般法则

法则1——for循环

一个for循环的运行时间至多是该for循环内部那些语句(包括测试)的运行时间乘以迭代的次数。

法则2——嵌套的for循环

从里向外分析这些循环。在一组嵌套循环内部的一条语句总的运行时间为该语句的运行时间乘以该组所有的for循环的大小的乘积。

例如, 下列程序片段为O(N2):

法则3——顺序语句

将各个语句的运行时间求和即可(这意味着, 其中的最大值就是所得的运行时间; 见2.1节中的法则1(a))。

例如, 下面的程序片段先是花费O(N), 接着是O(N2), 因此总量也是O(N2):

法则4——if/else语句

对于程序片段

一个if/else语句的运行时间从不超过判断的运行时间再加上S1和S2中运行时间长者的总的运行时间。

显然在某些情形下这么估计有些过头, 但决不会估计过低。

其他的法则都是显然的, 但是, 分析的基本策略是从内部(或最深层部分)向外展开工作的。如果有方法调用, 那么要首先分析这些调用。如果有递归过程, 那么存在几种选择。若递归实际上只是被薄面纱遮住的for循环, 则分析通常是很简单的。例如, 下面的方法实际上就是一个简单的循环从而其运行时间为O(N):

实际上这个例子对递归的使用并不好。当递归被正常使用时, 将其转换成一个循环结构是相当困难的。在这种情况下, 分析将涉及求解一个递推关系。为了观察到这种可能发生的情形, 考虑下列程序, 实际上它对递归使用的效率低得令人惊诧。

初看起来, 该程序似乎对递归的使用非常聪明。可是, 如果将程序编码并在N值为40左右时运行, 那么这个程序让人感到效率低得吓人。分析是十分简单的。令T(N)为调用函数fib(n)的运行时间。如果N=0或N=1, 则运行时间是某个常数值, 即第1行上做判断以及返回所用的时间。因为常数并不重要, 所以我们可以说T(0)=T(1)=1。对于N的其他值的运行时间则相对于基准情形的运行时间来度量。若N>2, 则执行该方法的时间是第1行上的常数工作加上第3行上的工作。第3行由一次加法和两次方法调用组成。由于方法调用不是简单的运算, 因此必须用它们自己来分析它们。第一次方法调用是fib(n-1), 从而按照T的定义它需要T(N-1)个时间单元。类似的论证指出, 第二次方法调用需要T(N-2)个时间单元。此时总的时间需求为T(N-1)+T(N-2)+2, 其中2指的是第1行上的工作加上第3行上的加法。于是对于N≥2, 有下列关于fib(n)的运行时间公式:

T(N)=T(N-1)+T(N-2)+2

但是fib(N)=fib(N-1)+fib(N-2), 因此由归纳法容易证明T(N)≥fib(N)。在1.2.5节我们证明过fib(N)<(5/3)N, 类似的计算可以证明(对于N>4)fib(N)≥(3/2)N,  从而这个程序的运行时间以指数的速度增长。这大致是最坏的情况。通过保留一个简单的数组并使用一个for循环, 运行时间可以显著降低。

这个程序之所以运行缓慢, 是因为存在大量多余的工作要做, 违反了在1.3节中叙述的递归的第四条主要法则(合成效益法则)。注意, 在第3行上的第一次调用即fib(n-1)实际上在某处计算fib(n-2)。这个信息被抛弃而在第3行上的第二次调用时又重新计算了一遍。抛弃的信息量递归地合成起来并导致巨大的运行时间。这或许是格言“计算任何事情不要超过一次”的最好的实例, 但它不应使你被吓得远离递归而不敢使用。本书中将随处看到递归的杰出使用。

喜欢的朋友可以添加我们的微信账号:

51CTO读书频道二维码


51CTO读书频道活动讨论群:342347198

【责任编辑:book TEL:(010)68476606】

回书目   上一节   下一节
点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

Tomcat与Java Web开发技术详解

本书详细介绍了在最新Tomcat 5版本上开发Java Web应用的各种技术。主要内容包括:Tomcat和Java Web开发的基础知识,Java Web开发的高级技术...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊