100930122闫博文算法设计与分析课程设计.docx
- 文档编号:27757587
- 上传时间:2023-07-04
- 格式:DOCX
- 页数:24
- 大小:87KB
100930122闫博文算法设计与分析课程设计.docx
《100930122闫博文算法设计与分析课程设计.docx》由会员分享,可在线阅读,更多相关《100930122闫博文算法设计与分析课程设计.docx(24页珍藏版)》请在冰豆网上搜索。
100930122闫博文算法设计与分析课程设计
HUNANCITYUNIVERSITY
算法设计与分析课程设计
题目:
背包问题的算法设计策略对比与分析
专业:
信息与计算科学
学号:
1009301-22
姓名:
闫博文
指导教师:
夏方礼
成绩:
二0一三年六月二十八日
引言
算法的概念是至关重要的,例如,在一个大型软件系统的开发中,设计出有效的算法将起决定性的作用。
算法是解决问题的一种方法或一个过程。
程序是算法用某种设计语言具体实现描。
计算机的普及极大的改变了人们的生活。
目前,各行业、各领域都广泛采用了计算机信息技术,并由此产生出开发各种应用软件的需求。
为了以最小的成本、最快的速度、最好的质量开发出适合各种应用需求的软件,必须遵循软件工程的原则。
设计一个高效的程序不仅需要编程小技巧,更需要合理的数据组织和清晰高效的素算法,这正是计算机科学领域数据结构与算法设计所研究的主要内容。
1、算法复杂性分析的方法介绍
算法复杂性是算法运行所需要的计算机资源的量,需要时间资源的量称为时间复杂性,需要的空间资源的量称为空间复杂性。
这个量应该只依赖于算法要解的问题的规模、算法的输入和算法本身的函数。
如果分别用N、I和A表示算法要解问题的规模、算法的输入和算法本身,而且用C表示复杂性,那么,应该有C=F(N,I,A)。
一般把时间复杂性和空间复杂性分开,并分别用T和S来表示,则有:
T=T(N,I)和S=S(N,I)。
(通常,让A隐含在复杂性函数名当中
最坏情况下的时间复杂性:
最好情况下的时间复杂性:
平均情况下的时间复杂性:
其中DN是规模为N的合法输入的集合;I*是DN中使T(N,I*)达到Tmax(N)的合法输入;是中使T(N,I*)达到Tmin(N)的合法输入;而P(I)是在算法的应用中出现输入I的概率。
a.算法复杂性在渐近意义下的阶:
渐近意义下的记号:
O、Ω、θ、o设f(N)和g(N)是定义在正数集上的正函数。
b.O的定义
如果存在正的常数C和自然数N0,使得当NN0时有f(N)Cg(N),则称函数f(N)当N充分大时上有界,且g(N)是它的一个上界,记为f(N)=O(g(N))。
即f(N)的阶不高于g(N)的阶。
根据O的定义,容易证明它有如下运算规则:
(1)O(f)+O(g)=O(max(f,g));
(2)O(f)+O(g)=O(f+g);
(3)O(f)O(g)=O(fg);
(4)如果g(N)=O(f(N)),则O(f)+O(g)=O(f);
(5)O(Cf(N))=O(f(N)),其中C是一个正的常数;
(6)f=O(f)。
c.Ω的定义
如果存在正的常数C和自然数N0,使得当NN0时有f(N)Cg(N),则称函数f(N)当N充分大时下有界,且g(N)是它的一个下界,记为f(N)=Ω(g(N))。
即f(N)的阶不低于g(N)的阶。
d.θ的定义
定义f(N)=θ(g(N))当且仅当f(N)=O(g(N))且f(N)=Ω(g(N))。
此时称f(N)与g(N)同阶。
e.o的定义
对于任意给定的ε>0,都存在正整数N0,使得当NN0时有f(N)/Cg(N)ε,则称函数f(N)当N充分大时的阶比g(N)低,记为f(N)=o(g(N))。
例如,4NlogN+7=o(3N2+4NlogN+7)。
2、常见的算法分析设计策略介绍
a.递归与分治策略
分治法的设计思想是,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
对这k个子问题分别求解。
如果子问题的规模仍然不够小,则再划分为k个子问题,如此递归的进行下去,直到问题规模足够小,很容易求出其解为止。
将求出的小规模的问题的解合并为一个更大规模的问题的解,自底向上逐步求出原来问题的解。
由于分治法产生的子问题往往是原来问题的较小规模,这就为使用递归技术提供了方便。
在这种情况下,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩小到很容易求出其解。
由此自然引出递归算法。
分治与递归像是一对孪生兄弟,经常同时应用在算法设计之中,并由此产生许多高效的算法。
b.动态规划
动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题。
但是经分解得到的子问题往往不是互相独立的。
不同子问题的数目常常只有多项式量级。
在用分治法求解时,有些子问题被重复计算了许多次。
如果能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,就可以避免大量重复计算,从而得到多项式时间算法。
动态规划的一般步骤:
找出最优解的性质,并刻划其结构特征。
递归地定义最优值。
以自底向上的方式计算出最优值。
根据计算最优值时得到的信息,构造最优解。
c.贪心算法
顾名思义,贪心算法总是作出在当前看来最好的选择。
也就是说贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上的局部最优选择。
具有最优子结构性质的问题,用贪心算法更简单、更直接且解题效率更高。
当然,希望贪心算法得到的最终结果也是整体最优的。
虽然贪心算法不能对所有问题都得到整体最优解,但对许多问题它能产生整体最优解。
如单源最短路经问题,最小生成树问题等。
在一些情况下,即使贪心算法不能得到整体最优解,其最终结果却是最优解的很好近似。
d.回溯法
有许多问题,当需要找出它的解集或者要求回答什么解是满足某些约束条件的最佳解时,往往要使用回溯法。
回溯法的基本做法是搜索,或是一种组织得井井有条的,能避免不必要搜索的穷举式搜索法。
这种方法适用于解一些组合数相当大的问题。
回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。
算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。
如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。
问题的解向量:
回溯法希望一个问题的解能够表示成一个n元式(x1,x2,…,xn)的形式。
显约束:
对分量xi的取值限定。
隐约束:
为满足问题的解而对不同分量之间施加的约束。
解空间:
对于问题的一个实例,解向量满足显式约束条件的所有多元组,构成了该实例的一个解空间。
e.分支限界法
分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。
在分支限界法中,每一个活结点只有一次机会成为扩展结点。
活结点一旦成为扩展结点,就一次性产生其所有儿子结点。
在这些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。
此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。
这个过程一直持续到找到所需的解或活结点表为空时为止。
从活结点表中选择下一扩展结点的不同方式导致不同的分支限界法:
队列式(FIFO)分支限界法:
按照队列先进先出(FIFO)原则选取下一个节点为扩展节点。
优先队列式分支限界法:
按照优先队列中规定的优先级选取优先级最高的节点成为当前扩展节点。
最大优先队列:
使用最大堆,体现最大效益优先
最小优先队列:
使用最小堆,体现最小费用优先
3、结合0-1背包问题详述动态规划、贪心算法、回溯法、分支限界法解决问题的过程
0-1背包问题:
给定n种物品和一个背包。
物品i的重量是Wi,其价值为Vi,背包的容量是c。
问应如何选择装入背包中的物品,使得装入背包中的物品总价值最大。
在选择装入背包的物品时,对每种物品i只能有两种选择,装入包或者不装入。
不能物品i装入包多次,也不能之装入部分的物品i。
a.动态规划算法
此问题的形式化描述是,给定c>0,Wi>0,Vi>0,1
i
n,要求找出一个n元0-1向量(x1,x2,……xn),
Xi
{0,1},1
i
n,使得
,而且
达到最大。
因此0-1背包问题是一个特殊的整数规划问题:
b.算法描述
设所给0-1背包问题的子问题
max∑(Vk*Xk)k=i..n;
max∑(Vk*Xk)〈=j(k=i..n);
Xk∈{0,1},i〈=k〈=n;
的最优值为m(i,j)是背包容量为j,可选择物品为i,i+1,…,n时0-1背包问题的最优值。
由0-1背包问题的最优子结构性质,我们可以建立计算m(i
j)的递归式如下:
m(i,j)=max{m(i+1,j),m(i+1,j-wi)+Vi}j>=wi
m(i,j)=m(i+1,j)0<=j m(n,j)=Vnj>=wn m(n,j)=00<=j 程序: #include #defineMax101 intm[Max][Max],w[Max],v[Max],x[Max]; voidKnapsack(intc,intn) { intjMax=min(w[n]-1,c); for(intj=0;j<=jMax;j++)m[n][j]=0; for(intj=w[n];j<=c;j++)m[n][j]=v[n]; for(inti=n-1;i>1;i--) { jMax=min(w[i]-1,c); for(intj=0;j for(intj=w[i];j<=c;j++)m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]); } m[1][c]=m[2][c]; if(c>=w[1])m[1][c]=max(m[1][c],m[2][c-w[1]]+v[1]); } voidTraceback(intc,intn) { for(inti=0;i if(m[i][c]==m[i+1][c])x[i]=0; else{ x[i]=1; c-=w[i]; } x[n]=(m[n][c])? 1: 0; } intmain() { intn,i,c; while(scanf("%d",&n)&&n) { scanf("%d",&c); for(i=1;i<=n;i++)scanf("%d%d",&v[i],&w[i]); Knapsack(c,n); Traceback(c,n); for(i=1;i<=n;i++)printf("%d",x[i]); printf("\n"); } return0; } c.贪心算法 问题描述: 假定有n个物体和一个背包,物体i有质量wi,价值为pi,而背包的载荷能力为M。 若将物体i的一部分xi(1<=i<=n,0<=xi<=1)装入背包中,则有价值pi*xi。 在约束条件(w1*x1+w2*x2+…………+wn*xn)<=M下使目标(p1*x1+p2*x2+……+pn*xn)达到极大,此处0<=xi<=1,pi>0,1<=i<=n.这个问题称为背包问题(Knapsackproblem)。 算法描述首先计算每种物品单位重量的价值Vi/Wi,然后,依贪心选择策略,将尽可能多的单位重量价值最高的物品装入背包。 若将这种物品全部装入背包后,背包内的物品总重量未超过C,则选择单位重量价值次高的物品并尽可能多地装入背包。 依此策略一直地进行下去,直到背包装满为止。 程序代码: #include structgoodinfo { floatp;//物品效益 floatw;//物品重量 floatX;//物品该放的数量 intflag;//物品编号 };//物品信息结构体 voidInsertionsort(goodinfogoods[],intn) { intj,i; for(j=2;j<=n;j++) { goods[0]=goods[j]; i=j-1; while(goods[0].p>goods[i].p) { goods[i+1]=goods[i]; i--; } goods[i+1]=goods[0]; }}//按物品效益,重量比值做升序排列 voidbag(goodinfogoods[],floatM,intn) { floatcu; inti,j; for(i=1;i<=n;i++) goods[i].X=0; cu=M;//背包剩余容量 for(i=1;i { if(goods[i].w>cu)//当该物品重量大与剩余容量跳出 break; goods[i].X=1; cu=cu-goods[i].w;//确定背包新的剩余容量 } if(i<=n) goods[i].X=cu/goods[i].w;//该物品所要放的量 for(j=2;j<=n;j++)/*按物品编号做降序排列*/ { goods[0]=goods[j]; i=j-1; while(goods[0].flag { goods[i+1]=goods[i]; i--; } goods[i+1]=goods[0]; } /////////////////////////////////////////// cout<<"最优解为: "< for(i=1;i<=n;i++) { cout<<"第"< "; cout< } } voidmain() { cout<<"|--------运用贪心法解背包问题---------|"< cout<<"|---powerbyzhanjiantao(028054115)---|"< cout<<"|-------------------------------------|"< intj; intn; floatM; goodinfo*goods;//定义一个指针 while(j) { cout<<"请输入物品的总数量: "; cin>>n; goods=newstructgoodinfo[n+1];// cout<<"请输入背包的最大容量: "; cin>>M; cout< inti; for(i=1;i<=n;i++) {goods[i].flag=i; cout<<"请输入第"< "; cin>>goods[i].w; cout<<"请输入第"< "; cin>>goods[i].p; goods[i].p=goods[i].p/goods[i].w;//得出物品的效益,重量比 cout< } Insertionsort(goods,n); bag(goods,M,n); cout<<"press<1>torunagian"< cout<<"press<0>toexit"< cin>>j; } } d.回溯算法 问题描述: 对于0-1背包问题回溯法的一个实例,n=4,c=7,p=[9,10,7,4],w=[3,5,2,1].这4个物品的单位重量价值分别为[3,2,3,5,4].以物品为单位价值的递减序装入物品。 先装入物品4,然后装入物品3和1.装入这3个物品后,剩余的背包容量为1,只能装入0.2的物品2.由此可得到一个解为x=[1,0,2,1,1],其相应的价值为22.尽管这不是一个可行解,但可以证明其价值是最有大的上界。 因此,对于这个实例,最优值不超过22. 算法描述: 0-l背包问题是子集选取问题。 一般情况下,0-1背包问题是NP难题。 0-1背包问题的解空间可用子集树表示。 解0-1背包问题的回溯法与装载问题的回溯法十分类似。 在搜索解空间树时,只要其左儿子结点是一个可行结点,搜索就进入其左子树。 当右子树有可能包含最优解时才进入右子树搜索。 否则将右子树剪去。 设r是当前剩余物品价值总和;cp是当前价值;bestp是当前最优价值。 当cp+r≤bestp时,可剪去右子树。 计算右子树中解的上界的更好方法是将剩余物品依其单位重量价值排序,然后依次装入物品,直至装不下时,再装入该物品的一部分而装满背包。 由此得到的价值是右子树中解的上界。 程序: #include usingnamespacestd; classKnap { friendintKnapsack(intp[],intw[],intc,intn); public: voidprint() { for(intm=1;m<=n;m++) { cout< } cout< }; private: intBound(inti); voidBacktrack(inti); intc;//背包容量 intn;//物品数 int*w;//物品重量数组 int*p;//物品价值数组 intcw;//当前重量 intcp;//当前价值 intbestp;//当前最优值 int*bestx;//当前最优解 int*x;//当前解 }; intKnap: : Bound(inti) { //计算上界 intcleft=c-cw;//剩余容量 intb=cp; //以物品单位重量价值递减序装入物品 while(i<=n&&w[i]<=cleft) { cleft-=w[i]; b+=p[i]; i++; } //装满背包 if(i<=n) b+=p[i]/w[i]*cleft; returnb; } voidKnap: : Backtrack(inti) { if(i>n) { if(bestp { for(intj=1;j<=n;j++) bestx[j]=x[j]; bestp=cp; } return; } if(cw+w[i]<=c)//搜索左子树 { x[i]=1; cw+=w[i]; cp+=p[i]; Backtrack(i+1); cw-=w[i]; cp-=p[i]; } if(Bound(i+1)>bestp)//搜索右子树 { x[i]=0; Backtrack(i+1); } } classObject { friendintKnapsack(intp[],intw[],intc,intn); public: intoperator<=(Objecta)const { return(d>=a.d); } private: intID; floatd; }; intKnapsack(intp[],intw[],intc,intn) { //为Knap: : Backtrack初始化 intW=0; intP=0; inti=1; Object*Q=newObject[n]; for(i=1;i<=n;i++) { Q[i-1].ID=i; Q[i-1].d=1.0*p[i]/w[i]; P+=p[i]; W+=w[i]; } if(W<=c) returnP;//装入所有物品 //依物品单位重量排序 floatf; for(i=0;i for(intj=i;j { if(Q[i].d { f=Q[i].d; Q[i].d=Q[j].d; Q[j].d=f; } } KnapK; K.p=newint[n+1]; K.w=newint[n+1]; K.x=newint[n+1]; K.bestx=newint[n+1]; K.x[0]=0; K.bestx[0]=0; for(i=1;i<=n;i++) { K.p[i]=p[Q[i-1].ID]; K.w[i]=w[Q[i-1].ID]; } K.cp=0; K.cw=0; K.c=c; K.n=n; K.bestp=0; //回溯搜索 K.Backtrack (1); K.print(); delete[]Q; delete[]K.w; delete[]K.p; returnK.bestp; } voidmain() { int*p; int*w; intc=0; intn=0; inti=0; chark; cout<<"0-1背包问题——回溯法"< cout<<"byzbqplayer"< while(k) { cout<<"请输入背包容量(c): "< cin>>c; cout<<"请输入物品的个数(n): "< cin>>n; p=newint[n+1]; w=newint[n+1]; p[0]=0; w[0]=0; cout<<"请输入物品的价值(p): "< for(i=1;i<=n;i++) cin>>p[i]; cout<<"请输入物品的重量(w): "< for(i=1;i<=n;i++) cin>>w[i]; cout<<"最优解为(bestx): "< cout<<"最优值为(bestp): "< cout< cout<<"[s]重新开始"< cout<<"[q]退出"< cin>>k; } } e.分支限界 问题描述: 已知有N个物品和一个可以容纳M重量的背包,每种物品I的重量为WEIGHT,一个只能全放入或者不放入,求解如何放入物品,可以使背包里的物品的总效益最大。 对物品的选取与否构成一棵解树,左子树表示不装入,右表示装入,通过检索问题的解树得出最优解,并用结点上界杀死不符合要求的结点。 算法描述: 首先,要对输入数据进行预处理,将各物品依其单位重量价值从大到小进行排列。 在下面描述的优先队列分支限界法中,节点的优先级由已装袋的物品价值加上剩下的最大单位重量价值的物品装满剩余容量的价值和。 算法首先检查当前扩展结点的左儿子结点的可行性。 如果该左儿子结点是可行结点,则将它加入到子集树和活结点优先队列中。 当前扩展结点的右儿子结点一定是可行结点,仅当右儿子结点满足上
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 100930122 闫博文 算法 设计 分析 课程设计
![提示](https://static.bdocx.com/images/bang_tan.gif)