<数据结构与算法分析>读书笔记--运行时间计算
有几种方法估计一个程序的运行时间。前面的表是凭经验得到的(可以参考:<数据结构与算法分析>读书笔记--要分析的问题)
如果认为两个程序花费大致相同的时间,要确定哪个程序更快的最好方法很可能将它们编码并运行。
一般地,存在几种算法思想,而我们总愿意尽早除去那些不好的算法思想,因此,通常需要分析算法。不仅如此,进行分析的能力常常提供对于设计有效算法的洞察能力。一般说来,分析还能准确地确定瓶颈,这些地方值得仔细编码。
为了简化分析,我们将采纳如下的约定:不存在特定的时间单位。因此,我们抛弃一些前导的常数。我们还将抛弃低阶项,从而要做的就计算大O运行时间。由于大O是一个上界,因此我们必须仔细,绝不要低估程序的运行时间。实际上,分析的结果为程序在一定的时间范围内能够终止运行提供了保障。程序可能提起结束,但绝不可能错后。
一、一个简单的例子
package cn.simple.example; public class SimpleExample { public static int sum(int n) { int partialSum; 1 partialSum = 0; 2 for(int i = 1; i <= n;i++)
3 partialSum = i * i *i; 4 return partialSum; } }
对这个程序段的分析是简单的。所有的声明均不计时间。第1行和第4行各占一个时间单元。第3行每执行一次占用4个时间单元(两次乘法,一次加法和一次赋值),而执行N次共占用4N个时间单元。第2行在初始化i、测试i<=N和对i的自增运算隐含着开销。所有这些的总开销是初始化1个单元时间,所有的测试为N+1个单元时间,而所有的自增运算为N个单元时间,共2N+2个时间单元。我们忽略调用方法和返回值的开销,得到总量是6N+4个时间单元。因此,我们说该方法是O(N)。
如果每次分析一个程序都要演示所有这些工作,那么这项任务很快就会变成不可行的负担。幸运的是,由于我们有了大O的结果,因此就存在许多可以采取的捷径,并且不影响最后的结果。例如,第3行(每次执行时)显然是O(1)语句,因此精确计算它究竟是2、3还是4个时间单元是愚蠢的。这无关紧要。第1行与for循环相比显然是不重要的,所以在这里花费时间也是不明智的。这使我们得到若干一般法则。
二、一般法则
法则1-for循环
一个for循环的运行时间至多是该for循环内部那些语句(包括测试)的运行时间乘以迭代的次数。
法则2-嵌套的for循环
从里向外分析,在一组嵌套循环内部的一条语句总的运行时间为该语句的运行时间乘以该组所有的for循环的大小的乘积。
例如:下列程序片段为O(N的2次方):
for(i=0;i<n;i++)
for(j =0;j<n;j++)
k++
法则3-顺序语句
将各个语句的运行时间求和即可(这意味着,其中的最大值就是所得的运行时间)
例如:下面的程序片段先花费O(N),接着是O(N的2次方),因此总量也是O(N的次方):
for(i=0;i<n;i++)
a[i]=0;
for(i=0;i<n;i++)
for(j=0;j<n;j++)
a[i]+=a[j]+i+j;
法则4-if/else语句
对于程序片段
if(condition)
S1
else
S2
一个if/else语句的运行时间从不超过判断的运行时间再加上S1和S2中运行时间长者的总的运行时间。
显然在某些情形下这么估计有些过头,但决不会估计过低。
其他的法则都是显然的,但是,分析的基本策略是从内部(或最深层部分)向外展开工作的。如果有方法调用,那么要首先分析这些调用。如果有递归过程,那么存在几种选择。若递归实际上只是被薄棉纱遮住的for循环,则分析通常是很简单的。例如,下面的方法实际上就是一个简单的循环从而其运行时间为O(N):
public static long factorial(int n) {
if(n <= 1)
return 1;
else
return n*factorial(n-1);
}
实际上这个例子对递归的使用并不好。当递归被正常使用时,将其转换成一个循环结构是相当困难的。在这种情况下,分析将涉及求解一个递推关系。为了观察到这种可能发生的情形,考虑下列程序,实际上它对递归使用的效率低得令人惊诧。
public static long fib(int n) {
1 if(n<=1)
2 return 1;
else
3 return fib(n-1) +fib(n-2);
}
初看起来,该程序似乎对递归的使用非常聪明。可是,如果将程序编码并在N值为40左右时运行,那么这个程序让人感到低得吓人。分析是十分简单的。令T(N)为调用函数fib(n)的运行时间。如果N=0或N=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)。之前我们证明过fib(N)<(5/3)的N次方,类似的计算可以证明(对于N>4)fib(N)>=(3/2)的N次方,从而这个程序的运行时间以指数的速度增长。这大致是最坏的情况。通过保留一个简单的数组 并使用一个for循环,运行时间可以显著降低。
这个程序员之所以运行缓慢,是因为存在大量多余的工作要做,违反了之前叙述的递归的第四条主要法则(合成效益法则)。注意,在第3行上的第一次调用即fib(n-1)实际上在某处计算fib(n-2)。这个信息被抛弃而在第3行上的第二次调用时又重新计算了一遍。抛弃的信息量递归第合成起来并导致巨大的运行时间。这或许是格言,“计算任何事情不要超过一次”的最好实例,但它不应使你被吓得远离递归而不敢使用。
《数据结构与算法分析》这本书确实不太好读,通过将边看边用记录,总算还是注意力比较集中。但愿能够使我痛苦的能够使我变得强大。
示例代码库:https://github.com/youcong1996/The-Data-structures-and-algorithms/tree/master/algorithm_analysis
<数据结构与算法分析>读书笔记--运行时间计算的更多相关文章
- <数据结构与算法分析>读书笔记--运行时间中的对数及其分析结果的准确性
分析算法最混乱的方面大概集中在对数上面.我们已经看到,某些分治算法将以O(N log N)时间运行.此外,对数最常出现的规律可概括为下列一般法则: 如果一个算法用常数时间(O(1))将问题的大小削减为 ...
- <数据结构与算法分析>读书笔记--最大子序列和问题的求解
现在我们将要叙述四个算法来求解早先提出的最大子序列和问题. 第一个算法,它只是穷举式地尝试所有的可能.for循环中的循环变量反映了Java中数组从0开始而不是从1开始这样一个事实.还有,本算法并不计算 ...
- <数据结构与算法分析>读书笔记--函数对象
关于函数对象,百度百科对它是这样定义的: 重载函数调用操作符的类,其对象常称为函数对象(function object),即它们是行为类似函数的对象.又称仿函数. 听起来确实很难懂,通过搜索我找到一篇 ...
- <数据结构与算法分析>读书笔记--利用Java5泛型实现泛型构件
一.简单的泛型类和接口 当指定一个泛型类时,类的声明则包括一个或多个类型参数,这些参数被放入在类名后面的一对尖括号内. 示例一: package cn.generic.example; public ...
- <数据结构与算法分析>读书笔记--数学知识复习
数学知识复习是<数据结构与算法分析>的第一章引论的第二小节,之所以放在后面,是因为我对数学确实有些恐惧感.不过再怎么恐惧也是要面对的. 一.指数 基本公式: 二.对数 在计算机科学中除非有 ...
- <数据结构与算法分析>读书笔记--要分析的问题
通常,要分析的最重要的资源就是运行时间.有几个因素影响着程序的运行时间.有些因素(如使用编译器和计算机)显然超出了任何理论模型的范畴,因此,虽然它们是重要的,但是我们在这里还是不能考虑它们.剩下的主要 ...
- <数据结构与算法分析>读书笔记--模型
为了在正式的构架中分析算法,我们需要一个计算模型.我们的模型基本上是一台标准的计算机,在机器中指令被顺序地执行.该模型有一个标准的简单指令系统,如加法.乘法.比较和赋值等.但不同于实际计算机情况的是, ...
- <数据结构与算法分析>读书笔记--实现泛型构件pre-Java5
面向对象的一个重要目标是对代码重用的支持.支持这个目标的一个重要的机制就是泛型机制:如果除去对象的基本类型外,实现的方法是相同的,那么我们就可以用泛型实现来描述这种基本的功能. 1.使用Object表 ...
- <数据结构与算法分析>读书笔记--递归
一.什么是递归 程序调用自身的编程技巧称为递归( recursion).递归做为一种算法在程序设计语言中广泛应用. 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的 ...
随机推荐
- windows server 2008 R2 开启远程桌面
1.计算机 ——属性 2.远程设置 3.远程桌面-允许运行任意版本远程连接-选择用户-添加用户:Administrator;或是可以是其他非管理员用户(默认的系统管理员已经被授予了访问权限); 点确 ...
- 洛谷P4425 [HNOI/AHOI2018]转盘(线段树)
题意 题目链接 Sol 首先猜一个结论:对于每次询问,枚举一个起点然后不断等到某个点出现时才走到下一个点一定是最优的. 证明不会,考场上拍了3w组没错应该就是对的吧... 首先把数组倍长一下方便枚举起 ...
- openlayer3之高级标注实现
首先看实现效果: 实现要点: 1)树形标注实现 2)复杂标注样式定义 3)效率优化 1.树形标注实现 树形标注采用字体符号来实现,包括以下几个步骤 1)载入字体 2)设置标注值与字体对照关系 3)设置 ...
- Android View体系(三)属性动画
上一篇文章讲了View滑动的六种方法,其中一种是使用动画,这篇文章我们来讲一讲动画的其中一种:属性动画. 1.android视图动画和属性动画 视图动画我们都了解,它提供了AlphaAnimation ...
- git批量修改已经提交的commit的姓名和邮箱
首先,我们创建change.sh脚本,并根据个人信息复制以下脚本. #!/bin/sh git filter-branch --env-filter ' OLD_EMAIL="填写原来的邮箱 ...
- 用U盘制作EXSI启动盘
用U盘制作EXSI启动盘这是一个比较困难的事,一般的人会用UltraISO这个软件来制作.但是很遗憾,这样的方法很不好,我试了好几次都没有成功.主要是不能引导. 之后我换了一个刻录软件(rufus), ...
- mysql初次启动相关配置
一.启动mysql服务 使用mysql之前,首先要启动mysql服务器端.使用命令mysqld.在命令行终端先进入mysql安装的目录直到bin目录,如图: 运行这个命令,可能会报错,提示找不到dat ...
- HDMI驱动热插拔检测方法
1. 使用poll机制 1.1 如何使用? a. open("/dev/HPD"); b. poll状态发生变化 c. read确定接上还是接下 1.2 情景分析: APP使用op ...
- Centos7.5调试/etc/sysctl.conf文件导致宕机
今天安装greenplus数据库,需要调试一个核心文件/etc/sysctl.conf文件,结果导致系统异常宕机,出现的问题就是使用任何命令都不能输出正确的结果,只有这个显示: 不知道是什么原因,ls ...
- Linux 小知识翻译 - 「命令行的提示符」
这次,聊聊关于「命令行提示符」的相关内容. bash之类的Shell程序是操作Linux所不可缺少的东西.其中bash的提示符也有承担了很重要的作用. 「命令行提示符」的英文是「command pro ...