1、算法设计与分析,主讲:李杰 安徽师范大学数学计算机科学学院 E-mail:,学习目标,掌握算法设计与分析的基本理论; 掌握进行算法设计与分析的基本方法(时间、空间复杂度分析,算法正确性的证明); 掌握计算机领域中常用的非数值计算方法,并学会用这些算法解决实际问题。,课程要求,教学方式: 理论(42学时)+实践(18学时) 考核方式: 考试(70%)+实验作业(30%) 课程学分: 3学分 先修课程: C语言程序设计离散数学数据结构,教材和教参,选用教材: 计算机算法基础(第三版)余祥宣,崔国华,邹海明 华中科技大学出版社 参考书目: 算法引论 张益新,沈雁 国防科技大学出版社 算法设计与分析
2、周培德 机械工业出版社,内容安排,第1章 数学预备知识 第2章 导引与基本数据结构 第3章 递归算法 第4章 分治法 第5章 贪心方法 第6章 动态规划 第7章 检索与周游 第8章 回溯法 第9章 分枝-限界 第10章 NP-问题 第11章 并行算法 ,第一章 导引,-计算机算法是计算机科学和计算机应用的核心。-数据结构+算法 = 程序-算法:计算机软件的灵魂1.什么是算法? 2.如何分析算法? 3.如何表示算法? 4.基本数据结构,1.什么是算法,算法,数值计算方法(求解数值问题,插值计算、数值积分等),非数值计算方法(求解非数值问题,主要进行判断比较),对算法(algorithm)一词给出
3、精确的定义是很难的。算法笼统的定义:求解一确定类问题的任意一种特殊方法。非形式描述:算法就是一组有穷的规则,它规定了解决某一特定类型问题的一系列运算。,什么是算法(续),算法是任何定义好了的计算程式,它取某些值或值的集合作为输入,并产生某些值或值的集合作为输出。因此,算法是将输入转化为输出的一系列计算步骤。 计算机科学中,算法已逐渐成了用计算机解决一类问题的精确、有效方法的代名词。,例 求两个正整数m,n的最大公因子,算法1-1 欧几里得算法 输入:正整数m和n 输出:m和n的最大公因子第一步:求余数。rm%n 第二步:判断r=0? 若是,终止(n为答案),否则,转第三步。 第三步:互换, m
4、n , nr ,返回第一步。,例 求两个正整数最大公因子的一个实例,假设 m=21 和 n=45,求21和45的最大公因子 第一步:r=m%n=21%45=21; 第二步:r 不等于0,转入第三步; 第三步:互换,m=n=45, n=r=21,返回第一步。 第一步:r=m%n=45%21=3; 第二步:r 不等于0,转入第三步; 第三步:互换,m=n=21 , n=r=3,返回第一步。 第一步:r=m%n=21%3=0; 第二步:r 等于0,算法结束,3即为21和45的最大公因子。,算法的五个重要特性,确定性 每一种运算必须要有确切的定义,无二义性 能行性 运算都是基本运算,原理上能在有限时间
5、内完成 输入 有 0个或多个输入 输出 一个或多个输出 有穷性 在执行了有穷步运算后终止,算法的五个重要特性,1)确定性算法的每种运算必须要有确切的定义,不能有二义性。 例:不符合确定性的运算 5/0 将6或7与x相加 未赋值变量参与运算,2)能行性算法中有待实现的运算都是基本的运算,原理上每种运算都能由人用纸和笔在有限的时间内完成。例:整数的算术运算是“能行”的;实数的算术运算是“不能行”的。,算法的五个重要特性,3)输入每个算法有0个或多个输入。这些输入是在算法开始之前给出的量,取自于特定的对象集合定义域(或值域),4)输出一个算法产生一个或多个输出,这些输出是同输入有某种特定关系的量。,
6、算法的五个重要特性,5)有穷性一个算法总是在执行了有穷步的运算之后终止。计算过程:只满足确定性、能行性、输入、输出四个特性但不一定能终止的一组规则。准确理解算法和计算过程的区别:不能终止的计算过程:操作系统算法是“可以终止的计算过程”算法的时效性:只能把在相当有穷步内终止的算法投入到计算机上运行。,算法的五个重要特性,算法的特性,凡是算法,都必须满足以上五条特性。 只满足前四条特性的一组规则不能称之为算法,只能叫做计算过程。 操作系统就是计算过程的一个典型例子。设计操作系统的目的是为了控制作业的运行,当没有作业时,这一计算过程并不终止,而是处于等待状态,一直等到一个新的作业的进入。,算法和程序
7、,一个算法可以用一个计算机程序来表示。 任何一种程序设计语言都可以实现任何一个算法。 算法的有穷性意味着不是所有的计算机程序都是算法。,算法学习的五个内容,如何设计算法 运用一些基本设计策略规划算法 如何表示算法 用恰当的方式表示算法 如何确认算法 算法正确性的证明(算法确认algorithm validation) 如何分析算法 通过时间和空间复杂度的分析,确定算法的优劣 如何测试程序 测试程序是否会产生错误的结果,算法学习将涉及5个方面的内容: 1)设计算法:创造性的活动 2)表示算法:思想的表示形式 3)确认算法:证明算法的正确性程序的证明 4)分析算法:算法时空特性分析 5)测试程序:
8、“调试只能指出有错误,而不能指出它们不存在错误”本课程集中于学习算法的设计与分析。通过学习,掌握计算机算法设计和分析基本策略与方法,为设计更复杂、更有效的算法奠定基础,我们的主要任务,2.如何分析算法,算法分析是对一个算法需要多少计算时间和存储空间作定量的分析。 计算机模型的假设 通用计算机模型: 顺序计算机 有足够的“内存” 能在固定的时间内存取数据单元算法分析步骤: 首先确定使用哪些运算以及执行这些运算所用的时间。(运算包括基本数值运算和一些更基本的任意长序列的运算) 其次是要确定出能反映算法在各种情况下工作的数据集。(即要求我们编造出能产生最好、最坏和有代表性情况的数据配置,通过使用这些
9、数据来运行算法,以更了解算法的性能),全面分析一个算法的两个阶段,事前分析(a prior analysis) 求出该算法的一个时间限界函数(一些关于参数的函数) 事前分析只限于每条语句的频率计数(frequency count,该语句的执行次数,与所用的机器无关,且独立于程序设计语言,可由算法直接确定) 事后测试(a posterior testing) 收集此算法的实际执行时间和占用空间的统计资料,例 频率计数例子,考虑语句xx+y在下面三个程序段中的频率计数,begin xx+y endFC:1,begin for i1 to n doxx+y repeat EndFC:n,begin
10、for i1 to n dofor j1 to n doxx+yrepeat Repeat EndFC:n2,计算时间的渐进估计表示,定义1.1 如果存在两个正常数c和n0,对于所有的nn0,有|f(n)|c|g(n)| 则记作:f(n)=O(g(n) 因此,当说一个算法具有O(g(n)的计算时间时,指的就是如果此算法用n值不变的同一类数据在某台机器上运行时,所用的时间总是小于|g(n)|的一个常数倍。 g(n)是计算时间f(n)的一个上界函数,f(n)的数量级就是g(n),时间的渐进估计表示,定理1.1 若A(n)=amnm+a1n+a0是一个m次多项式,则A(n)=O(nm)。 证明:取n
11、0=1,当nn0时利用A(n)的定义和一个简单的不等式,有|A(n)| |am|nm+|a1 | n+|a0 | (|am|+|am-1|/n +|a0 |/nm ) nm (|am|+|am-1|+|a0 |) nm 取c= |am|+|am-1|+|a0 |,定理得证。,时间的渐进估计表示,定理表明,变量n的固定阶数为m的任一多项式,与此多项式的最高阶nm同阶。因此,一个计算时间为m阶多项式的算法,其时间都可以用O(nm)来表示。 例如,一个算法的数量级为c1nm1,c2nm2,cknmk的k个语句,则算法的数量级及计算时间就是c1nm1+c2nm2+cknmk=O(nm) 其中m=max
12、mi|1 i k,算法的分类,从计算时间上可把算法分为两类 多项式时间算法(polynomial time algorithm):可用多项式来对其计算时间限界的算法。以下六种计算时间的多项式时间算法是最为常见的 O(1)O(logn)O(n)O(nlogn)O(n2)O(n3) 指数时间算法(exponential time algorithm):计算时间用指数函数限界的算法 O(2n)O(n!)O(nn),对于问题输入长度n取不同值,各种不同的时间复杂性函数的算法,在机器上的运行所需时间如下所示:,计算时间函数,典型的计算时间函数曲线,效率比速度更重要,Rich Man,Smart Prog
13、rammer,举例:效率比速度更重要,举例:效率比速度更重要,To sort one million numbers, computer A takes: Computer B takes: Computer B runs 20 times faster than computer A!,时间的渐进估计表示,定义1.2 如果存在两个正常数c和n0,对于所有的nn0,有|f(n)| c|g(n)|则记作:f(n)=(g(n) 定义1.3 如果存在两个正常数c1 , c2和n0,对于所有的nn0,有c1 |g(n)| |f(n)| c2 |g(n)|则记作:f(n)=(g(n),常用的整数求和公式
14、,3. 如何表示算法,将算法的基本思想和基本步骤用语言表示出来,便于阅读并能很容易地用人工或机器翻译成其他实际使用的程序设计语言。 用SPARKS语言写算法 SPARKS语言的组成 基本数据类型 整型(integer),实型(float),布尔型(boolean),字符型(char) 保留字 具有特殊含义的标识符,用黑体字表示,SPARKS语言的组成(1),变量命名规则 以字母开头,不允许使用特殊字符,不要太长,不允许与任何保留字重复。 语句:以分号作为语句结束的标志 赋值语句: 布尔值:True False 逻辑运算符:and ,or ,not 关系运算符: , 数组:任意整数下界和上界的多
15、维数组,SPARKS语言的组成(2),条件语句if 条件 then s1 或 if 条件 thenelse s2 s1endif endif Case语句case: 条件 1:s1: 条件 n:sn:else :sn+1endcase,SPARKS语言的组成(3),循环语句 while 条件 do loopS S Repeat until 条件 repeatFor vblestart to finish by increment doS Repeat,SPARKS语言的组成(4),过程(函数) Procedure Name()S Return() End Name 局部变量(local var
16、iabl) 在当前的过程中说明的变量 全局变量(global variabl) 在已包含当前过程的过程中说明为局部变量的变量 形式参数(formal parameter) 参数表中的一个标识符,1.4 基本数据结构(略),栈和队列 栈的运算 队列的运算 树 二元树 堆 二分检索树 图,1.4.3 集合的树表示和不相交集合的合并 树结构应用的实例,对于集合经常进行的是求取它们的交集和并集的运算。 若集合是一些不相交的集合,则对它们大量施行的两种基本运算是合并某些集合和查找某个元素在哪一个集合之中。 为了使集合的上述运算有效地被执行,就需要将集合中的元素构造成一定的结构形式。,集合操作举例,n=1
17、0,U=1, 2, 3, 4, 5, 6, 7, 8, 9, 10 s1=1, 7, 8, 9; s2=2, 5, 10; s3=3, 4, 6 合并运算: s1s2=1, 7, 8, 9, 2, 5, 10 查找运算: 元素4包含在s1, s2, s3的哪个集合中?,方法一:位向量,方法一:位向量 s1= 1, 0, 0, 0, 0, 0, 1, 1, 1, 0;s2= 0, 1, 0, 0, 1, 0, 0, 0, 0, 1; 利用位运算可得出 s1s2= 1, 1, 0, 0, 1, 0, 1, 1, 1, 1 优点:用“逻辑加”和“逻辑乘”指令便于实现两个集合的并和交之类的运算。 缺点
18、:只适用于n很小的情况。,方法二:集合元素表,s1=1, 7, 8, 9; s2=2, 5, 10 集合元素表是无序的。 合并操作:| s1|+| s1| 查找操作:最坏为|n| 执行并运算,花费的时间与参加运算的两个集合长度的和成正比。 确定某元素属于哪一个集合的运算时间则与这些集合长度的和成正比。,方法三: 树,用树结构来表示集合。 对于不相交集合,能方便地进行集合的并和确定某元素属于哪个集合这两种基本运算。,用树结构来表示集合,全集U=1,2,3,4,5,6,7,8,9,10 集合s1=1,7,8,9s2=2,5,10,集合在使用树表示的情况下,求两个不相交集合的并集,一种最直接的方法就
19、是使一棵树变成另一棵树的子树。,集合树表示法的具体实现,若树用链表表示,每个结点需要设置PARENT域,无需LCHILD和RCHILD。 为方便起见,集合中的元素都用存放这些元素数组的下标值来代替,并使这些下标与表示链表的数组的下标相对应。这样,链表的数组元素的下标就代表集合中的相应元素,从而可以取消链表中的结点的DATA域,结点只含有PARENT域。,集合树表示法的具体实现(续),全集U的数组:,表示链表的数组记为PARENT(1:n)。 PARENT(i)中存放着元素i在树中的父结点的指针,根结点的PARENT域中的内容就为零。,PARENT,数据结构,字符数组U=1, 2, 3, 4,
20、5, 6, 7, 8, 9, 10 子集s1=1, 7, 8, 9; s2=2, 5, 10 则用数组PARENT表示集合s1和s2:数组中记录的是节点Ui的父节点在PARENT中的位置,合并操作U(1,2)后:(Parent1=2),简单的合并与查找运算,procedure U(i,j)/根为i和j的两个不相交集合用它们的并来取代integer i,j;PARENT(i)=j end U,procedure F(i)/找包含元素i的树的根integer i,jj=i while PARENT(j)0 doj=PARENT(j)repeatreturn(j) end F,查找元素F(9),U操
21、作为常量时间,F操作则与查找元素在集合树中的层数有关。,U和F的性能问题退化树,问题描述:有集合如下:依次作下列操作:U(1,2), F(1), U(2,3), F(1), , U(n-1,n) 按照算法U和F,最终得到的树及时间耗费分析,U:每次都是常量时间,因此总共是O(n-1) F(1):2+3+(n-1),因此是O(n2) 症结?合并操作!,加权规则,为避免造出退化树,使用加权规则。 加权规则: 如果树i的结点数少于树j的结点数,则使j成为i的父亲,反之,则使i成为j的父亲。,加权规则(续),要使用加权规则就需要知道每一棵树有多少个结点,方法是:在每一棵树的根结点增设一个COUNT计数
22、域。 为了保持结点大小固定,实际上将结点计数以负数形式保存在根结点的PARENT域中。,加权规则,节点数少的树合并到节点数多的树中。 字符数组U=1, 2, 3, 4, 5, 6, 7, 8, 9, 10 子集s1=1, 7, 8, 9; s2=2, 5, 10,使用加权规则的合并算法,procedure UNION(i,j) /使用加权规则合并根为i 和j 两个集合, ij /PARENT(i)=-COUNT(i), PARENT(j)=-COUNT(j)integer i,j,xx=PARENT(i)+PARENT(j)if PARENT(i)PARENT(j)then PARENT(i)
23、=jPARENT(j)=xelse PARENT(j)=i PARENT(i)=xendif end UNION,Union F序列分析,UNION(1,2), F(1), UNION (2,3), F(1), , UNION (n-1,n) UNION合并的开销较u要大,但仍然是常量时间 每次查找1耗费时间为2,常量时间,则执行n-2次查找耗费时间为O(n) 注意:本例的查找耗时不是最坏情况 最坏情况由引理1.3给出,引理1.3设T是一棵由算法UNION所产生的有n个结点的树。在T中没有结点的级数会大于logn+1。,证明: n=1时,成立; 假设对于所有结点数为i的树,in-1,引理为真;
24、 现证明对于i=n,引理亦为真。 设T是由算法UNION所产生的一棵n个结点的树, 现考虑最后一次执行的合并运算UNION(k,j)。 设m是树j的结点数,n-m是树k的结点数。 不失一般性,可以假设1 m n/2。 于是,T中任何一结点的最大级数或者与k中的最大级数相同,或者比j的最大级数大1。 若为前者,则T的最大级数小于等于log(n-m)+1 logn+1。 若为后者,则的最大级数小于等于logm+2 log(n/2)+2 logn+1。 证毕。,例1.1,UNION(1,2) UNION(3,4) UNION(5,6) UNION(7,8) UNION(1,3) UNION(5,7)
25、 UNION(1,5),分析,对于算法UNION所产生的n个结点的树,执行一次查找的最长时间至多是O(logn)。 如果要处理的合并和查找序列含有n次合并和m次查找,则最坏情况时间就变成了O(n+mlogn)。,压缩规则,如果j是由i到它的根的路径上的一个结点,则置PARENT(j)root(i)。,使用压缩规则的查找算法,procedure FIND(i) /查找含有元素i的树根,使用压缩规则去压缩由i到根j的所有结点j=iwhile PARENT(j)0 doj=PARENT(j)repeatk=iwhile k!=j dot=PARENT(K)PARENT(K)=jk=trepeat r
26、eturn(j),使用压缩规则的查找,更快的平均查找时间,适用于频繁查找操作。 例1.1中实现8次对元素8的查找,用FIND(8)算法实现总共20次,优于使用F的8*3=24次。 结论:对于m次FIND和n次UNION的混合序列(mn),处理时间接近O(m),但稍差。详细描述见引理1.4。,1.5 递归和消去递归,递归 直接调用自己或间接通过一些语句调用自己 优点:描述某些数学问题非常自然,证明算法很容易。 缺点:执行时间、空间消耗多 一个递归问题可分为“回推”和“递推”两个阶段,未知,已知,递归例子:Fibonacci数列,定义 F(n)=,1, n=0,1, n=1,F(n-1)+F(n-
27、2), n1,递归部分,起始条件,求Fibonacci数列算法 Procedure F(n)integer nif n1 then return(1)else return(F(n-1)+F(n-2)end if End F,用递归实现求最大公因数,Procedure GCD(a,b)if b=0 then return(a)else return(GCD(b,a mod b)endif End GCD例如:a=22 , b=8 求GCD(22,8)=?,GCD(22,8),GCD(8,6),GCD(6,2),GCD(2,0),2,回推,回推,回推,回推,递归,递归,递归,递归,用递归实现求最大公因数,结果为GCD(22,8)=2,消去递归,递归的优点:与数学定义相似,容易编写算法 递归的缺点:计算时间长,很多值都被重复计算了多次 消去递归 目的是克服递归时间空间的开销 解决方法:先使用递归,然后证明所设计的递归算法正确并且确信是一个好算法,再把递归消去,翻译成与之等价的只使用迭代的算法。 直接递归翻译成迭代过程的规则,小结,算法就是一组有穷的规则,它规定了解决某一特定类型问题的一系列运算。 算法的五个重要特性 确定性、可行性、输入、输出、有穷性 算法分析是对一个算法需要多少时间很存储空间作定量分析。 用SPARKS语言写算法 基本数据结构 递归和消去递归,