算法试题实例分析.docx
- 文档编号:26704785
- 上传时间:2023-06-21
- 格式:DOCX
- 页数:18
- 大小:135.67KB
算法试题实例分析.docx
《算法试题实例分析.docx》由会员分享,可在线阅读,更多相关《算法试题实例分析.docx(18页珍藏版)》请在冰豆网上搜索。
算法试题实例分析
计算机算法设计与分析典型实例
整理者:
邓淑平邱海华
分治法的基本思想:
分治法的基本思想是将一个规模为n的问题分解为k哥规模较小的子问题,这些子问题互相独立且与原问题相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。
它的一般的算法设计模式如下:
divide-and-conquer(P)
{
if(|P|<=n0)adhoc(P);
dividePintosmallersubinstancesP1,P2,…,Pk;
for(i=1;i<=k;i++)
yi=divide-and-conquer(Pi);
returnmerge(y1,y2,…,yk);
}
【典型实例1】大整数乘积:
设X和Y都是n位的十进制整数,计算它们的乘积XY。
1)小学手算的程序实现及效率分析。
2)利用分治法求解的程序实现及效率分析,包括效率分析过程中的递归方程求解。
分析:
1)小学手算的程序实现及效率分析。
效率分析:
用小学所学的方法来设计一个计算乘积XY的算法,这样做计算步骤太多,显得效率较低。
如果将每2个1位数的乘法或加法看作一步运算,那么这种方法要作O(n2)步运算才能求出乘积XY。
所以用小学手算的方法效率为O(n2)。
2)利用分治法求解的程序实现及效率分析,包括效率分析过程中的递归方程求解。
用分治法来设计一个更有效的大整数乘积算法。
将n位的二进制整数X和Y各分为2段,每段的长为n/2位(为简单起见,假设n是2的幂),如图所示。
图:
大整数X和Y的分段
由此,X=A2n/2+B,Y=C2n/2+D。
这样,X和Y的乘积为:
XY=[A2n/2+B][C2n/2+D]=AC2n+(AD+CB)2n/2+BD
(1)
如果按式
(1)计算XY,则我们必须进行4次n/2位整数的乘法(AC,AD,BC和BD),以及3次不超过2n位的整数加法(分别对应于式
(1)中的加号),此外还要做2次移位(分别对应于式
(1)中乘2n和乘2n/2)。
所有这些加法和移位共用O(n)步运算。
设T(n)是2个n位整数相乘所需的运算总数,则由式
(1),我们有:
n>1
n=1
由此可得T(n)=O(n2)。
因此,用
(1)式来计算X和Y的乘积并不比小学生的方法更有效。
要想改进算法的计算复杂性,必须减少乘法次数。
为此我们把XY写成另一种形式:
XY=AC*2n+[(A-B)(D-C)+AC+BD]*2n/2+BD (3)
虽然,式(3)看起来比式
(1)复杂些,但它仅需做3次n/2位整数的乘法(AC,BD和(A-B)(D-C)),6次加、减法和2次移位。
由此可得:
n=1
n>1
用解递归方程的套用公式法马上可得其解为T(n)=O(nlog3)=O(n1.59)。
利用式(3),可写出相应的代码:
利用分治法计算大整数的过程,当某个中间值在int所表示的范围内时,可直接用整数的加减乘运算进行计算(同时必须保证进行这种运算的结果也在int所标示的范围内),加快计算速度。
动态规划算法的基本要素:
(1)最优子结构性质
(2)重叠子问题性质
动态规划法的变形——备忘录方法
设计动态规划法算法的步骤:
(1)找出最优解得性质,并刻画其结构特征
(2)递归地定义最优值
(3)以自底向上的方式计算最优值
(4)根据计算最优值时得到的信息构造最优解
【典型实例2】求最大子段和
1)简单蛮力算法的程序实现及其效率分析,
2)分治法求解的程序实现及其效率分析,包括效率分析过程的递归求解。
3)动态规划法求解的程序实现及其效率分析。
分析:
最大字段和问题
给定n个整数(可能为负数)组成的序列a1,a2,…,an,求该序列如
k的子段和的最大值。
当所给的整均为负数时定义子段和为0,依此定义,所求的最优值为max{0,
}例如,当(a1,a2,a3,a4,a4,a6)=(-2,11,-4,13,-5,-2)时,最大子段和为20。
1)简单蛮力算法的程序实现及其效率分析
效率分析:
从这个算法的两个for循环可看出它的时间复杂度,T(n)=O(n2)。
2)分治法求解的程序实现及其效率分析。
最大子段和问题的分治算法。
针对最大子段和这个具体问题本身的结构,我们还可以从算法设计的策略上对上述O(n2)计算时间算法进行更进一步的改进。
从问题的解结构也可以看出,它适合于用分治法求解。
如果将所给的序列a[1:
n]分为长度相等的两段a[1:
n/2]和a[n/2+1:
n],分别求出这两段的最大子段和,则a[1:
n]的最大子段和有三种情况:
(1)a[1:
n]的最大子段和与a[1:
n/2]的最大子段和相同
(2)a[1:
n]的最大子段和与a[n/2+1:
n]的最大子段和相同
(3)a[1:
n]的最大子段和为a+…+a[j],并且1<=i<=n/2,n/2+1<=j<=n。
对于
(1)和
(2)两种情况可递归求得,但是对于情况(3),容易看出a[n/2],a[n/2+1]在最大子段中。
因此,我们可以在a[1:
n/2]中计算出s1=max(a[n/2]+a[n/2-1]+…+a),0<=i<=n/2,并在a[n/2+1:
n]中计算出s2=max(a[n/2+1]+a[n/2+2]+…+a),n/2+1<=i<=n。
则s1+s2为出现情况(3)的最大子段和。
据此可以设计出最大子段和问题的分治算法如下:
该算法所需的计算时间T(n)可满足典型的分治算法递归分式
T(n)=
由此递归方程可知,T(n)=O(nlogn)。
3)动态规划算法来处理这个问题。
最大子段和问题的动态规划算法
在对于上述分治算法的分析中我们注意到,若记b[j]=
则所求的最大子段和为
=
。
由b[j]的定义可易知,当b[j-1]>0时b[j]=b[j-1]+a[j],否则b[j]=a[j]。
故b[j]的动态规划递归式为:
b[j]=max(b[j-1]+a[j],a[j]),1<=j<=n。
据此,可设计出求最大子段和问题的动态规划算法:
上述算法只用到了O(n)的时间复杂度和O
(1)的空间复杂度。
我们从是最大子段和问题的优化过程中可以看出,我们把一个算法从O(n2)优化到O(nlogn),再到O(n)。
最优的算法为动态规划算法。
【典型实例3】0-1本背包问题
1)动态规划法求解问题的一般思路,动态规划法求解本问题的思路及其C/C++程序实现与算法的效率分析。
分析:
动态规划求解问题的一般思路见动态规划法算法的步骤。
0-1背包问题是一个特殊的整数规划问题。
动态规划求解0-1背包问题过程如下:
动态规划是用空间换时间的一种方法的抽象。
其关键是发现子问题和记录其结果。
然后利用这些结果减轻运算量。
程序实现与效率分析:
2)回溯法求解问题的一般思路,回溯法求解本问题的思路及其C/C++程序实现与算法的效率分析。
3)分支限界法法求解问题的一般思路,分支限界法求解本问题的思路及其C/C++程序实现与算法的效率分析。
4)贪心算法在0-1背包问题求解中的应用
1.动态规划求解0-1背包问题
/*1.1最优子结构性质
/*设(y1,y2,...,yn)是给定0-1背包问题的一个最优解,则必有
/*结论,(y2,y3,...,yn)是如下子问题的一个最优解:
/*maxsum_{i=2ton}(vi*xi)
/*
(1)sum_{i=2ton}(wi*xi)<=c-w1*y1
/*
(2)xi∈{0,1},2<=i<=n
/*因为如若不然,则该子问题存在一个最优解(z2,z3,...,zn),
/*而(y2,y3,...,yn)不是其最优解。
那么有:
/*sum_{i=2ton}(vi*zi)>sum_{i=2ton}(vi*yi)
/*且,w1*y1+sum_{i=2ton}(wi*zi)<=c
/*进一步有:
/*v1*y1+sum_{i=2ton}(vi*zi)>sum_{i=1ton}(vi*yi)
/*w1*y1+sum_{i=2ton}(wi*zi)<=c
/*这说明:
(y1,z2,z3,...zn)是所给0-1背包问题的更优解,那么
/*说明(y1,y2,...,yn)不是问题的最优解,与前提矛盾,所以最优
/*子结构性质成立。
/*1.2子问题重叠性质
/*设所给0-1背包问题的子问题P(i,j)为:
/*maxsum_{k=iton}(vk*xk)
/*
(1)sum_{k=iton}(wk*xk)<=j
/*
(2)xk∈{0,1},i<=k<=n
/*问题P(i,j)是背包容量为j、可选物品为i,i+1,...,n时的子问题
/*设m(i,j)是子问题P(i,j)的最优值,即最大总价值。
则根据最优
/*子结构性质,可以建立m(i,j)的递归式:
/*a.递归初始m(n,j)
/*//背包容量为j、可选物品只有n,若背包容量j大于物品n的
/*//重量,则直接装入;否则无法装入。
/*m(n,j)=vn,j>=wn
/*m(n,j)=0,0<=j /*b.递归式m(i,j) /*//背包容量为j、可选物品为i,i+1,...,n /*//如果背包容量j /*m(i,j)=m(i+1,j),0<=j /*//如果j>=wi,则在不装物品i和装入物品i之间做出选择 /*不装物品i的最优值: m(i+1,j) /*装入物品i的最优值: m(i+1,j-wi)+vi /*所以: /*m(i,j)=max{m(i+1,j),m(i+1,j-wi)+vi},j>=wi /* 贪心算法的基本要素: 贪心选择性质和最优子结构性质。 2.贪心法的基本思路: 0-1背包问题与背包问题类似,所不同的是在选择物品 装入背包时,可以选择一部分,而不一定要全部装入背包。 这两类问题都具有最优子结构性质,相当相似。 但是背包问题可以用贪心法求解,而0-1背包问题却不能用贪心法求解。 贪心法之所以得不到最优解,是由于物品不允许分割,因此,无法保证最终能将背包装满,部分闲置的背包容量使背包单位重量的价值降低了。 事实上,在考虑0-1背包问题时,应比较选择物品和不选择物品所导致的方案,然后做出最优解。 由此导出了许多相互重叠的子问题,所以,0-1背包问题可以用动态规划法得到最优解。 在这里就不再用贪心法解0-1背包问题了。 3.回溯算法求解0-1背包问题 1.0-l背包问题是子集选取问题。 一般情况下,0-1背包问题是NP难题。 0-1背包 问题的解空间可用子集树表示。 解0-1背包问题的回溯法与装载问题的回溯法十分类 似。 在搜索解空间树时,只要其左儿子结点是一个可行结点,搜索就进入其左子树。 当 右子树有可能包含最优解时才进入右子树搜索。 否则将右子树剪去。 设r是当前剩余 物品价值总和;cp是当前价值;bestp是当前最优价值。 当cp+r≤bestp时,可剪去右 子树。 计算右子树中解的上界的更好方法是将剩余物品依其单位重量价值排序,然后 依次装入物品,直至装不下时,再装入该物品的一部分而装满背包。 由此得到的价值是 右子树中解的上界。 2.解决办法思路: 为了便于计算上界,可先将物品依其单位重量价值从大到小排序,此后只要顺序考 察各物品即可。 在实现时,由bound计算当前结点处的上界。 在搜索解空间树时,只要其左儿子节点是一个可行结点,搜索就进入左子树,在右子树中有可能包含最优解是才进入右子树搜索。 否则将右子树剪去。 回溯法是一个既带有系统性又带有跳跃性的的搜索算法。 它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树。 算法搜索至解空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。 如果肯定不包含,则跳过对以该结点为根的子树的系统搜索,逐层向其祖先结点回溯。 否则,进入该子树,继续按深度优先的策略进行搜索。 回溯法在用来求问题的所有解时,要回溯到根,且根结点的所有子树都已被搜索遍才结束。 而回溯法在用来求问题的任一解时,只要搜索到问题的一个解就可以结束。 这种以深度优先的方式系统地搜索问题的解的算法称为回溯法,它适用于解一些组合数较大的问题。 2.算法框架: a.问题的解空间: 应用回溯法解问题时,首先应明确定义问题的解空间。 问题的解空间应到少包含问题的一个(最优)解。 b.回溯法的基本思想: 确定了解空间的组织结构后,回溯法就从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。 这个开始结点就成为一个活结点,同时也成为当前的扩展结点。 在当前的扩展结点处,搜索向纵深方向移至一个新结点。 这个新结点就成为一个新的活结点,并成为当前扩展结点。 如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。 换句话说,这个结点不再是一个活结点。 此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。 回溯法即以这种工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已没有活结点时为止。 3.运用回溯法解题通常包含以下三个步骤: a.针对所给问题,定义问题的解空间; b.确定易于搜索的解空间结构; c.以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索; 4.分支限界法求解0-1背包问题 1.问题描述: 已知有N个物品和一个可以容纳M重量的背包,每种物品I的重量为WEIGHT,一个只能全放入或者不放入,求解如何放入物品,可以使背包里的物品的总效益最大。 2.设计思想与分析: 对物品的选取与否构成一棵解树,左子树表示不装入,右表示装入,通过检索问题的解树得出最优解,并用结点上界杀死不符合要求的结点。 效率分析: 动态规划法: 由于函数Knapsack中有一个两重for循环,所以时间复杂度为O[(n+1)x(m+1)].空间复杂度也是O[(n+1)x(m+1)],即O(nm). 回溯法: 由于计算上界的函数MaxBoundary需要O(n)时间,在最坏情况下有 个右儿子结点需要计算上界,所以解0-1背包问题的回溯法算法BackTrack所需要的计算时间为 . 限界分支法: 在使用限界分治法时,就是使用更好的限界剪枝函数使得不必要的解被剔除,但是在最坏情况下的解仍然是和回溯法是相同的。 本算法中也是用到了计算上界的函数MaxBoundary需要O(n)的时间,而且在最坏情况下有 个结点需要计算上界,所以在最坏情况下的时间复杂度仍然为 。 【典型实例4】运动员最佳配对问题 1)回溯法求解问题的一般思路,回溯法求解本问题的思路及其C/C++程序实现及效率分析。 分析: 回溯法求解本问题的思路: 假设男运动员已经按照1到n排好序不动,用一个数组w存放配对的女运动员的编号,即第i号男运动员配第w[i]号女运动员,初始时设w[i]=i,然后不断的重新排列w数组,每得到一次排列,就要计算在此排列下的配对总和,若发现比之前的总和大,则更新最优解。 套用排列树框架,做好初始化后开始回溯,关键在于到达叶子节点时,需要计算sum+=p[i][w[i]]*q[w[i]][i],若发现sum比之前的最优值大,则更新最优值和配对顺序,回溯完成后则可得到最大总和及其相应的运动员配对方法。 2)分支限界法求解问题的一般思路,分支限界法求解本问题的思路及其C/C++程序实现及效率分析。 分支限界法求解本问题的思路: 效率分析: 【典型实例5】旅行售货员问题 1)回溯法求解问题的一般思路,回溯法求解本问题的思路及其C/C++程序实现及效率分析。 2)分支限界法求解问题的一般思路,分支限界法求解本问题的思路及其C/C++程序实现及效率分析 分析: 回溯法即遍历解空间树法,旅行售货员问题是一个排列树的解空间树,解旅行售货员问题即为遍历一个排列树。 回溯法的基本思路: (1)回溯法有“通用的解题法”之称。 用它可以系统地搜索一个问题的所有解或最优解。 回溯法是一个既带系统性又带有跳跃性的搜索算法。 它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发,搜索解空间树。 (2)算法搜索至解空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。 如果肯定不包含,则跳过对以该结点为根的子树的系统性搜索,逐层向其祖先结点回溯(称为剪枝)。 否则,进入该子树,继续按深度优先的策略进行搜索。 (3)回溯法在用来求问题的所有解(或最优解? )时,要回溯到根,且根结点的所有子树都已被搜索遍才结束。 而在求任一解时,只要搜索到一个解就结束。 这种以深度优先的方式系统地搜索问题的解的算法称为回溯法,它适合于解一些组合数较大的问题。 本问题的求解思路 1回溯法 旅行售货员问题的解空间是一棵排列树。 对于排列树的回溯搜索与生成1,2,…,n的所有排列的递归算法perm类似。 开始时 ,相应的排列树由 的所有排列构成。 在递归算法backtrack中,当i=n时,当前的扩展结点是排列树的叶结点的父结点。 此时算法检测图G是否存在一条从顶点 到顶点 的边和从顶点 到顶点1的边。 如果这两条边都存在,则找到一条旅行售货员回路。 此时算法还需要判别这条回路的费用是否优于当前已经找到的最优回路的费用bestc。 如果是,则必须更新当前的最优值bestc和当前的最优解bestx。 当 时,当前的扩展结点位于排列树的第 层。 图G中存在从顶点 到达顶点 的边时, 构成图G中的一条路径,且当 的费用小于当前最优值时算法进入排列树的第i层;否则,则剪去相应的子树。 算法中用变量cc记录当前路径 的费用。 2分支限界法 分枝限界法的基本思想: 分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。 问题的解空间树时表示问题解空间的一棵有序树,常见的由子集树和排列树。 在搜索问题的解空间树时,分支限界法与回溯法的主要不同在于它们对当前扩展结点所采用的扩展方式。 在分支限界法中,每一个活结点只有一次机会成为扩展检点。 活结点一旦成为扩展结点,就一次性产生其所有儿子结点。 在这些儿子结点总,导致不可行的解或者非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。 此后从活结点表中取下一结点成为当前扩展结点,并重复上述扩展过程。 这个过程一直持续到找到所需的解或活结点表空为止。 从活结点表中选择下一扩展结点的不同方式将导致不同的分支限界法。 最常见的有以下两种方式。 (1)队列式(FIFO)分支限界法 队列式分支限界法将活结点表组织成一个队列,并按照队列的先进先出FIFO(firstinfirstout)原则选取下一个结点为当前扩展结点。 (2)优先队列式分支限界法 优先队列式的分支限界法将活结点表组织成一个优先队列,并按照优先队列中规定的结点优先级选取优先级最高的下一个结点成为当前扩展结点。 优先队列中规定的结点优先级常用一个与该结点相关的数值p表示。 结点优先级的高低与p值的大小相关。 最大优先队列规定p值较大的结点优先级较高。 在算法是现实通常用最大堆来实现最大优先队列,用最大堆的removeMax运算抽取堆中下一个结点成为当前扩展结点,体现最大效益优先的原则。 类似的,最小优先队列规定p值较小的结点优先级较高。 在算法实现时通常用最小堆来实现最小优先队列,用最小堆的removeMin运算抽取堆中下一个结点成为当前的扩展结点,体现最小费用优先的原则。 用优先队列式分支限界法解具体问题式,应该根据具体问题的特点确定选用最大优先队列或者最小优先队列表示解空间的活结点表。 考察4城市旅行售货员的例子,如图3-1所示。 该问题的解空间树一棵排列树。 解此问题的队列式分支限界法以排列树中结点B作为初始扩展结点。 此时,活结点队列为空。 由于从图G的顶点1到顶点2,3,4均有边相连,所以结点B的儿子结点C,D,E均为可行结点,它们被加入到活结点队列中,并舍去当前扩展结点B。 当前活结点队列中的队首结点C成为下一个扩展结点。 由于图G的顶点2到顶点3和4有边相连,故结点C的2个儿子结点F和G均为可行结点,从而被加入到活结点队列中。 接下来,结点D和结点E相继成为扩展结点而被扩展。 此时,活结点队列中的结点为F,G,H,I,J,K。 结点F成为下一个扩展结点,其儿子结点L是一个叶结点。 找到了一条旅行售货员回路,其费用为59。 从下一个扩展结点G得到叶结点M,它相应的旅行售货员回路的费用为66。 结点H依次成为扩展结点,得到结点N相应的旅行售货员回路,其费用为25。 这已经时最好的一条回路。 下一个扩展结点时结点I。 以结点I为根的子树被剪去。 最后,结点J,K被依次扩展,活结点队列成为空,算法终止。 算法搜索得到最优值为25,相应的最优解时从根结点到结点N的路径(1,3,2,4,1)。 解同一问题的优先队列式分支限界法用一极小堆来存储活结点表,。 其优先级是结点的当前费用。 算法还是从排列树的结点B和空优先队列开始。 结点B被扩展后,它的3个儿子结点C,D和E被一次插入堆中。 此时,由于E是堆中具有最小当前费用的节点,所以处于堆顶位置,它自然成为下一个扩展结点。 结点E被扩展后,其儿子结点J和K被插入当前堆中,它们的费用分别为14和24。 此时堆顶元素是结点D,它成为下一个结点。 如此,它的两个儿子结点H和I被插入堆中。 此时,堆中含有结点C,H,I,J,K。 在这些结点中,结点H具有最小费用,从而它成为下一个扩展结点。 扩展结点H后得到一条旅行售货员回路(1,3,2,4,1),相应的最小费用为25。 接下来结点J成为扩展结点,由此得到另外一条旅行售货员回路(1,4,2,3,1),相应的费用为25。 此后的扩展结点为K,I。 由结点K得到的可行解费用高于当前最优解。 结点I本身的费用已高于当前最优解。 从而它们都不是最好的解。 最后,优先队列为空,算法终止。 三、问题的求解结果与算法分析 1问题的求解结果 (1)当结点数为N=4,其各个点之间的边的权矩阵为 时, 即其解为(1,4,2,3,1),最优解值为13。 2算法分析 (1)回溯法 如果不考虑更新bestx所需的计算时间,则算法backtrack需要 计算时间。 由于算法backtrack在最坏的情况下可能需要更新当前最优解 次,每次更新bestx需 计算时间,从而整个算法的计算时间复杂性为 。 (2)分支限界法 由于是NP问题,其时间复杂度很高,当相对于回溯法而言,分支限界法剪掉了一些不必要的计算,效率有很大的提高,但是在最坏的情况下可能需要满历所有的结点。 此时的时间复杂度也是很高的。 【典型实例6】n后问题 1)回溯法求解问题的一般思路,回溯法求解本问题的思路及其C/C++程序实现及效率分析。 2)LasVegas算法求解问题的一般思路,LasVegas算法求解本问题的思路及其C/C++程序实现及效率分析。 分析: 回溯法一般思路: 在用回溯法搜索解空间树时,通常采用两种策略来避免无效搜索,提高回溯法的搜索效率。 其一是约束函数在扩展结点处剪去不满足约束的子树;其二是用限界函数剪去不能得到最优解的子树(如前面的M点和G点)
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 算法 试题 实例 分析
![提示](https://static.bdocx.com/images/bang_tan.gif)