<数据结构与算法分析>读书笔记--运行时间中的对数及其分析结果的准确性
分析算法最混乱的方面大概集中在对数上面。我们已经看到,某些分治算法将以O(N log N)时间运行。此外,对数最常出现的规律可概括为下列一般法则:
如果一个算法用常数时间(O(1))将问题的大小削减为其一部分(通常是1/2),那么该算法就是O(logN)。另一方面,如果使用常数时间只是把问题减少一个常数的数量(如将问题减少1),那么这种算法就是O(N)的。
下面,提供了具有对数特点的三个例子,分别为如下:
1.折半查找;
2.欧几里得算法;
3.幂运算;
一、折半查找
第一个例子通常叫做折半查找。
折半查找:给定一个整数X和整数A0,A1,.....,AN-1,后者已经预先排序并在内存中,求下标i使得Ai =X,如果X不在数据中,则返回i=-1。
明显的解法是从左到右扫描数据,其运行花费线性时间。然而,这个算法没有用到该表已经排序的事实,这就使得算法很可能不是最好的。一个好的策略是验证X是否是居中的元素。
如果是,则答案就找到了。如果X小于居中元素,那么我们可以应用同样的策略于居中元素左边已排序的子序列;同理,如果X大于居中元素,那么我们检查数据的右半部分。(同样,也存在可能会终止的情况)。
示例一(反映了Java语言数据下标从0开始的惯例):
public static <AnyType extends Comparable<? super AnyType>> int binarySearch(AnyType[] a,AnyType x) {
int low = 0,high = a.length-1;
while(low <=high) {
int mid = (low+high)/2;
if(a[mid].compareTo(x) < 0)
low = mid + 1;
else if (a[mid].compareTo(x) > 0)
high = mid - 1;
else
return mid;
}
return -1;//NOT_FOUND
}
显然,每次迭代在循环内的所有工作花费O(1),因此分析需要确定循环的次数。循环从high-low=N-1开始,并保持high-low >= -1。
每次循环后high-low的值至少将该次循环前的值折半;于是,循环的次数最多为[log(N-1)]+2。(例如,若high-low=128,则在各次迭代后high-low的最大值是64,32,16,8,4,2,1,0,-1。)因此,运行时间是O(logN)。与此等价,我们也可以写出运行时间的递推公式,不过,当我们理解实际在做什么以及为什么的原理时,这种强行写公式的做法通常没有必要。
折半查找可以看作是我们的第一个数据结构实现方法,它提供了在O(logN)时间内的contains操作,但是所有其他操作(特别是insert操作)均需要O(N)时间。在数据是稳定(即不允许插入操作和删除操作)的应用中,这种操作可能是非常有用的。此时输入数据需要一次排序,但是此后的访问会很快。有个例子是一个程序,它需要保留(产生于化学和物理领域的)元素周期表的信息。这个表是相对稳定的,因为很少会加进新的元素。元素名可以始终是排序的。由于只有大约110种元素,因此找出一个元素最多需要访问8次。要是执行顺序查找就会需要多得多的访问次数。
二、欧几里得算法
第二个例子是计算最大公因数的欧几里得算法。两个整数的最大公因数(gcd)是同时整除二者的最大整数。于是,gcd(50,15)=5。
示例二(所示的算法计算gcd(M,N),假设M>=N(如果N>M,则循环的第一次迭代将它们互相交换):
public static long gcd(long m,long n) {
while( n != 0) {
long rem = m % n;
m = n;
n = rem;
}
return m;
}
算法连续计算余数直到余数是0为止,最后的非零余数就是最大公因数。因此,如果M=1989和N=1590,则余数序列是399,393,6,3,0。从而,gcd(1989,1590)=3。正如例子所表明的,这是一个快速算法。
如前所述,估计算法的整个运行时间依赖于确定余数序列究竟有多长。虽然logN看似像理想中的答案,但是根本看不出余数的值按照常数因子递减的必然性,因为我们看到,例中的余数从399仅仅降到393事实上,在一次迭代中余数并不按照一个常数因子递减。然而,我们可以证明,在两次迭代以后,余数最多是原始值的一半。这就证明了,迭代次数至多2 log N = O(logN)从而得到运行时间。这个证明并不难,因此我们将它放在这里,可从下列定理直接推出它。
定理2.1
如果M>N,则M mod N < M/2。
证明:
存在两种情形。如果N<=M/2,则由于余数小于N,故定理在这种情形下成立。另一种情形是N>M/2。但是此时M仅含有一个N从而余数为M-N < M/2,定理得证。
从上面的例子来看,2 log N 大约为20,而我们仅进行了7次运算,因此有人会怀疑这是不是可能的最好的界。事实上,这个常数在最坏的情况下还可以稍微改进1.44 log N(如M和N是两个相邻的斐波那契数时就是这种情况)。欧几里得算法在平均情况下的性能需要大量篇幅的高度复杂的数学分析,其迭代的平均次数约为(12 ln 2 lnN)/π2 + 1.47。
三、幂运算
最后一个例子是处理一个整数的幂(它还是一个整数)。由取幂运算得到的数一般都是相当大的,因此,我们只能在假设有一台机器能够存储这样一些大整数(或有一个编译程序能够模拟它)的情况下进行我们的分析。我们将用乘法的次数作为运行时间的度量。
计算XN 的明显的算法是使用N-1次乘法自乘。有一种递归算法效果较好。N<=1是这种递归的基准情形。否则,若N是偶数,我们有XN = XN/2 . XN/2 ,如果N是奇数,则XN = X(N-1)/2 .
X(N-1)/2 .X。
例如,为了计算X62,算法将如下进行,它只用到9次乘法:
X3 = (X3)X ,X7 = (X3)2X,X15 = (X7)2X,X31 = (X15)2X,X62 = (X31)2
显然,所需要的乘法次数最多是2logN,因为把问题分半最多需要两次乘法(如果N是奇数)。
这里,我们又可以写出一个递推公式并将其解出。简单的直觉避免了盲目的强行处理。
示例三:
public static long pow(long x , int n) {
if( n == 0)
return 1;
if( n == 1)
return x;
if( isEven(n))
return pow(x * x,n/2);
else
return pow(x * x,n/2) * x;
}
public static boolean isEven(int n) {
return (n % 2 == 0);
}
关于分析结果的准确性:
根据经验,有时分析会估计过大。如果这种情况发生,那么或者需要进一步细化分析(一般通过机敏的观察),或者可能是平均运行时间显著小于最坏情形的运行时间,不可能对所得的界再加以改进。对于许多复杂的算法,最坏的界通过某个坏的输入是可以达到的,但在实践中情形下仍然悬而未决),而最坏情形的界尽管过分地悲观,但却是最好的已知解析结果。
示例源码地址为:https://github.com/youcong1996/The-Data-structures-and-algorithms/tree/master/algorithm_analysis
<数据结构与算法分析>读书笔记--运行时间中的对数及其分析结果的准确性的更多相关文章
- <数据结构与算法分析>读书笔记--运行时间计算
有几种方法估计一个程序的运行时间.前面的表是凭经验得到的(可以参考:<数据结构与算法分析>读书笔记--要分析的问题) 如果认为两个程序花费大致相同的时间,要确定哪个程序更快的最好方法很可能 ...
- <数据结构与算法分析>读书笔记--最大子序列和问题的求解
现在我们将要叙述四个算法来求解早先提出的最大子序列和问题. 第一个算法,它只是穷举式地尝试所有的可能.for循环中的循环变量反映了Java中数组从0开始而不是从1开始这样一个事实.还有,本算法并不计算 ...
- <数据结构与算法分析>读书笔记--函数对象
关于函数对象,百度百科对它是这样定义的: 重载函数调用操作符的类,其对象常称为函数对象(function object),即它们是行为类似函数的对象.又称仿函数. 听起来确实很难懂,通过搜索我找到一篇 ...
- <数据结构与算法分析>读书笔记--利用Java5泛型实现泛型构件
一.简单的泛型类和接口 当指定一个泛型类时,类的声明则包括一个或多个类型参数,这些参数被放入在类名后面的一对尖括号内. 示例一: package cn.generic.example; public ...
- <数据结构与算法分析>读书笔记--数学知识复习
数学知识复习是<数据结构与算法分析>的第一章引论的第二小节,之所以放在后面,是因为我对数学确实有些恐惧感.不过再怎么恐惧也是要面对的. 一.指数 基本公式: 二.对数 在计算机科学中除非有 ...
- <数据结构与算法分析>读书笔记--实现泛型构件pre-Java5
面向对象的一个重要目标是对代码重用的支持.支持这个目标的一个重要的机制就是泛型机制:如果除去对象的基本类型外,实现的方法是相同的,那么我们就可以用泛型实现来描述这种基本的功能. 1.使用Object表 ...
- <数据结构与算法分析>读书笔记--要分析的问题
通常,要分析的最重要的资源就是运行时间.有几个因素影响着程序的运行时间.有些因素(如使用编译器和计算机)显然超出了任何理论模型的范畴,因此,虽然它们是重要的,但是我们在这里还是不能考虑它们.剩下的主要 ...
- <数据结构与算法分析>读书笔记--模型
为了在正式的构架中分析算法,我们需要一个计算模型.我们的模型基本上是一台标准的计算机,在机器中指令被顺序地执行.该模型有一个标准的简单指令系统,如加法.乘法.比较和赋值等.但不同于实际计算机情况的是, ...
- <数据结构与算法分析>读书笔记--递归
一.什么是递归 程序调用自身的编程技巧称为递归( recursion).递归做为一种算法在程序设计语言中广泛应用. 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的 ...
随机推荐
- CentOS配置VSFTP服务
1.安装vsftpd a.查看是否安装vsftp [root@wsyjlly ~]# rpm -q vsftpd package vsftpd is not installed b.如果没有则安装vs ...
- java_分数
题目内容: 设计一个表示分数的类Fraction.这个类用两个int类型的变量分别表示分子和分母. 这个类的构造函数是: Fraction(int a, int b) 构造一个a/b的分数. 这个类要 ...
- 从零开始学习html(二)认识标签(第一部分)——上
一.语义化,让你的网页更好的被搜索引擎理 学习html标签过程中,主要注意两个方面的学习:标签的用途.标签在浏览器中的默认样式. 标签的用途:我们学习网页制作时,常常会听到一个词,语义化. 那么什么叫 ...
- 安全测试 WEB安全测试手册
WEB安全测试手册 By:授客 QQ:1033553122 欢迎加入软件性能测试交流QQ群:7156436 概述 Ø 目的 Ø 适用读者 Ø 适用 ...
- Linux 时间及时区设置
时间以及时区设置 by:授客 QQ:1033553122 1.首先确认使用utc还是local time. UTC(Universal Time Coordinated)=GMT(Greenwich ...
- 《数据库系统概念》11-扩展的E-R特性
虽然基本的E-R特性可以应对大部分数据库建模,但为了使用方便,也提供扩展的E-R特性 一.特化Specialization有时一个实体集会保护若干子集,这些子集各自具有不同的属性.比如person可 ...
- MySQL四种隔离级别和MVCC
事务在一个数据库中的地位尤为重要,尤其是高并发的场合.保证数据库操作的原子性和错误出现情况下的回滚,对数据的安全性和可靠性提供了保障.事务有四大原则,即ACID原则.网上关于这个问题的文章有很多,读者 ...
- Hexo + Github 个人博客设置以及优化
原文地址: Hexo + Github 个人博客设置以及优化 一.博客设置 分类.标签云.关于等页面 在站点目录下分别执行: hexo new page "categories" ...
- 将 Azure 文件共享用于 Windows VM
可将 Azure 文件共享用作一种从 VM 中存储和访问文件的方式. 例如,你可以存储一个要与所有 VM 共享的脚本或应用程序配置文件. 本主题将介绍如何创建和装载 Azure 文件共享,以及如何上传 ...
- openldap系列
openldap系列 阅读视图 系列介绍 openldap系列目录 1. 系列介绍 本系列文档大部分来自于郭大勇老师的<OpenLDAP实战指南>,少部分来自于互联网.所有文档均已经过本人 ...