01背包问题的算法设计策略对比与分析Word文件下载.docx
- 文档编号:21734413
- 上传时间:2023-02-01
- 格式:DOCX
- 页数:13
- 大小:129.22KB
01背包问题的算法设计策略对比与分析Word文件下载.docx
《01背包问题的算法设计策略对比与分析Word文件下载.docx》由会员分享,可在线阅读,更多相关《01背包问题的算法设计策略对比与分析Word文件下载.docx(13页珍藏版)》请在冰豆网上搜索。
下面描述算法复杂性时都是用的简化的复杂性算法分析,引入了渐近意义的记号O,Ω,θ,和o。
O表示渐近上界Ω表示渐近下界:
θ表示同阶即:
f(n)=O(g(n))且f(n)=Ω(g(n))
2常见的算法分析设计策略介绍
2.1递归与分治策略
分治法的设计思想是,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
直接或间接地调用自身的算法称为递归算法。
用函数自身给出定义的函数称为递归函数。
由分治法产生的子问题往往是原问题的较小模式,这就为使用递归技术提供了方便。
在这种情况下,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩小到很容易直接求出其解。
这自然导致递归过程的产生。
分治与递归像一对孪生兄弟,经常同时应用在算法设计之中,并由此产生许多高效算法。
递归算法举例:
Fibonacci数列
无穷数列1,1,2,3,5,8,13,21,34,55,……,称为Fibonacci数列。
它可以递归地定义为:
第n个Fibonacci数可递归地计算如下:
intfibonacci(intn)
{
if(n<
=1)return1;
returnfibonacci(n-1)+fibonacci(n-2);
}
从上看出:
递归算法的有点为:
结构清晰,可读性强,而且容易用数学归纳法来证明算法的正确性,因此它为设计算法、调试程序带来很大方便。
缺点为:
递归算法的运行效率较低,无论是耗费的计算时间还是占用的存储空间都比非递归算法要多。
分治算法:
一个分治法将规模为n的问题分成k个规模为n/m的子问题去解。
设分解阀值n0=1,且adhoc解规模为1的问题耗费1个单位时间。
再设将原问题分解为k个子问题以及用merge将k个子问题的解合并为原问题的解需用f(n)个单位时间。
用T(n)表示该分治法解规模为|P|=n的问题所需的计算时间,则有:
通过迭代法求得方程的解:
算法举例:
二分搜索技术:
给定已按升序排好序的n个元素a[0:
n-1],现要在这n个元素中找出一特定元素x。
据此容易设计出二。
搜索算法:
template<
classType>
intBinarySearch(Typea[],constType&
x,intl,intr)
{
while(r>
=l){
intm=(l+r)/2;
if(x==a[m])returnm;
if(x<
a[m])r=m-1;
elsel=m+1;
}
return-1;
}
算法复杂度分析:
每执行一次算法的while循环,待搜索数组的大小减少一半。
因此,在最坏情况下,while循环被执行了O(logn)次。
循环体内运算需要O
(1)时间,因此整个算法在最坏情况下的计算时间复杂性为O(logn)。
快速排序法:
在快速排序中,记录的比较和交换是从两端向中间进行的,关键字较大的记录一次就能交换到后面单元,关键字较小的记录一次就能交换到前面单元,记录每次移动的距离较大,因而总的比较和移动次数较少。
voidQuickSort(Typea[],intp,intr)
if(p<
r){
intq=Partition(a,p,r);
QuickSort(a,p,q-1);
//对左半段排序
QuickSort(a,q+1,r);
//对右半段排序
}
复杂性分析:
最坏时间复杂度:
O(n2)
平均时间复杂度:
O(nlogn)
辅助空间:
O(n)或O(logn)
2.2动态规划
动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题但是经分解得到的子问题往往不是互相独立的。
不同子问题的数目常常只有多项式量级。
在用分治法求解时,有些子问题被重复计算了许多次。
如果能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,就可以避免大量重复计算,从而得到多项式时间算法。
方法步骤:
1)找出最优解的性质,并刻划其结构特征。
2)递归地定义最优值。
3)以自底向上的方式计算出最优值。
4)根据计算最优值时得到的信息,构造最优解。
举例:
矩阵连成问题
基本要素:
1)最优子结构
2)重叠子问题
3)备忘录方法
将矩阵连乘积简记为A[i:
j],这里i≤j
考察计算A[i:
j]的最优计算次序。
设这个计算次序在矩阵Ak和Ak+1之间将矩阵链断开,i≤k<
j,则其相应完全加括号方式为:
计算量:
A[i:
k]的计算量加上A[k+1:
j]的计算量,再加上
k]和A[k+1:
j]相乘的计算量。
算法如下:
voidMatrixChain(int*p,intn,int**m,int**s)
for(inti=1;
i<
=n;
i++)m[i][i]=0;
for(intr=2;
r<
r++)
=n-r+1;
i++){
intj=i+r-1;
m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j];
s[i][j]=i;
for(intk=i+1;
k<
j;
k++){
intt=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
if(t<
m[i][j]){m[i][j]=t;
s[i][j]=k;
算法复杂度分析:
算法matrixChain的主要计算量取决于算法中对r,i和k的3重循环。
循环体内的计算量为O
(1),而3重循环的总次数为O(n3)。
因此算法的计算时间上界为O(n3)。
算法所占用的空间显然为O(n2)。
2.3贪心算法
顾名思义,贪心算法总是作出在当前看来最好的选择。
也就是说贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上的局部最优选择。
当然,希望贪心算法得到的最终结果也是整体最优的。
虽然贪心算法不能对所有问题都得到整体最优解,但对许多问题它能产生整体最优解。
如单源最短路经问题,最小生成树问题等。
在一些情况下,即使贪心算法不能得到整体最优解,其最终结果却是最优解的很好近似。
可用贪心算法解决的问题的性质:
1)贪心选择性质
2)最优子结构性质
最优装载问题
有一批集装箱要装上一艘载重量为c的轮船。
其中集装箱i的重量为Wi。
最优装载问题要求确定在装载体积不受限制的情况下,将尽可能多的集装箱装上轮船。
算法描述
最优装载问题可用贪心算法求解。
采用重量最轻者先装的贪心选择策略,可产生最优装载问题的最优解。
具体算法描述如下。
template<
voidLoading(intx[],Typew[],Typec,intn)
int*t=newint[n+1];
Sort(w,t,n);
for(inti=1;
i++)x[i]=0;
=n&
&
w[t[i]]<
=c;
i++){x[t[i]]=1;
c-=w[t[i]];
最优装载问题满足贪心算法的两个基本性质,可以用贪心算法实现。
2.4回溯法
回溯法的基本做法是搜索,或是一种组织得井井有条的,能避免不必要搜索的穷举式搜索法。
这种方法适用于解一些组合数相当大的问题。
回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。
算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。
如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;
否则,进入该子树,继续按深度优先策略搜索。
为了避免生成那些不可能产生最佳解的问题状态,要不断地利用限界函数(boundingfunction)来处死那些实际上不可能产生所需解的活结点,以减少问题的计算量。
具有限界函数的深度优先生成法称为回溯法。
回溯法的基本思想:
(1)针对所给问题,定义问题的解空间;
(2)确定易于搜索的解空间结构;
(3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索
举例分析:
符号三角形问题:
---+
-+++-
-++-
-+-
--
+
下图是由14个“+”和14个“-”组成的符号三角形。
2个同号下面都是“+”,2是“-”。
解法:
解向量:
用n元组x[1:
n]表示符号三角形的第一行。
可行性约束函数:
当前符号三角形所包含的“+”个数与“-”个数均不超过n*(n+1)/4
无解的判断:
n*(n+1)/2为奇数
voidTriangle:
:
Backtrack(intt)
if((count>
half)||(t*(t-1)/2-count>
half))return;
if(t>
n)sum++;
else
for(inti=0;
i<
2;
i++){
p[1][t]=i;
count+=i;
for(intj=2;
j<
=t;
j++){
p[j][t-j+1]=p[j-1][t-j+1]^p[j-1][t-j+2];
count+=p[j][t-j+1];
Backtrack(t+1);
j++)
count-=p[j][t-j+1];
count-=i;
复杂度分析
计算可行性约束需要O(n)时间,在最坏情况下有O(2n)个结点需要计算可行性约束,故解符号三角形问题的回溯算法所需的计算时间为O(n2n)。
2.5分支限界法
分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。
在分支限界法中,每一个活结点只有一次机会成为扩展结点。
活结点一旦成为扩展结点,就一次性产生其所有儿子结点。
在这些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。
此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。
这个过程一直持续到找到所需的解或活结点表为空时为止。
常见的两种分支限界法:
(1)队列式(FIFO)分支限界法
按照队列先进先出(FIFO)原则选取下一个节点为扩展节点。
(2)优先队列式分支限界法
按照优先队列中规定的优先级选取优先级最高的节点成为当前扩展节点。
举例:
0-1背包问题
算法思想:
首先,要对输入数据进行预处理,将各物品依其单位重量价值从大到小进行排列。
在下面描述的优先队列分支限界法中,节点的优先级由已装袋的物品价值加上剩下的最大单位重量价值的物品装满剩余容量的价值和。
算法首先检查当前扩展结点的左儿子结点的可行性。
如果该左儿子结点是可行结点,则将它加入到子集树和活结点优先队列中。
当前扩展结点的右儿子结点一定是可行结点,仅当右儿子结点满足上界约束时才将它加入子集树和活结点优先队列。
当扩展到叶节点时为问题的最优值。
部分算法如下:
while(i!
=n+1){//非叶结点
//检查当前扩展结点的左儿子结点
Typewwt=cw+w[i];
if(wt<
=c){//左儿子结点为可行结点
if(cp+p[i]>
bestp)bestp=cp+p[i];
AddLiveNode(up,cp+p[i],cw+w[i],true,i+1);
up=Bound(i+1);
//检查当前扩展结点的右儿子结点
if(up>
=bestp)//右子树可能含最优解
AddLiveNode(up,cp,cw,false,i+1);
//取下一个扩展节点(略)
3结合0-1背包问题详述动态规划、贪心算法、回溯法、分支限界法解决问题的过程
0-1背包问题:
给定n种物品和一背包。
物品i的重量是wi,其价值为vi,背包的容量为C。
问应如何选择装入背包的物品,使得装入背包中物品的总价值最大?
动态规划算法:
设所给0-1背包问题的子问题
的最优值为m(i,j),即m(i,j)是背包容量为j,可选择物品为i,i+1,…,n时0-1背包问题的最优值。
由0-1背包问题的最优子结构性质,可以建立计算m(i,j)的递归式如下。
从m(i,j)的递归式容易看出,算法需要O(nc)计算时间。
当背包容量c很大时,算法需要的计算时间较多。
例如,当c>
2n时,算法需要Ω(n2n)计算时间。
改进算法:
由m(i,j)的递归式容易证明,在一般情况下,对每一个确定的i(1≤i≤n),函数m(i,j)是关于变量j的阶梯状单调不减函数。
跳跃点是这一类函数的描述特征。
在一般情况下,函数m(i,j)由其全部跳跃点唯一确定。
对每一个确定的i(1≤i≤n),用一个表p[i]存储函数m(i,j)的全部跳跃点。
表p[i]可依计算m(i,j)的递归式递归地由表p[i+1]计算,初始时p[n+1]={(0,0)}。
函数m(i,j)是由函数m(i+1,j)与函数m(i+1,j-wi)+vi作max运算得到的。
因此,函数m(i,j)的全部跳跃点包含于函数m(i+1,j)的跳跃点集p[i+1]与函数m(i+1,j-wi)+vi的跳跃点集q[i+1]的并集中。
易知,(s,t)q[i+1]当且仅当wisc且(s-wi,t-vi)p[i+1]。
因此,容易由p[i+1]确定跳跃点集q[i+1]如下q[i+1]=p[i+1](wi,vi)={(j+wi,m(i,j)+vi)|(j,m(i,j))p[i+1]}
另一方面,设(a,b)和(c,d)是p[i+1]q[i+1]中的2个跳跃点,则当ca且d<
b时,(c,d)受控于(a,b),从而(c,d)不是p[i]中的跳跃点。
除受控跳跃点外,p[i+1]q[i+1]中的其它跳跃点均为p[i]中的跳跃点。
由此可见,在递归地由表p[i+1]计算表p[i]时,可先由p[i+1]计算出q[i+1],然后合并表p[i+1]和表q[i+1],并清除其中的受控跳跃点得到表p[i]。
改进后复杂性分析:
上述算法的主要计算量在于计算跳跃点集p[i](1≤i≤n)。
由于q[i+1]=p[i+1](wi,vi),故计算q[i+1]需要O(|p[i+1]|)计算时间。
合并p[i+1]和q[i+1]并清除受控跳跃点也需要O(|p[i+1]|)计算时间。
从跳跃点集p[i]的定义可以看出,p[i]中的跳跃点相应于xi,…,xn的0/1赋值。
因此,p[i]中跳跃点个数不超过2n-i+1。
由此可见,算法计算跳跃点集p[i]所花费的计算时间为
从而,改进后算法的计算时间复杂性为O(2n)。
当所给物品的重量wi(1≤i≤n)是整数时,|p[i]|≤c+1,(1≤i≤n)。
在这种情况下,改进后算法的计算时间复杂性为O(min{nc,2n})。
贪心算法:
在选择装入背包的物品时,对每种物品i只有2种选择,即装入背包或不装入背包。
不能将物品i装入背包多次,也不能只装入部分的物品i。
贪心算法的两条性质,可以放入物品的部分,使它适合背包问题。
不适合0-1背包问题,所以不能用贪心算法计算。
回溯法:
回溯法的三个条件:
解空间:
子集树
上界函数:
classTypew,classTypep>
TypepKnap<
Typew,Typep>
Bound(inti)
{//计算上界
Typewcleft=c-cw;
//剩余容量
Typepb=cp;
//以物品单位重量价值递减序装入物品
while(i<
w[i]<
=cleft){
cleft-=w[i];
b+=p[i];
i++;
//装满背包
if(i<
=n)b+=p[i]/w[i]*cleft;
returnb;
分支界限法:
如上第二部分2.5所述。
4对比分析以上四种算法策略
四种算法中都用到了最优子结构这一概念,判断用何种算法,取决于具体问题的具体分析,看是否适用本身,能达到最优算法。
动态规划算法与分治算法相似。
用于贪心算法的有活动安排问题,最优装载问题,哈夫曼编码问题,单源最短路径问题。
对于回溯法,通过约束找到满足条件的所有解,特点为能进就进,不能进就退回来,与递归类似。
分支法与回溯法类似,但解的目标是通过约束找到满足条件的一个解,或找到在某种意义下的最优解。
回溯法以深度优先的方式搜索解空间树,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间树。
5课程总结
通过本课程的学习,加强了我的思维能力,增强了思考问题的深度,也了解到,在编写程序时,算法对于一个程序的重要性,以前只知道程序就是代码,现在知道,代码并不是最重要的部分,好的算法可以说是一个程序成功的关键,也是功能好坏的体现,好的算法可以提高程序的效率,在一定程度上可以说是没有算法,就谈不上编写程序,通俗的说,算法在是一种解决问题的方法,是从事计算机行业人员的必备能力。
参考文献
[1]王晓东.计算机算法设计与分析.电子工业出版社
[2]余祥宣,崔国华,邹海明.计算机算法基础[M].武汉:
华中科技大学出版社,2003.
[3]吕国英,任瑞征,钱宇华.算法设计与分析:
清华大学出版社.
[4]《算法设计与分析》(第二版)霍红卫编著,西安电子科技大学出版社.
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 01 背包 问题 算法 设计 策略 对比 分析