1、计算机算法设计与分析1.1 算法的定义和特征算法的定义和特征1)什么是算法?什么是算法?算法是求解某一特定问题特定问题的一组的一组有有穷规则穷规则的集合,它是由若干条指令组成是由若干条指令组成的有穷符号串的有穷符号串。2 2)算法的五个重要特性算法的五个重要特性 确定性确定性、可实现性可实现性、输入输入、输出输出、有穷性有穷性3 3)算法设计的质量指标算法设计的质量指标 正确性,可读性,健壮性,效率与存储量正确性,可读性,健壮性,效率与存储量算法和程序的区别算法和程序的区别n程序:一个计算机程序是对一个算法使用某种程序设计语言的具体实现n任何一种程序设计语言都可以实现任何一个算法n算法的有穷性
2、意味着不是所有的计算机程序都是算法问题求解问题求解(Problem Solving)证明正确性分析算法设计程序理解问题精确解或近似解选择数据结构算法设计策略设计算法 一般只考虑三种情况下的时间性一般只考虑三种情况下的时间性:最坏情况、最坏情况、最好情况和平均情况下的复杂性,分别记为最好情况和平均情况下的复杂性,分别记为Tmax(n)、Tmin(n)和和Tavg(n)算法复杂性算法复杂性 =算法所需要的计算机资源算法所需要的计算机资源 =时间复杂性时间复杂性+空间复杂性空间复杂性算法渐近复杂性算法渐近复杂性1)上界函数)上界函数定义定义1 如果存在两个正常数c和n0,对于所有的nn0,有|f(n
3、)|c|g(n)|则记作f(n)=(g(n)含义:含义:n如果算法用n值不变的同一类数据在某台机器上运行时,所用的时间总是小于|g(n)|的一个常数倍。所以g(n)是计算时间f(n)的一个上界函数。f(n)的数量级就是g(n)。nf(n)的增长最多像g(n)的增长那样快n试图求出最小的g(n),使得f(n)=(g(n)。算法分类(计算时间)算法分类(计算时间)多项式时间算法多项式时间算法:可用多项式(函数)对其计算时间限界的算法。常见的多项式限界函数常见的多项式限界函数有:(1)(logn)(n)(nlogn)(n2)(n3)指数时间算法指数时间算法:计算时间用指数函数限界的算法。常见的指数时
4、间限界函数常见的指数时间限界函数:(2n)(n!)(nn)说明说明:当n取值较大时,指数时间算法和多项式时间算法在计算时间上非常悬殊。典型的计算时间函数曲线典型的计算时间函数曲线定义定义1.2 如果存在两个正常数c和n0,对于所有的nn0,有|f(n)|c|g(n)|则记作f(n)=(g(n)含义:含义:n如果算法用n值不变的同一类数据在某台机器上运行时,所用的时间总是不小于|g(n)|的一个常数倍。所以g(n)是计算时间f(n)的一个下界函数。nf(n)的增长至少像g(n)的增长那样快n试图求出“最大”的g(n),使得f(n)=(g(n)。2)下界函数下界函数定义定义1.3 如果存在正常数c
5、1,c2和n0,对于所有的nn0,有 c1|g(n)|f(n)|c2|g(n)|则记作含义含义:n算法在最好和最坏情况下的计算时间就一个常数因子范围内而言是相同的。可看作:既有f(n)=(g(n),又有f(n)=(g(n)n 记号表明算法的运行时间有一个较准确的界)()(ngnf3)“平均情况平均情况”限界函数限界函数最优算法最优算法n问题的计算时间下界为(f(n),则计算时间复杂性为O(f(n)的算法是最优算法。n例如,排序问题的计算时间下界为(nlogn),计算时间复杂性为O(nlogn)的排序算法是最优算法。2.1 直接或间接地调用自身的算法称为递归算法递归算法。函数自身给出定义的函数称
6、为递归函数递归函数。n 基于归纳法归纳法的递归n 基于分治法分治法的递归2.1 例例 Fibonacci Fibonacci数列数列无穷数列1,1,2,3,5,8,13,21,34,55,称为Fibonacci数列。它可以递归地定义为:边界条件边界条件递归方程递归方程110)2()1(11)(nnnnFnFnF第n个Fibonacci数可递归地计算如下:int fibonacci(int n)if(n=1)return 1;return fibonacci(n-1)+fibonacci(n-2);分治法分治法的的设计思想设计思想是,是,将一个难以直接解决的将一个难以直接解决的大问题,分割成一些
7、规模较小的相同问题,以大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。便各个击破,分而治之。n该问题的规模缩小到一定的程度就可以该问题的规模缩小到一定的程度就可以容易地解容易地解决决;n该问题可以分解为若干个规模较小的相同问题,该问题可以分解为若干个规模较小的相同问题,即该问题具有即该问题具有最优子结构性质最优子结构性质n利用该问题分解出的子问题的解利用该问题分解出的子问题的解可以合并可以合并为该问为该问题的解;题的解;n该问题所分解出的各个子问题是该问题所分解出的各个子问题是相互独立相互独立的,即的,即子问题之间不包含公共的子问题。子问题之间不包含公共的子问题。(1):将原问题
8、分解为若干个规模较小:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;,相互独立,与原问题形式相同的子问题;(2):若子问题规模较小而容易被解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题;则直接解,否则递归地解各个子问题;(3):将各个子问题的解合并为原问题:将各个子问题的解合并为原问题的解。的解。一个分治法将规模为n的问题分成k个规模为nk的子问题去解。设分解阀值n0=1,且adhoc解规模为1的问题耗费1个单位时间。再设将原问题分解为k个子问题以及用merge将k个子问题的解合并为原问题的解需用f(n)个单位时间。用T(n)表示该分治法解规模为|P|=
9、n的问题所需的计算时间,则有:11)()/()1()(nnnfknkTOnT通过迭代法求得方程的解:nbnnnTklog)(给定已按升序排好序的给定已按升序排好序的n个元素个元素a0:n-1,现要在这,现要在这n个元素中找个元素中找出一特定元素出一特定元素x。据此容易设计出二分搜索算法二分搜索算法:template int BinarySearch(Type a,const Type&x,int l,int r)while(r=l)int m=(l+r)/2;if(x=am)return m;if(x am)r=m-1;else l=m+1;return-1;算法复杂度分析:算法复杂度分析:每
10、执行一次算法的while循环,待搜索数组的大小减少一半。因此,在最坏情况下,while循环被执行了O(logn)次。循环体内运算需要O(1)时间,因此整个算法在最坏情况下的计算时间复杂性为O(logn)。基本思想:基本思想:将待排序元素分成大小大致相同的2个子集合,分别对2个子集合进行排序,最终将排好序的子集合合并成为所要求的排好序的集合。public static void mergeSort(Comparable a,int left,int right)if(leftright)/至少有2个元素 int i=(left+right)/2;/取中点 mergeSort(a,left,i);
11、mergeSort(a,i+1,right);merge(a,b,left,i,right);/合并到数组b copy(a,b,left,right);/复制回数组a 复杂度分析复杂度分析T(n)=O(nlogn)渐进意义下的最优算法11)()2/(2)1()(nnnOnTOnT算法算法 消去递归的合并排序算法消去递归的合并排序算法输入:输入:具有个元素的数组A输出:输出:按递增顺序排序的数组A1.template 2.void merge_sort(Type A,int n)3.4.int i,s,t=1;5.while(tn)6.s=t;t=2*s;i=0;7.while(i+tn)8.m
12、erge(A,i,i+s-1,i+t-1,t);9.i=i+t;10.11.if(i+sn)12.merge(A,i,i+s-1,n-1,n-i);13.14.算法mergeSort的递归过程可以消去。初始序列49 38 65 97 76 13 2738 49 65 97 13 76 27第一步第二步38 49 65 97 13 27 76第三步13 27 38 49 65 76 97private static void qSort(int p,int r)if(pr)int q=partition(p,r);/以ap为基准元素将ap:r划分成3段ap:q-1,aq和aq+1:r,使得ap:
13、q-1中任何元素小于等于aq,aq+1:r中任何元素大于等于aq。下标q在划分过程中确定。qSort(p,q-1);/对左半段排序 qSort(q+1,r);/对右半段排序 templateint Partition(Type a,int p,int r)int i=p,j=r+1;Type x=ap;while(true)while(a+i x&ir);/将将x);/将将 x的元素交换到右边区的元素交换到右边区域域 if(i=j)break;Swap(ai,aj);ap=aj;aj=x;return j;线性时间选择问题线性时间选择问题n问题描述问题描述:给定线性集中n个元素和一个整数k,要
14、求找出这n个元素中第k小的元素,即如果将这n个元素依其线性序排列时,排在第k个位置的元素即为我们要找的元素。n当k=1时,即找最小元素;当k=n时,即找最大元素;当k=(n+1)/2时,称为找中位数。templateType RandomizedSelect(Type a,int p,int r,int k)if(p=r)return ap;int i=RandomizedPartition(a,p,r),j=i-p+1;if(k=j)return RandomizedSelect(a,p,i,k);else return RandomizedSelect(a,i+1,r,k-j);线性时间选
15、择问题算法线性时间选择问题算法n上述Partition算法可用来求选择问题的有效解。如果划分元素v测定在A(j)的位置上,则有j-1个元素小于或等于A(j),且有n-j 个元素大于或等于A(j)。因此,若kj,则第k小元素在A(j+1:n)中,再对之进一步划分。在最坏情况下,算法randomizedSelect需要O(n2)计算时间但可以证明,算法randomizedSelect可以在O(n)平均时间内找出n个输入元素中的第k小元素。l将n个输入元素划分成n/5个组,每组5个元素,只可能有一个组不是5个元素。用任意一种排序算法,将每组中的元素排好序,并取出每组的中位数,共n/5个。l递归调用s
16、elect来找出这n/5个元素的中位数。如果n/5是偶数,就找它的2个中位数中较大的一个。以这个元素作为划分基准。Type Select(Type a,int p,int r,int k)if(r-p75)用某个简单排序算法对数组ap:r排序;return ap+k-1;for(int i=0;i=(r-p-4)/5;i+)将ap+5*i至ap+5*i+4的第3小元素 与ap+i交换位置;/找中位数的中位数,r-p-4即上面所说的n-5 Type x=Select(a,p,p+(r-p-4)/5,(r-p-4)/10);int i=Partition(a,p,r,x),j=i-p+1;if(k
17、=j)return Select(a,p,i,k);else return Select(a,i+1,r,k-j);复杂度分析复杂度分析T(n)=O(n)7575)4/3()5/()(21nnnTnTnCCnT32:n 把求解的问题把求解的问题分成许多阶段或多个子问题分成许多阶段或多个子问题,然后然后按顺序求解各个子问题按顺序求解各个子问题。前一个子问题的。前一个子问题的解为后一个子问题的求解提供了有用的信息。解为后一个子问题的求解提供了有用的信息。在求解任何一子问题时,列出各种可能的局部在求解任何一子问题时,列出各种可能的局部解,解,通过决策保留那些有可能达到最优的局部通过决策保留那些有可能
18、达到最优的局部解,丢弃其他局部解解,丢弃其他局部解,依次解决各子问题,最,依次解决各子问题,最后一个子问题就是问题的解。后一个子问题就是问题的解。动态规划算法的两个基本要素动态规划算法的两个基本要素 对于一个多阶段过程问题,最优决策是否存在,不仅依赖于该问题是否有最优子结构性质,而能否采用动态规划的方法,还要看该问题的子问题是否具有重叠性质。最优子结构性质最优子结构性质:原问题的最优解包含了其子问题的最优解。子问题重叠性质子问题重叠性质:每次产生的子问题并不总是新问题,有些子问题被反复计算多次。34n找出最优解的性质,并刻划其结构特征。找出最优解的性质,并刻划其结构特征。n递归递归地定义最优值
19、。地定义最优值。n以以自底向上自底向上的方式计算出最优值。的方式计算出最优值。n根据计算最优值时得到的信息,构造最根据计算最优值时得到的信息,构造最优解。优解。35u穷举法穷举法u动态规划动态规划将矩阵连乘积 简记为Ai:j,这里ij jiiAAA.1考察计算Ai:j的最优计算次序。设这个计算次序在矩阵Ak和Ak+1之间将矩阵链断开,ikj,则其相应完全加括号方式为).)(.(211jkkkiiAAAAAA计算量:Ai:k的计算量加上Ak+1:j的计算量,再加上Ai:k和Ak+1:j相乘的计算量 令令mij,1i,jn,为计算,为计算Ai,j 的最少数的最少数乘次数,则原问题为乘次数,则原问题
20、为m1n。n当i=j时,Ai,j为单一矩阵,mij=0;n当ij时,利用最优子结构性质有:mij=minmik+mk+1j+pi1pkpjikj其中矩阵Ai,1in,的维数为pi1pi。n根据此递归式就可以直接用递归程序来实现。消除重复的矩阵连乘算法消除重复的矩阵连乘算法nVoid MatrixChain(int p,int n,int*m,int*s)n for(int i=1;i=n;i+)mii=0;n /将对角线元素赋值为零,即单个矩阵计算量为0n for(int r=2;r=n;r+)n for(int i=1;i=n r+1;i+)n int j=i+r 1;n(5)mij=mi+
21、1j+pi1*pi*pj;n /计算Ai,j=Ai:i Ai+1:jn sij=i;/记下断点in(7)for(int k=i+1;k j;k+)n int t=mik+mk+1j+pi1*pk*pj;n /对ikj,逐个计算Ai,j=Ai:k Ak+1:jn if(t mij)mij=t;sij=k;n /记下较小的mij及相应的断点k n 算法时间复杂性的上界为O(n3)39由最长公共子序列问题的最优子结构性质建立子问题最优值的递归关系。用cij记录序列和的最长公共子序列的长度。其中,和 当i=0或j=0时,空序列是A和B的最长公共子序列。故此时Cij=0。其它情况下,由最优子结构性质可建
22、立递归关系如下:jijiyxjiyxjijijicjicjicjic;0,;0,0,01,1max1 110naaaA21mbbbB2140由于在所考虑的子问题空间中,总共有(mn)个不同的子问题,因此,用动态规划算法地计算最优值能提高算法的效率。void LCSLength(int m,int n,char*x,char*y,int*c,int*b)int i,j;for(i=1;i=m;i+)ci0=0;for(i=1;i=n;i+)c0i=0;for(i=1;i=m;i+)for(j=1;j=cij-1)cij=ci-1j;bij=2;else cij=cij-1;bij=3;构造最长公
23、共子序列构造最长公共子序列void LCS(int i,int j,char*x,int*b)if(i=0|j=0)return;if(bij=1)LCS(i-1,j-1,x,b);coutxi;else if(bij=2)LCS(i-1,j,x,b);else LCS(i,j-1,x,b);41给定n种物品和一背包。物品i的重量是wi,其价值为vi,背包的容量为C。问应如何选择装入背包的物品,使得装入背包中物品的总价值最大?0-1背包问题是一个特殊的整数规划问题。niiixv1maxnixCxwiniii1,1,0142设所给0-1背包问题的子问题nikkkxvmaxnkixjxwknikk
24、k,1,0的最优值为的最优值为m(i,j),即,即m(i,j)是背包容量为是背包容量为j,可选择物品为,可选择物品为i,i+1,n时时0-1背包问题的最优值。由背包问题的最优值。由0-1背包问题的最优子背包问题的最优子结构性质,可以建立计算结构性质,可以建立计算m(i,j)的递归式如下。的递归式如下。iiiiwjwjjimvwjimjimjim0),1(),1(),1(max),(nnnwjwjvjnm00),(43templatevoid Knapsack(Type v,int w,int c,int n,Type*m)int jMax=min(wn-1,c);for(int j=0;j=j
25、Max;j+)mnj=0;for(int j=wn;j=c;j+)nnj=vn;for(int i=n-1;i1;i-)jMax=min(wi-1,c);for(int j=0;j=jMax;j+)mij=mi+1j;for(int j=wi;j=w1)m1c=max(m1c,m2c-w1+v1);贪心算法贪心算法n贪心算法总是作出在贪心算法总是作出在当前看来最好的选当前看来最好的选择择,即所作的选择只是,即所作的选择只是局部最优选择局部最优选择。n希望从希望从局部的最优选择局部的最优选择得到得到整体最优解整体最优解。n采用逐步构造最优解的方法。在每个阶采用逐步构造最优解的方法。在每个阶段,都
26、作出一个看上去最优的决策(在段,都作出一个看上去最优的决策(在一定的标准下)。决策一旦作出,就不一定的标准下)。决策一旦作出,就不可再更改。可再更改。贪心方法描述量度标准A(1)A(2)A(n)A(1)A(2)A(n)S(1)S(2)这种量度意义下的部分最优解原问题的n个输入排序后的n个输入A(j)贪心算法的基本要素贪心算法的基本要素贪心算法的基本要素贪心算法的基本要素n可用贪心算法求解的问题的基本要素可用贪心算法求解的问题的基本要素 最优子结构性质最优子结构性质贪心选择性质贪心选择性质。贪心算法的基本要素贪心算法的基本要素贪心算法与动态规划算法的差异贪心算法与动态规划算法的差异n相同点:都具
27、有最优子结构性质相同点:都具有最优子结构性质n区别:动态规划算法每步所作的选择往往依赖区别:动态规划算法每步所作的选择往往依赖于相关子问题的解。只有解出相关子问题才能于相关子问题的解。只有解出相关子问题才能作出选择。作出选择。贪心算法仅在当前状态下作出最好选择,即局贪心算法仅在当前状态下作出最好选择,即局部最优选择,但不依赖于子问题的解部最优选择,但不依赖于子问题的解 贪心:贪心:自顶向下自顶向下 动态规划:动态规划:自底向上自底向上贪心算法的基本要素贪心算法的基本要素活动安排问题活动安排问题 已知已知n个活动个活动 E=1,2,n 要求使用同要求使用同一资源,第一资源,第k个活动要求的开始和
28、结束时间个活动要求的开始和结束时间为为sk,fk,其中其中sk fk,k=1,2,n。活动。活动k与活动与活动j称为相容的如果称为相容的如果sk fj 或者或者sj fk。活动安排问题就是要在所给的活动集合中活动安排问题就是要在所给的活动集合中选出最大的相容活动子集。选出最大的相容活动子集。问题提出:问题提出:基本思路基本思路n在安排时应该将在安排时应该将结束时间早的活动结束时间早的活动尽量往前安排,尽量往前安排,好给后面的活动安排留出更多的空间,从而达到好给后面的活动安排留出更多的空间,从而达到安排最多活动的目标。安排最多活动的目标。n贪心准则应当是:贪心准则应当是:在未安排的活动中挑选结束
29、时在未安排的活动中挑选结束时间最早的活动安排。间最早的活动安排。活动安排问题活动安排问题n首先将安排的首先将安排的11个活动的开始时间和结束时个活动的开始时间和结束时间按结束时间的间按结束时间的非减次序排列非减次序排列如下:如下:k1234567891011sk 130535688212fk 4567891011121314活动安排问题活动安排问题0123456789 101112 13 141isifi1142353064575386597610881198121021311121421314141461471481489148101481114811活动安排问题活动安排问题5 阴影长条阴影
30、长条表示的表示的活动是已选入集合活动是已选入集合A的活动,而的活动,而空白空白长条长条表示的活动是表示的活动是当前正在检查相容当前正在检查相容性的活动。性的活动。52活动安排问题nPublic static void greedySelector(int s,int f,boolean a)n int n=s.length-1;n a0=true;n int j=0;int count=1;n for(int i=1;i=fj)ai=true;j=i;count+;n else ai=false;n n return count;n下面给出解活动安排问题的贪心算法下面给出解活动安排问题的贪心算
31、法GreedySelector:GreedySelector:各活动的起始时间和结各活动的起始时间和结束时间存储于数组束时间存储于数组s s和和f f中且按结束时间的非减中且按结束时间的非减序排列序排列 53 给定给定n n种物品和一个背包。物品种物品和一个背包。物品i i的重量是的重量是W Wi i,其价值为其价值为V Vi i,背包的容量为,背包的容量为C C。应如何选择装入背。应如何选择装入背包的物品,使得装入背包中物品的总价值最大包的物品,使得装入背包中物品的总价值最大?在选择装入背包的物品时,对每种物品在选择装入背包的物品时,对每种物品i i只有只有2 2种选择,即种选择,即装入背包
32、或不装入背包。不能将物品装入背包或不装入背包。不能将物品i i装入背包多次,也不能只装入背包多次,也不能只装入部分的物品装入部分的物品i i。54n 背包问题:背包问题:与0-1背包问题类似,所不同的是在选择物品i装入背包时,可以选择物品可以选择物品i i的一部分的一部分,而不一定要全部装入背包,1in。形式化描述为:这2类问题都具有最优子结构最优子结构性质,极为相似,但背包问题可以用贪心算法求解,而0-1背包问题却不能用贪心算法求解。nixCwxtsvxniniiiiii1,1,0,.max11其中C0为背包容量,wi0,vi0,(x1,x2,xn)为最优解。55 首先计算每种物品单位重量的
33、价值首先计算每种物品单位重量的价值v vi i/w/wi i,然后,依贪心,然后,依贪心选择策略,将尽可能多的选择策略,将尽可能多的单位重量价值最高单位重量价值最高(即即v vi i/w/wi i尽可大尽可大的的)的物品装入背包。若将这种物品全部装入背包后,背包的物品装入背包。若将这种物品全部装入背包后,背包内的物品总重量未超过内的物品总重量未超过C C,则选择单位重量价值次高的物品,则选择单位重量价值次高的物品并尽可能多地装入背包。依此策略一直地进行下去,直到背并尽可能多地装入背包。依此策略一直地进行下去,直到背包装满为止。若最后一个物品不能全部装入时,仅装其一部包装满为止。若最后一个物品不
34、能全部装入时,仅装其一部分使背包装满即可。分使背包装满即可。具体算法可描述如下页:用贪心算法解背包问题的基本思想:贪心解背包问题贪心解背包问题n首先计算每种物品单位重量的价值首先计算每种物品单位重量的价值vi/wi,然后,将尽可能,然后,将尽可能多的单位重量价值最高的物品装入背包。多的单位重量价值最高的物品装入背包。void Knapsack(int n,float M,float v,float w,float x )Sort(n,v,w);/将各种物品按单位重量价值排序将各种物品按单位重量价值排序 int i;for(i=1;i=n;i+)xi=0;/将解向量初始化为零将解向量初始化为零
35、float c=M;/是背包剩余容量初始化为是背包剩余容量初始化为M for(i=1;i=n;i+)if()break;x i =1;c ;if(i c-=w i x i =c/w i 算法复杂度算法复杂度 该算法的主要计算时间在于将各种物品该算法的主要计算时间在于将各种物品依其单位重量的价值从大到小顺序。因依其单位重量的价值从大到小顺序。因此算法的计算时间上界为此算法的计算时间上界为O(nlogn)。58单源最短路径给定带权有向图给定带权有向图G=(V,E)G=(V,E),其中每条边的权是非负实数。,其中每条边的权是非负实数。另外,还给定另外,还给定V V中的一个顶点,称为中的一个顶点,称为
36、源源。现在要计算从源到所。现在要计算从源到所有其它各顶点的有其它各顶点的最短路径长度最短路径长度。这里径路的长度是指路径上。这里径路的长度是指路径上各边权之和。这个问题通常称为各边权之和。这个问题通常称为单源最短路径问题单源最短路径问题。1、Dijkstra算法基本思想 Dijkstra算法是解单源最短路径问题的贪心算法。其基本基本思想思想是:设置顶点集合S(初始仅含源源)并不断地作贪心选择贪心选择清华大学出版社59单源最短路径来扩充这个集合;一个顶点属于集合S当且仅当从源到该顶点的最短路径长度已知已知。具体而言:初始时,S中仅含有源。设u是G的某一个顶点,把从源到u且中间只经过S中顶点的路径
37、称为从源到u的特殊路径,并用数组dist记录当前每个顶点当前每个顶点所对应的最短特殊路径长度。Dijkstra算法每次从V-S中取出具有最短特殊路长度的顶点u添加到S中,同时根据S中顶点的最短路径对数组dist作必要的修改。一旦S包含了V中所有顶点,则dist就记录了从源到其它所有顶点的最短路径的长度。清华大学出版社v0v2v1v3v4v5204550101515201035303迭代迭代选取选取结点结点SDIST(1)(2)(3)(4)(5)置初值置初值-0501045120,250102545230,2,345102545310,2,3,145102545440,2,3,1,4451025
38、45550,2,3,1,4,545102545考虑需要哪些存储结构?考虑需要哪些存储结构?算法如何实现?算法如何实现?循环需要几次?循环需要几次?每次循环做什么工作?每次循环做什么工作?首先为第一行赋初值;首先为第一行赋初值;循环循环n-1次;次;每次根据新加进来的每次根据新加进来的结点修改可以修改的结点修改可以修改的路径,并选择最短的路径,并选择最短的61最小生成树 设G=(V,E)是无向连通带权图,即一个网络网络。E中每条边(v,w)的权为cvw。如果G的子图G是一棵包含G的所有顶点的树,则称G为G的生成树。生成树上各边权的总和称为该生成树的耗费耗费。在G的所有生成树中,耗费最小的生成树称
39、为G的最小生成树最小生成树。62最小生成树1、最小生成树性质用贪心算法设计策略可以设计出构造最小生成树的有效算法。本节介绍的构造最小生成树的PrimPrim算法算法和KruskalKruskal算法算法都可以看作是应用贪心算法设计策略的例子。尽管这2个算法做贪心选择的方式不同,它们都利用了下面的最小生成树性质最小生成树性质:设G=(V,E)是连通带权图,U是V的非空真子集。如果(u,v)E,且uU,vV-U,且在所有这样的边中(即断集中),(u,v)的权cuv最小,那么一定存在G的一棵最小生成树,它以(u,v)为其中一条边。这个性质有时也称为MSTMST性质性质。证明略。63最小生成树Prim
40、Prim算法算法 设G=(V,E)是连通带权图,V=1,2,n。构造G的最小生成树的Prim算法的基本思想基本思想是:首先置S=1,然后,只要S是V的真子集,就作如下的贪贪心选择心选择:选取满足条件iS,jV-S,且cij最小的边,将顶点j添加到S中。这个过程一直进行到S=V时为止。在这个过程中选取到的所有边恰好构成G的一棵最最小生成树小生成树。清华大学出版社64最小生成树利用最小生成树性质和数学归纳法容易证明,上述算法中的边集合边集合T T始终始终包含包含G G的某棵最小生成树中的某棵最小生成树中的边的边。因此,在算法结束时,T中的所有边构成G的一棵最小生成树。例如例如,对于右图中的带权图,
41、按PrimPrim算法算法选取边的过程如下页图所示。清华大学出版社654.6 最小生成树清华大学出版社66最小生成树3 3、KruskalKruskal算法算法Kruskal算法构造G的最小生成树的基本思想基本思想是,首先将G的n个顶点看成n个孤立的连通分支。将所有的边按权从小到大排序。然后从第一条边开始,依边权递增的顺序查看每一条边,并按下述方法连接并按下述方法连接2 2个不同个不同的连通分支:的连通分支:当查看到第k条边(v,w)时,如果端点v和w分别是当前2个不同的连通分支T1和T2中的顶点时,就用边(v,w)将T1和T2连接成一个连通分支,然后继续查看第k+1条边;如果端点v和w在当前
42、的同一个连通分支中,就直接再查看第k+1条边。这个过程一直进行到只剩下一个连通分支时为止。清华大学出版社67最小生成树例如,例如,对前面的连通带权图,按Kruskal算法顺序得到的最小生成树上的边如下图所示。清华大学出版社问题的解向量问题的解向量:一个一个n n元式元式(x1,x2,xn)(x1,x2,xn)的形式。的形式。显约束:对分量显约束:对分量xi i的取值限定。的取值限定。隐约束:为满足问题的解而对不同分量之间施加的隐约束:为满足问题的解而对不同分量之间施加的约束。约束。解空间:解空间:问题的解空间一般用问题的解空间一般用解空间树解空间树(Solution Space Trees,也
43、称状态空间树)的方式组织也称状态空间树)的方式组织,其中,树的根结点位于第其中,树的根结点位于第1层层,表示搜索的初始状态,表示搜索的初始状态,第第2层的结点表示对解向量的第一个分量做出选择后到达的状态层的结点表示对解向量的第一个分量做出选择后到达的状态,第第1层到第层到第2层的边上标出对第一个分量选择的结果,层的边上标出对第一个分量选择的结果,依此类依此类推,推,从树的根结点到叶子结点的路径就构成了解空间的一个从树的根结点到叶子结点的路径就构成了解空间的一个可能解。可能解。n 回溯法从回溯法从根结点出发根结点出发,按照,按照深度优先深度优先策略策略搜索搜索(遍历)解空间树(遍历)解空间树,搜
44、索满足约束条件的解。,搜索满足约束条件的解。n初始时,根结点成为一个初始时,根结点成为一个活结点活结点,同时也称为当,同时也称为当前的前的扩展结点扩展结点。在当前扩展结点处,搜索向纵深。在当前扩展结点处,搜索向纵深方向移至一个新结点。这个新结点成为一个新的方向移至一个新结点。这个新结点成为一个新的活结点,并成为当前的扩展结点。如果在当前的活结点,并成为当前的扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,则当前的扩扩展结点处不能再向纵深方向移动,则当前的扩展结点就成为一个展结点就成为一个死结点死结点(即不再是一个活节(即不再是一个活节点)。此时,应往回移动(回溯)至最近的一个点)。此时,
45、应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。活结点处,并使这个活结点成为当前的扩展结点。(1)(1)针对所给问题,定义问题的解空间;针对所给问题,定义问题的解空间;(2)(2)确定易于搜索的解空间结构;确定易于搜索的解空间结构;(3)(3)深度优先搜索解空间,并在搜索中用剪枝函数避深度优先搜索解空间,并在搜索中用剪枝函数避免无效搜索。免无效搜索。需要注意的是,问题的解空间树是虚拟的,并不需要在算法需要注意的是,问题的解空间树是虚拟的,并不需要在算法运行时构造一棵真正的树结构。在任何时刻,算法只保存从运行时构造一棵真正的树结构。在任何时刻,算法只保存从根结点到当前扩
46、展结点的路径。如果解空间树中从根结点到根结点到当前扩展结点的路径。如果解空间树中从根结点到叶结点的最长路径的长度为叶结点的最长路径的长度为h(n),则回溯法所需的计算空间,则回溯法所需的计算空间通常为通常为O(h(n)。而显式地存储整个解空间则需要。而显式地存储整个解空间则需要O(2h(n)或或O(h(n)!)内存空间。内存空间。n在搜索过程中,通常采用两种策略避免无效搜索:在搜索过程中,通常采用两种策略避免无效搜索:(1)用)用约束条件约束条件剪去得不到可行解的子树;剪去得不到可行解的子树;(2)用)用限界函数限界函数剪去得不到最优解的子树。剪去得不到最优解的子树。这两类函数统称为这两类函数
47、统称为剪枝函数剪枝函数(Pruning Function)。n在搜索至树中任一结点时,先判断该结点对应的在搜索至树中任一结点时,先判断该结点对应的部分解是否满足部分解是否满足约束条件约束条件,或者是否超出,或者是否超出限界函限界函数数的界,也就是判断该结点是否的界,也就是判断该结点是否包含包含问题的(最问题的(最优)解,如果肯定不包含,则跳过对以该结点为优)解,如果肯定不包含,则跳过对以该结点为根的子树的搜索,即所谓根的子树的搜索,即所谓剪枝剪枝(Pruning);否则,;否则,进入以该结点为根的子树,继续按照深度优先策进入以该结点为根的子树,继续按照深度优先策略搜索。略搜索。n当所给问题是从
48、当所给问题是从n个元素的集合个元素的集合S中找出满足某中找出满足某种性质的种性质的子集子集时,解空间为时,解空间为子集树子集树。n 当所给问题是从当所给问题是从n个元素的集合个元素的集合S中找出满足某中找出满足某种性质的种性质的排列排列时,解空间为时,解空间为排列树排列树。遍历子集树需O(2n)计算时间 遍历排列树需要O(n!)计算时间 void backtrack(int t)if(tn)output(x);else for(int i=0;in)output(x);else for(int i=t;i当前最优载重量当前最优载重量bestw11cxwniii不满足不满足约束函数约束函数不满足
49、不满足上界函数上界函数n n 集装箱数;集装箱数;ww集装箱重量数组;集装箱重量数组;c c第一艘轮船载重量;第一艘轮船载重量;cw cw 在遍历结点处的当前载重量在遍历结点处的当前载重量 bsetw bsetw 当前最优载重量当前最优载重量private static void backtrack(int i)/搜索第搜索第i层结点层结点 if(i n)/到达叶结点到达叶结点 if(cwbestw)bestw=cw;return;if(cw+wi n)/到达叶结点 更新最优解bestx,bestw;return;r-=wi;if(cw+wi bestw)/搜索右子树 xi=0;backtra
50、ck(i+1);r+=wi;复杂度分析复杂度分析算法算法backtrack在最坏情况下在最坏情况下可能需要更新当前最优解可能需要更新当前最优解O(2n)次,每次更新次,每次更新bestx需计需计算时间算时间O(n),从而整个算法,从而整个算法的计算时间复杂性为的计算时间复杂性为O(n2n)。进一步改进算法,可降低时进一步改进算法,可降低时间复杂性为间复杂性为O(2n)。n4皇后问题:*n8皇后问题:1Q2Q3Q4Q5Q6 Q7Q8Q1 2 3 4 5 6 7 8经典的回溯算法,该算法的思路如下经典的回溯算法,该算法的思路如下:依次在棋盘的每一行上摆放一个皇后。每次摆放都要检查当前摆放是否可行。