回溯法实验(0-1背包问题)
- 格式:doc
- 大小:122.00 KB
- 文档页数:8
回溯法解决0-1背包问题问题描述: 有n件物品和⼀个容量为c的背包。
第i件物品的价值是v[i],重量是w[i]。
求解将哪些物品装⼊背包可使价值总和最⼤。
所谓01背包,表⽰每⼀个物品只有⼀个,要么装⼊,要么不装⼊。
回溯法: 01背包属于找最优解问题,⽤回溯法需要构造解的⼦集树。
在搜索状态空间树时,只要左⼦节点是可⼀个可⾏结点,搜索就进⼊其左⼦树。
对于右⼦树时,先计算上界函数,以判断是否将其减去,剪枝啦啦!上界函数bound():当前价值cw+剩余容量可容纳的最⼤价值<=当前最优价值bestp。
为了更好地计算和运⽤上界函数剪枝,选择先将物品按照其单位重量价值从⼤到⼩排序,此后就按照顺序考虑各个物品。
#include <stdio.h>#include <conio.h>int n;//物品数量double c;//背包容量double v[100];//各个物品的价值double w[100];//各个物品的重量double cw = 0.0;//当前背包重量double cp = 0.0;//当前背包中物品价值double bestp = 0.0;//当前最优价值double perp[100];//单位物品价值排序后int order[100];//物品编号int put[100];//设置是否装⼊//按单位价值排序void knapsack(){int i,j;int temporder = 0;double temp = 0.0;for(i=1;i<=n;i++)perp[i]=v[i]/w[i];for(i=1;i<=n-1;i++){for(j=i+1;j<=n;j++)if(perp[i]<perp[j])//冒泡排序perp[],order[],sortv[],sortw[]{temp = perp[i];perp[i]=perp[i];perp[j]=temp;temporder=order[i];order[i]=order[j];order[j]=temporder;temp = v[i];v[i]=v[j];v[j]=temp;temp=w[i];w[i]=w[j];w[j]=temp;}}}//回溯函数void backtrack(int i){double bound(int i);if(i>n){bestp = cp;return;}if(cw+w[i]<=c){cw+=w[i];cp+=v[i];put[i]=1;backtrack(i+1);cw-=w[i];cp-=v[i];}if(bound(i+1)>bestp)//符合条件搜索右⼦数backtrack(i+1);}//计算上界函数double bound(int i){double leftw= c-cw;double b = cp;while(i<=n&&w[i]<=leftw){leftw-=w[i];b+=v[i];i++;}if(i<=n)b+=v[i]/w[i]*leftw;return b;}int main(){int i;printf("请输⼊物品的数量和容量:");scanf("%d %lf",&n,&c);printf("请输⼊物品的重量和价值:");for(i=1;i<=n;i++){printf("第%d个物品的重量:",i);scanf("%lf",&w[i]);printf("价值是:");scanf("%lf",&v[i]);order[i]=i;}knapsack();backtrack(1);printf("最有价值为:%lf\n",bestp);printf("需要装⼊的物品编号是:");for(i=1;i<=n;i++){if(put[i]==1)printf("%d ",order[i]);}return 0;}时间复杂度分析: 上界函数bound()需要O(n)时间,在最坏的情况下有O(2^n)个右⼦结点需要计算上界,回溯算法backtrack需要的计算时间为O(n2^n)。
实验报告. 基于回溯法的0-1背包等问题实验内容本实验要求基于算法设计与分析的一般过程(即待求解问题的描述、算法设计、算法描述、算法正确性证明、算法分析、算法实现与测试),通过回溯法的在实际问题求解实践中,加深理解其基本原理和思想以及求解步骤。
求解的问题为0-1背包。
作为挑战:可以考虑回溯法在如最大团、旅行商、图的m着色等问题中的应用。
实验目的◆理解回溯法的核心思想以及求解过程(确定解的形式及解空间组织,分析出搜索过程中的剪枝函数即约束函数与限界函数);◆掌握对几种解空间树(子集树、排列数、满m叉树)的回溯方法;◆从算法分析与设计的角度,对0-1背包等问题的基于回溯法求解有进一步的理解。
环境要求对于环境没有特别要求。
对于算法实现,可以自由选择C, C++或Java,甚至于其他程序设计语言如Python等。
实验步骤步骤1:理解问题,给出问题的描述。
步骤2:算法设计,包括策略与数据结构的选择。
步骤3:描述算法。
希望采用源代码以外的形式,如伪代码或流程图等;步骤4:算法的正确性证明。
需要这个环节,在理解的基础上对算法的正确性给予证明;步骤5:算法复杂性分析,包括时间复杂性和空间复杂性;步骤6:算法实现与测试。
附上代码或以附件的形式提交,同时贴上算法运行结果截图;步骤7:技术上、分析过程中等各种心得体会与备忘,需要言之有物。
说明:步骤1-6在“实验结果”一节中描述,步骤7在“实验总结”一节中描述。
实验结果步骤1:问题描述。
给定 n个物品,其中第 i 个物品的重量为w i ,价值为 v i 。
有一容积为 W 的背包,要求选择一些物品放入背包,使得物品总体积不超过W的前提下,物品的价值总和最大。
0-1背包问题的限制是,每种物品只有一个,它的状态只有放和不放两种。
0-1背包问题是特殊的整数规划问题,其可用数学语言表述为:对于给定 n >0,W >0,v,w (v i ,w i >0,1≤i ≤n),找出一个 n 元0-1向量x =( x 1, x 2,⋯, x n ) 其中x i ∈{0,1},1≤i ≤n ,使得∑v i n i=1x i 最大,并且∑w i n i=1x i ≤W ,即:max x (∑v i ni=1x i ) s.t.∑w i ni=1x i ≤W, x i ∈{0,1},1≤i ≤n步骤2:算法设计,即算法策略与数据结构的选择。
0-1背包问题——回溯法求解【Python】回溯法求解0-1背包问题:问题:背包⼤⼩ w,物品个数 n,每个物品的重量与价值分别对应 w[i] 与 v[i],求放⼊背包中物品的总价值最⼤。
回溯法核⼼:能进则进,进不了则换,换不了则退。
(按照条件深度优先搜索,搜到某⼀步时,发现不是最优或者达不到⽬标,则退⼀步重新选择)注:理论上,回溯法是在⼀棵树上进⾏全局搜索,但是并⾮每种情况都需要全局考虑,毕竟那样效率太低,且通过约束+限界可以减少好多不必要的搜索。
解决本问题思路:使⽤0/1序列表⽰物品的放⼊情况。
将搜索看做⼀棵⼆叉树,⼆叉树的第 i 层代表第 i 个物品,若剩余空间允许物品 i 放⼊背包,扩展左⼦树。
若不可放⼊背包,判断限界条件,若后续继续扩展有可能取得最优价值,则扩展右⼦树(即此 i 物品不放⼊,但是考虑后续的物品)。
在层数达到物品的个数时,停⽌继续扩展,开始回溯。
注:如何回溯呢?怎样得到的,怎样恢复。
放⼊背包中的重量取出,加在bagV上的价值减去。
约束条件:放⼊背包中物品的总质量⼩于等于背包容量限界条件:当前放⼊背包中物品的总价值(i及之前) + i 之后的物品总价值 < 已知的最优值这种情况下就没有必要再进⾏搜索数据结构:⽤⼀个变量记录当前放⼊背包的总价值 bagV(已扩展),⼀个变量记录后续物品的总价值 remainV(未扩展),当前已得到的⼀种最优值 bestV(全局情况),⼀个⽤0/1表⽰的数组bestArr[]记录哪些物品放⼊了背包。
核⼼结构:递归思路进⾏解决。
层层递归,递归到尽头,保留最优值,恢复递归中,层层回溯,即将原来加上去的重量与价值恢复。
# -*- coding:utf-8 -*-def Backtrack(t):global bestV, bagW, bagV,arr, bestArr, cntVif t > n: #某次深度优先搜索完成if bestV < bagV:for i in range(1, n+1):bestArr[i] = arr[i]bestV = bagVelse: #深度优先搜索未完成if bagW + listWV[t][0] <= w: #第t个物品可以放⼊到背包中,扩展左⼦树arr[t] = TruebagW += listWV[t][0]bagV += listWV[t][1]Backtrack(t+1)bagW -= listWV[t][0]bagV -= listWV[t][1]if cntV[t] + bagV > bestV: #有搜索下去的必要arr[t] = FalseBacktrack(t+1)if__name__ == '__main__':w = int(input()) #背包⼤⼩n = int(input()) #物品个数listWV = [[0,0]]listTemp = []sumW = 0sumV = 0for i in range(n):listTemp = list(map(int, input().split())) #借助临时list每次新增物品对应的list加⼊到listWV中sumW += listTemp[0]sumV += listTemp[1]listWV.append(listTemp) #依次输⼊每个物品的重量与价值bestV = 0bagW = 0bagV = 0remainV = sumVarr = [False for i in range(n+1)]bestArr = [False for i in range(n+1)]cntV = [0 for i in range(n+1)] #求得剩余物品的总价值,cnt[i]表⽰i+1~n的总价值 cntV[0] = sumVfor i in range(1, n+1):cntV[i] = cntV[i-1] - listWV[i][1]if sumW <= w:print(sumV)else:Backtrack(1)print(bestV)print(bestArr)print(cntV)检测:1052 65 34 52 43 617[False, True, False, True, False, True][24, 18, 15, 10, 6, 0]。
0—1背包问题一、实验目的学习掌握回溯思想。
二、实验内容用回溯求解0—1背包问题,并输出问题的最优解。
0—1背包问题描述如下:给定n种物品和一背包。
物品i的重量是Wi,其价值为Vi,背包的容量是c,问应如何选择装入背包中的物品,使得装入背包中物品的总价值最大。
三、实验条件Jdk1.5以上四、需求分析对于给定n种物品和一背包。
在容量最大值固定的情况下,要求装入的物品价值最大化。
五、回溯法的基本思想:确定了解空间的组织结构后,回溯法就从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。
这个开始结点就成为一个活结点,同时也成为当前的扩展结点。
在当前的扩展结点处,搜索向纵深方向移至一个新结点。
这个新结点就成为一个新的活结点,并成为当前扩展结点。
如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。
换句话说,这个结点不再是一个活结点。
此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。
回溯法即以这种工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已没有活结点时为止。
六、详细设计/** BackTrace.java** Created on 2007年6月2日, 下午6:09** To change this template, choose Tools | Template Manager* and open the template in the editor.*/package sunfa;import java.util.Date;public class BackTrace {/*** @param args*/public static void main(String[] args) {double w[]={2,2,6,5,4};double v[]={6,3,5,4,6};int n=5;double c=10;knapsack(v,w,c);System.out.println(bestp);}//比较两个元素大小的类private static class Element implements Comparable{int id;double d;private Element(int idd,double dd){id=idd;d=dd;}public int compareTo(Object x){double xd=((Element)x).d;if(d<xd)return -1;if(d==xd)return 0;return 1;}public boolean equals(Object x){return d==((Element)x).d;}}static double c; //背包容量static int n;//物品数static double[]w;//物品重量数组static double[]p; //物品价值数组static double cw;//当前重量static double cp;//当前价值static double bestp; //当前最优值static int [] x;//解static int [] sortX;//排好序之后的解static int [] bestX;//最有解static Date date = null; // @jve:decl-index=0:public static double knapsack(double[]pp,double[]ww,double cc){ c=cc;n=pp.length-1;cw=0.0;cp=0.0;bestp=0.0;Element[]q=new Element[n];//q为单位重量价值数组for(int i=1;i<=n;i++)q[i-1]=new Element(i,pp[i]/ww[i]);MergeSort.mergeSort(q);p=new double[n+1];w=new double[n+1];x=new int[n+1];sortX=new int[n+1];bestX=new int[n+1];for(int i=1;i<=n;i++){p[i]=pp[q[n-i].id];w[i]=ww[q[n-i].id];sortX[i]=q[n-i].id;}backtrack(1);//回溯搜索return bestp;}private static void backtrack(int i){if(i>=n){if(cp>bestp){bestp=cp;for(int j=1;j<=n;j++){bestX[j]=x[j];}}return;}//搜索子树if(cw+w[i]<=c){//进入左子树x[sortX[i]]=1;cw+=w[i];cp+=p[i];backtrack(i+1);cw-=w[i];cp-=p[i];}if(bound(i+1)>bestp)x[sortX[i]]=0;backtrack(i+1);//进入右子树}//计算上界private static double bound(int i){double cleft=c-cw;double bound=cp;//以物品重量价值递减顺序装入物品while(i<=n&&w[i]<=cleft){cleft-=w[i];bound+=p[i];i++;}//装满背包if(i<=n)bound+=p[i]/w[i]*cleft;return bound;}public static String getX(){String solution=String.valueOf(bestX[1]);for(int i=2;i<bestX.length;i++){solution+=",";solution+=String.valueOf(bestX[i]);}return solution;}public static double getBestValue(){return bestp;}}主程序运行结果:。
回溯法实验(0-1 背包问题)算法分析与设计实验报告第丄次附加实验巔I 軌鬻123^EC70?1O牧品价值分别为:12345678? 10妆品重量和忙值分别为;<2^2> <3,3> <4.4>O^SJ C9,9) <18,10>在实验中并没有生成多组数据,进行比较,也没有利用随机生 成函数,因为在这种有实际有关联的问题中,利用随机生成函数生 成的数据是十分的不合适的,在此我们只需要验证该程序是否正确 即可。
0-1背包问题和之前的最优装载其实质上一样的,都是利用 解空间树,通过深度优先搜索子集树,通过利用上界函数和一些剪 枝策略,从而得到最优解。
由于数据较小,所以时间上并不能反映 出什么东西。
当输入的数据有解时:测试结果实验分析当输入的数据无解时:当输入的数据稍微大点时:四回溯法X FUFO ORe\Debuq\zeno cnejext阀品上数为:M在这一章的回溯算法中,我们用的比较多的就是;利用子集树 来进行问题的探索,就例如上图是典型的一种子集树,在最优装 载、0-1背包都是利用了这种满二叉树的子集树进行求解,然后通 过深度优先的策略,利用约束函数和上界函数,将一些不符合条件 或者不包含最优解的分支减掉,从而提高程序的效率。
对于0-1背包问题我们基本上在每一个算法中都有这么一个实例,足以说明这 个问题是多么经典的一个问题啊,通过几个不同的算法求解这一问 题,我也总算对该问题有了一定的了解。
实验得分附录:完整代码(回溯法)〃0-1背包问题 回溯法求解#i nclude <iostream> using namespacestd;template <class Typew, class Typep> class Knap //Knap 类记录解空间树的结点信息{template <class Typew, class Typep>friend Typep Knapsack(Typep [],Typew [],Typew, int );private :Typep Bound( int i);//计算上界的函数实验心得 助教签名void Backtrack( int i); //回溯求最优解函数Typew c; //背包容量int n; //物品数Typew *w; //物品重量数组|Typep *p; //物品价值数组Typew cw; //当前重量Typep cp; //当前价值Typep bestp; //当前最后价值};template <class Typew, class Typep>Typep Knapsack(Typep p[],Typew w[],Typew c, int n); //声明背包问题求解函数template < class Type>in li ne void Swap (Type &a,Type & b); // 声明交换函数template <class Type>void BubbleSort(Type a[], int n); // 声明冒泡排序函数int main(){int n ; //物品数int c ; //背包容量cout«"物品个数为:";cin»n;cout«"背包容量为:";cin> >c;int *p = new int [n]; //物品价值下标从1开始int *w = new int [n]; //物品重量下标从1开始cout«"物品重量分别为:"<<e ndl;for (int i=1; i<=n; i++){cin> >w[i];}cout«"物品价值分别为:"<<e ndl;for (int i=1; i<=n; i++) //以二元组(重量,价值)的形式输出每物品的信息{cin> >p[i];}coutvv "物品重量和价值分别为:"<<e ndl;for (int i=1; i<=n; i++) //以二元组(重量,价值)的形式输出每个物品的信息{coutvv "(" <<w[i]<< "," <<p[i]<< ")";}coutvve ndl;coutvv "背包能装下的最大价值为:"<<Knapsack(p,w,c,n)<<endl; //输出结果system( "pause");return 0;}template vclass Typew, class Typep>void Knap<Typew,Typep>::Backtrack( int i){if (i>n) //到达叶子节点{bestp = cp; //更新最优值return ;}if (cw + w[i] <= c) // 进入左子树{cw += w[i];cp += p[i];Backtrack(i+1); // 回溯//回溯结束回到当前根结点cw -= w[i];cp -= p[i];}//进入右子树,条件是上界值比当前最优值大,否则就将右子树剪掉if (Bound(i+1)>bestp){Backtrack(i+1);}}template <class Typew, class Typep>Typep KnapvTypew, Typep>::Bound( int i) //计算上界{Typew cleft = c - cw; // 剩余容量Typep b = cp;//以物品单位重量价值递减序装入物品while (i <= n && w[i] <= cleft){cleft -= w[i];b += p[i];i++;}//如果背包剩余容量不足以装下一个物品if (i <= n){b += p[i]/w[i] * cleft; //则将物品的部分装入到背包中}return b;}class Object //定义对象类,作用相当于结构体{template vclass Typew, class Typep>friend Typep Knapsack(Typep[],Typew [],Typew, int ); public : int operator >= (Object a) const // 符号重载函数,重载>=符号{return (d>=a.d);}private :int ID; // 编号float d; //单位重量的价值};template <class Typew, class Typep>Typep Knapsack(Typep p[],Typew w[],Typew c, int n){// 为Knap::Backtrack 初始化Typew W = 0;Typep P = 0;Object *Q = newObject[n]; // 创建Object 类的对象数组| //初始化Object类的对象数组|for (int 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) //装入所有物品{return P;}//依物品单位重量价值降序排序BubbleSort(Q, n);Knap<Typew,Typep> K; // 创建Knap的对象KK.p = n ewTypep[ n+1];K.w = n ewTypew [n+1];for (int i=1; i<=n; i++){K.p[i] = p[Q[i-1]」D];K.w[i] = w[Q[i-1].ID];}//初始化KK.cp = 0;K.cw = 0;K.c = c;K.n = n;K.bestp = 0;//回溯搜索K.Backtrack(1);delete []Q;delete []K.w;delete []K.p;return K.bestp; // 返回最优解} template <class Type>void BubbleSort(Type a[], int n) {//记录一次遍历中是否有元素的交换bool excha nge;for (int i=0; i<n-1;i++){exchange = false ;for (int j=i+1; j<=n-1; j++){if (a[j]>=a[j-1]){Swap(a[j],a[j-1]); exchange = true ;}}//如果这次遍历没有元素的交换,那么排序结束if (exchange==false ){break ;}}} template < class Type>inline void Swap (Type &a,Type &b) // 交换函数{Type temp = a; a = b;b = temp;}。
回溯法(⼆)——0-1背包问题 问题1、给定背包容量w,物品数量n,以及每个物品的重量wi,求背包最多能装多少多重的物品。
问题2、给定背包容量w,物品数量n,以及每个物品的重量wi、价值vi,求背包最多能装多少价值的物品。
这是⼀个基本的0-1背包问题,每个物品有两种状态(0:不装、1:装),总共有n步,所以可以⽤回溯法来解决,复杂度是O(2^n)。
C++版代码如下#include <iostream>#include <math.h>#include <cstring>using namespace std;#define MAXSIZE 256int maxWeight = -9999;// 回溯法解决0-1背包问题(其实可以暴⼒(n层for循环),回溯法也是n层for循环,即复杂度是O(2^n))void basePackage(int stuff[], int curState, int state, int curWeight, int weight){// 如果装满了(其实应该是接近装满了)或者已经“⾛完”所有物品if(curState == state || curWeight == weight){if(curWeight > maxWeight)maxWeight = curWeight;return ;}// 不装basePackage(stuff, curState + 1, state, curWeight + 0, weight);// 装if(curWeight + stuff[curState] <= weight)basePackage(stuff, curState + 1, state, curWeight + stuff[curState], weight);}// 回溯法解决0-1背包问题(其实可以暴⼒(n层for循环),回溯法也是n层for循环,即复杂度是O(2^n))// 背包升级问题回溯法解决(加⼊背包的价值)void secPackage(int weight[], int value[], int curV, int curW, int weightLimit, int curS, int n){// 如果背包总重量等于背包限制if(curW == weightLimit || curS == n){if(curV > maxWeight)maxWeight = curV;return ;}// 不装secPackage(weight, value, curV, curW, weightLimit, curS + 1, n);if(curW + weight[curS] <= weightLimit)// 装secPackage(weight, value, curV + value[curS], curW + weight[curS], weightLimit, curS + 1, n);}int main(int argc, char* argv[]){// 总重量,物品个数int w, n;cin>>w>>n;int a[MAXSIZE + 1];for(int i = 0; i < n; i++)cin>>a[i];basePackage(a, 0, n, 0, w);cout<<maxWeight<<endl;return 0;}。
0-1背包问题(回溯法)实验报告姓名:学号:指导老师:一.算法设计名称:0-1背包问题(回溯法)二.实验内容问题描述:给定n 种物品和一背包。
物品i 的重量是w i ,其价值为v i ,背包的容量为C 。
问应如何选择装入背包的物品,使得装入背包中物品的总价值最大?在选择装入背包的物品时,对每种物品i 只有两种选择,即装入背包或不装入背包。
不能将物品装入背包多次,也不能只装入部分的物品。
三.实验目的1.运用回溯思想,设计解决上述问题的算法,找出最大背包价值的装法。
2.掌握回溯法的应用四.算法设计:问题求解思路1.由0-1背包问题的最优子结构性质,建立计算m[i][j]的递归式如下:i i i w j w j j i m i v w j i m j i m j i m <≤≥⎩⎨⎧-+---=0],1[]}[],1[],,1[max{),(2.查找装入背包物品的回溯函数:从0-1二叉树的根开始搜索:若是叶子节点,则判断此时的价值是否比当前最优的价值大,否则将之替换,并获得最优解向量且返回;若不是叶子节点,则向左右子树搜索,先改变当前的数据状态,递归的调用自己,然后恢复数据状态表示回溯。
3.边界函数bound主要是当还未搜索到叶子节点时,提前判断其子树是否存可能存在更优的解空间,否则进行回溯,即裁剪掉子树的解空间。
关键数据结构及函数模块:(Backtrack.h )#ifndef __BACKTRACK_H__#define __BACKTRACK_H__class BP_01_P{public:∑=ni i i x v 1max ⎪⎩⎪⎨⎧≤≤∈≤∑=n i x C x w i n i i i 1},1,0{1BP_01_P(int w,int n):m_Sum_weitht(0),m_Number(0) {m_Sum_weitht=w;m_Number=n;bestHav=0;bestVal=0;curVal=0;curHav=0;m_hav=new int[n];m_val=new int[n];temop=new int[n];option=new int[n];}~BP_01_P(){delete []m_hav;delete []m_val;delete []temop;delete []option;}void traceBack(int n);int bound(int n);void printBestSoulation();int *m_hav;//每个物品的重量int *m_val;//每个物品的价值int *temop;//01临时解int *option;//01最终解int bestHav;//最优价值时的最大重量int bestVal;//最优的价值int curVal;//当前的价值int curHav;//当前的重量private:int m_Sum_weitht;//背包的总容量int m_Number;//物品的种类};#endif __BACKTRACK_H__五:主要的算法代码实现:(Backtrack.cpp)边界函数:bound( )int BP_01_P::bound(int n){int hav_left=m_Sum_weitht-curHav;int bo=curVal;while(n<m_Number && m_hav[n]<=hav_left){hav_left-=m_hav[n];bo+=m_val[n];n++;}if(n<m_Number){bo+=m_val[n]*hav_left/m_hav[n];//bo+=hav_left;}return bo;}回溯递归函数:traceBack( )void BP_01_P::traceBack(int n){if(n>=m_Number){if(curVal>=bestVal){bestVal=curVal;for(int i=0;i<n;i++){option[i]=temop[i];}return ;}}if(curHav+m_hav[n]<=m_Sum_weitht)//向左子树搜索 {curHav=curHav+m_hav[n];curVal=curVal+m_val[n];temop[n]=1;//标记要选择这个物品traceBack(n+1);curHav=curHav-m_hav[n];curVal=curVal-m_val[n];}if(bound(n+1)>bestVal)//向右子树搜索{temop[n]=0;//标记要丢弃这个物品traceBack(n+1);}}主控函数:(main.cpp)#include <iostream>#include "Backtrack.h"using namespace std;int main(){int number,weigth;cout<<"包的总容量:";cin>>weigth;cout<<"物品的种类:";cin>>number;BP_01_P *ptr=new BP_01_P(weigth,number);cout<<"各种物品的重量:"<<endl;for(int i=0;i<number;i++)cin>>ptr->m_hav[i];cout<<"各种物品的价值:"<<endl;for(i=0;i<number;i++)cin>>ptr->m_val[i];ptr->traceBack(0);ptr->printBestSoulation();cout<<"总重量:"<<ptr->bestHav<<"\t总价值:"<<ptr->bestVal<<endl;return 0;}六:算法分析采用回溯法解决0-1背包问题,明显比动态规划法更优良。
0-1背包问题四种不同算法的实现兰州交通大学数理与软件工程学院题目0-1背包问题算法实现院系数理院专业班级信计09学生姓名雷雪艳学号200905130指导教师李秦二O一二年 六 月 五 日一、问题描述:1、0—1背包问题:给定n 种物品和一个背包,背包最大容量为M ,物品i 的重量是w i ,其价值是平P i ,问应当如何选择装入背包的物品,似的装入背包的物品的总价值最大?背包问题的数学描述如下:2、要求找到一个n 元向量(x1,x2…xn),在满足约束条件:⎪⎩⎪⎨⎧≤≤≤∑10i i i x M w x 情况下,使得目标函数px ii ∑max ,其中,1≤i ≤n ;M>0;wi>0;pi>0。
满足约束条件的任何向量都是一个可行解,而使得目标函数达到最大的那个可行解则为最优解[1]。
给定n 种物品和1个背包。
物品i 的重量是wi ,其价值为pi ,背包的容量为M 。
问应如何装入背包中的物品,使得装人背包中物品的总价值最大?在选择装人背包的物品时,对每种物品i 只有两种选择,即装入背包、不装入背包。
不能将物品i 装人背包多次,也不能只装入部分的物品i 。
该问题称为0-1背包问题。
0-1背包问题的符号化表示是,给定M>0, w i >0, pi >0,1≤i ≤n ,要求找到一个n 元0-1向量向量(x1,x2…xn), X i =0 或1 , 1≤i ≤n, 使得Mwx ii≤∑ ,而且px ii∑达到最大[2]。
二、解决方案:方案一:贪心算法1、贪心算法的基本原理与分析贪心算法总是作出在当前看来是最好的选择,即贪心算法并不从整体最优解上加以考虑,它所作出的选择只是在某种意义上的局部最优解。
贪心算法不是对所有问题都能得到整体最优解,但对范围相当广的许多问题它能产生整体最优解。
在一些情况下,即使贪心算法不能得到整体最优解,但其最终结果却是最优解的很好近似解。
1、问题描述一、0-1背包问题给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,如何选择才能使得物品的总价格最高?2、算法设计思想回溯法3、算法过程描述1.解空间的定义物品有装入和不装入两种状态,设装入为1,不装入为0,例如n=3时,解空间为{(0,0,0),(0,0,1)……(1,1,1)}2.解空间结构的组织使用一棵子集树来表示解空间每一条边的权值代表装或不装(1或0)采用深度优先搜索遍历整个树3.约束条件1)重量不超过背包的容量2)已装入物品的价值Cp+剩余空间装入物品的最大价值r<=最优解bestP注*:最大价值r的求法:假设物品能够分割,运用贪心算法的思想,求出背包理论最大价值r4、算法实现及运行结果#include<stdio.h>int n,c,bestp;//物品的个数,背包的容量,最大价值int p[10000],w[10000],x[10000],bestx[10000];//物品的价值,物品的重量,x[i]暂存物品的选中情况,物品的选中情况void Backtrack(int i,int cp,int cw){ //cw当前包内物品重量,cp当前包内物品价值int j;if(i>n)//回溯结束{if(cp>bestp){bestp=cp;for(i=0;i<=n;i++) bestx[i]=x[i];}}elsefor(j=0;j<=1;j++){x[i]=j;if(cw+x[i]*w[i]<=c){cw+=w[i]*x[i];cp+=p[i]*x[i];Backtrack(i+1,cp,cw);cw-=w[i]*x[i];cp-=p[i]*x[i];}}}int main(){int i;bestp=0;printf("请输入背包最大容量:\n");scanf("%d",&c);printf("请输入物品个数:\n");scanf("%d",&n);printf("请依次输入物品的重量:\n");for(i=1;i<=n;i++)scanf("%d",&w[i]);printf("请依次输入物品的价值:\n");for(i=1;i<=n;i++)scanf("%d",&p[i]);Backtrack(1,0,0);printf("最大价值为:\n");printf("%d\n",bestp);printf("被选中的物品依次是(0表示未选中,1表示选中)\n");for(i=1;i<=n;i++)printf("%d ",bestx[i]);printf("\n");return 0;}5、算法复杂度分析及算法改进时间复杂度为O(2^n)*O(n)(求上界)=O(n2^n)。
实验4回溯法解0-1背包问题一、实验要求1.要求用回溯法求解0-1背包问题;2.要求交互输入背包容量,物品重量数组,物品价值数组;3.要求显示结果。
二、实验仪器和软件平台仪器:带usb接口微机软件平台:WIN-XP + VC++6.0三、实验源码#include "stdafx.h"#include<iostream>#include<cstdio>#include<conio.h>#include<iomanip>using namespace std;template<class ty>class Knap{public:friend void Init();friend void Knapsack();friend void Backtrack(int i);friend float Bound(int i);bool operator<(Knap<ty> a)const{if(fl<a.fl) return true;else return false;}private:ty w; //重量ty v; //价值float fl; //单位重量的价值v/wint kk; //记录第几个物品int flag; //记录是否放入包中};template<class ty>void Sort(Knap<ty> *li,int n){int i,j,k; Knap<ty> minl;for(i=1;i<n;i++){minl=li[0]; k=0;for(j=1;j<=n-i;j++){if(minl<li[j]){minl=li[j]; swap(li[j],li[k]); k=j;}}}}namespace jie //命名空间{int c=0,n=0;int *x=NULL;Knap<int> *bag=NULL;int cp=0,cw=0;int bestp=0;}using namespace jie;void Init(){int i=0;cout<<endl;cout<<"请输入物品数量n = ";cin>>n; cout<<endl;cout<<"请输入背包容量C = ";cin>>c; cout<<endl;bag=new Knap<int> [n];x=new int[n];cout<<"请依次输入"<<n<<"个物品的重量W:"<<endl;for(i=0;i<n;i++)cin>>bag[i].w;cout<<endl;cout<<"请依次输入"<<n<<"个物品的价值P:"<<endl;for(i=0;i<n;i++)cin>>bag[i].v;for(i=0;i<n;i++){bag[i].flag=0; bag[i].kk=i;bag[i].fl=1.0*bag[i].v/bag[i].w;}}void Backtrack(int i){if(i>=n) //到达叶节点{bestp=cp; //更新最优价值return;}if(cw+bag[i].w<=c) //进入左子树{bag[i].flag=1; cw+=bag[i].w;cp+=bag[i].v; Backtrack(i+1);cw-=bag[i].w; cp-=bag[i].v;}if(Bound(i+1)>bestp)//进入右子树{bag[i].flag=0; Backtrack(i+1);}}//计算当前节点处的上界float Bound(int i){int cleft = c-cw; //剩余容量float b = cp;while (i<n&&bag[i].w<=cleft){//以物品单位重量价值递减序装入cleft-=bag[i].w ;b+=bag[i].v;i++;}//装满背包if (i<n) b+=1.0*bag[i].v/bag[i].w * cleft;return b;}void Knapsack() //计算最优解和变量值{int L(0); //用L累计价值,初始价值设置为0for(int k=0;k<n;k++){x[bag[k].kk]=bag[k].flag; //x=0表示未放入背包,x=1表示放入背包L+=bag[k].flag*bag[k].v; //价值累加}cout<<endl;cout<<"当前最优价值为:"<<L<<endl;cout<<"变量值x = ";for(int i=1;i<=n;i++){cout<<x[i-1];}delete []bag; bag=NULL;delete []x; x=NULL;cout<<endl; getch();}int main(){cout<<endl;cout<<"|**********回溯法解0-1背包问题**********|"<<endl;Init();Backtrack(0);Knapsack();return 0;}四、运行结果五、实验小结通过该实验,我充分了解了回溯法与分支界限法的区别。
算法分析与设计实验报告第五次附加实验cp += p[i];Backtrack(i+1); //回溯//回溯结束回到当前根结点cw -= w[i];cp -= p[i];}//进入右子树,条件是上界值比当前最优值大,否则就将右子树剪掉if(Bound(i+1)>bestp){Backtrack(i+1);}}测试结果当输入的数据有解时:当输入的数据无解时:当输入的数据稍微大点时:附录:完整代码(回溯法)//0-1背包问题 回溯法求解 #include <iostream> using namespace std;template <class Typew,class Typep>class Knap //Knap 类记录解空间树的结点信息 {template <class Typew,class Typep>friend Typep Knapsack(Typep [],Typew [],Typew,int ); private :Typep Bound(int i); //计算上界的函数void Backtrack(int i); //回溯求最优解函数实验分析在实验中并没有生成多组数据,进行比较,也没有利用随机生成函数,因为在这种有实际有关联的问题中,利用随机生成函数生成的数据是十分的不合适的,在此我们只需要验证该程序是否正确即可。
0-1背包问题和之前的最优装载其实质上一样的,都是利用解空间树,通过深度优先搜索子集树,通过利用上界函数和一些剪枝策略,从而得到最优解。
由于数据较小,所以时间上并不能反映出什么东西。
实验心得在这一章的回溯算法中,我们用的比较多的就是;利用子集树来进行问题的探索,就例如上图是典型的一种子集树,在最优装载、0-1背包都是利用了这种满二叉树的子集树进行求解,然后通过深度优先的策略,利用约束函数和上界函数,将一些不符合条件或者不包含最优解的分支减掉,从而提高程序的效率。
一、实验目的与要求掌握回溯法、分支限界法的原理,并能够按其原理编程实现解决0-1背包问题,以加深对回溯法、分支限界法的理解。
1.要求分别用回溯法和分支限界法求解0-1背包问题;2.要求交互输入背包容量,物品重量数组,物品价值数组;3.要求显示结果。
二、实验方案在选择装入背包的物品时,对每种物品i只有2种选择,即装入背包或不装入背包。
不能将物品i装入背包多次,也不能只装入部分的物品i。
三、实验结果和数据处理1.用回溯法解决0-1背包问题:代码:import .*;public class Knapsack{private double[] p,w;d];w[i+1]=ww[q[i].id];}cw=;cp=;bestX = new int[n+1];heap = new MaxHeap(n);double bestp = MaxKnapsack();for(int j=0;j<n;j++)xx[q[j].id]=bestX[j+1];return bestp;}public static void main(String [] args){double w[]=new double[5];w[1]=3;w[2]=5;w[3]=2;w[4]=1;double p[]=new double[5];p[1]=9;p[2]=10;p[3]=7;p[4]=4;double c=7;int x[] = new int[5];double m = Knapsack(p,w,c,x);"优先队列式分支限界法:");"物品个数:n=4");"背包容量:c=7");"物品重量数组:w= {3,5,2,1}"); "物品价值数组:p= {9,10,7,4}"); "最优值:="+m);"选中的物品是:");for(int i=1;i<=4;i++)" ");}}pperProfit;if(upperProfit < xup)return -1;if(upperProfit == xup)return 0;elsereturn 1;}}class Element implements Comparable{int id;double d;public Element(int idd,double dd) {id=idd;d=dd;}public int compareTo(Object x){double xd=((Element)x).d;if(d<xd)return -1;if(d==xd)return 0;return 1;}public boolean equals(Object x){return d==((Element)x).d;}}class MaxHeap{static HeapNode [] nodes;static int nextPlace;static int maxNumber;public MaxHeap(int n){maxNumber = (int)((double)2,(double)n);nextPlace = 1;pperProfit<nodes[j+1].upperProfit) ++j;if(!<nodes[j].upperProfit))break;nodes[s] = nodes[j];s = j;}nodes[s] = rc;}private static void heapSort(HeapNode [] nodes){for(int i=(nextPlace-1)/2;i>0;--i){heapAdjust(nodes,i,nextPlace-1);}}}运行结果:3.用队列式分支限界法解决0-1背包问题:代码:#include<>#include<>#define MAXNUM 100struct node{int step;double price;double weight;double max, min;unsigned long po;};typedef struct node DataType;struct SeqQueue{ /* 顺序队列类型定义 */int f, r;DataType q[MAXNUM];};typedef struct SeqQueue *PSeqQueue;PSeqQueue createEmptyQueue_seq( void ){PSeqQueue paqu;paqu = (PSeqQueue)malloc(sizeof(struct SeqQueue));if (paqu == NULL)printf("Out of space!! \n");elsepaqu->f = paqu->r = 0;return paqu;}int isEmptyQueue_seq( PSeqQueue paqu ){return paqu->f == paqu->r;}/* 在队列中插入一元素x */void enQueue_seq( PSeqQueue paqu, DataType x ){if((paqu->r + 1) % MAXNUM == paqu->f)printf( "Full queue.\n" );else{paqu->q[paqu->r] = x;paqu->r = (paqu->r + 1) % MAXNUM;}}/* 删除队列头元素 */void deQueue_seq( PSeqQueue paqu ){if( paqu->f == paqu->r )printf( "Empty Queue.\n" );elsepaqu->f = (paqu->f + 1) % MAXNUM;}/* 对非空队列,求队列头部元素 */DataType frontQueue_seq( PSeqQueue paqu ){return (paqu->q[paqu->f]);}/* 物品按性价比从新排序*/void sort(int n, double p[], double w[]){int i, j;for (i = 0; i < n-1; i++)for (j = i; j < n-1; j++){double a = p[j]/w[j];double b = p[j+1]/w[j+1];if (a < b){double temp = p[j];p[j] = p[j+1];p[j+1] = temp;temp = w[j];w[j] = w[j+1];w[j+1] = temp;}}}/* 求最大可能值*/double up(int k, double m, int n, double p[], double w[]) {int i = k;double s = 0;while (i < n && w[i] < m){m -= w[i];s += p[i];i++;}if (i < n && m > 0){s += p[i] * m / w[i];i++;}return s;}/* 求最小可能值*/double down(int k, double m, int n, double p[], double w[]) {int i = k;double s = 0;while (i < n && w[i] <= m){m -= w[i];s += p[i];i++;}return s;}/* 用队列实现分支定界算法*/double solve(double m, int n, double p[], double w[], unsigned long* po) {double min;PSeqQueue q = createEmptyQueue_seq();DataType x = {0,0,0,0,0,0};sort(n, p, w);= up(0, m, n, p, w);= min = down(0, m, n, p, w);if (min == 0) return -1;enQueue_seq(q, x);while (!isEmptyQueue_seq(q)){int step;DataType y;x = frontQueue_seq(q);deQueue_seq(q);if < min) continue;step = + 1;if (step == n+1) continue;= + up(step, m - , n, p, w);if >= min){= + down(step, , n, p, w);= ;= ;= step;= << 1;if >= min){min = ;if (step == n) *po = ;}enQueue_seq(q, y);}if +w[step-1]<= m){= + p[step-1]+up(step, [step-1], n, p, w);if >= min) {= + p[step-1] +down(step, [step-1], n, p, w);= + p[step-1];= + w[step-1];= step;= << 1) + 1;if >= min){min = ;if (step == n) *po = ;}enQueue_seq(q, y);}}}return min;}#define n 4double m = 7;double p[n] = {9, 10, 7, 4};double w[n] = {3, 5, 1, 2};int main(){int i;double d;unsigned long po;d = solve(m, n, p, w, &po);if (d == -1)printf("No solution!\n");else{for (i = 0; i < n; i++)printf("x%d 为 %d\n", i + 1, ((po & (1<<(n-i-1))) != 0)); printf("最优值是:%f\n", d);}getchar();return 0;}运行结果:。
回溯法是一种解决0-1背包问题的有效方法。
以下是使用回溯法解决0-1背包问题的具体步骤和例题:1.定义问题:假设有N件物品,每件物品有一定的重量Wi和价值Vi,背包能够承受的最大重量为W。
目标是选择一些物品放入背包,使得背包中物品的总价值最大,同时不超过背包的最大承重。
2.使用回溯法求解:回溯法的核心是深度优先搜索,通过尝试每一种可能性来找到最优解。
o初始化:将所有物品按照价值从大到小排序。
o递归函数:▪如果当前选择的物品重量超过了背包的承重,则返回(因为无法放入背包)。
▪如果当前选择的物品价值大于之前所有选择物品的总价值,则更新当前最大价值。
▪标记当前选择的物品为已选(例如,使用一个布尔数组表示)。
▪递归地尝试下一个物品。
o回溯:如果递归到最后一个物品,并且没有超过背包的承重,则将最后一个物品加入背包,并更新最大价值。
然后回溯到上一个物品,尝试不放入背包中。
3.求解步骤:o初始状态:未选择任何物品,总价值为0。
o递归函数:对于每个物品i,如果未选择(即第i个物品的布尔数组标记为false),则执行递归函数。
如果选择了第i个物品,并且总价值大于当前最大价值,则更新最大价值。
标记第i个物品为已选。
然后递归地尝试下一个物品。
o回溯:如果尝试了所有物品都没有超过背包的承重,并且总价值大于当前最大价值,则将最后一个选择的物品加入背包,并更新最大价值。
然后回溯到上一个物品,尝试不放入背包中。
4.例题:假设有3件物品,重量分别为20、15、10,价值分别为20、30、25,背包的承重为25。
根据回溯法求解的步骤如下:o首先尝试第一个物品(重量20,价值20)。
由于20>25,所以无法放入背包。
o接下来尝试第二个物品(重量15,价值30)。
由于15+20=35>25,所以也无法放入背包。
o然后尝试第三个物品(重量10,价值25)。
由于10+20=30<25,所以可以放入背包中。
此时的最大价值为25+25=50。
学号:日期:《算法设计与分析》实验报告姓名:得分:____________、实验内容:用回溯法求解0/1背包问题注:给定n种物品和一个容量为C的背包,物品i的重量是W i,其价值为V i,背包问题是如何使选择装入背包内的物品,使得装入背包中的物品的总价值最大。
其中,每种物品只有全部装入背包或不装入背包两种选择。
、所用算法的基本思想及复杂度分析:1. 回溯法求解背包问题:1)基本思想:回溯法:为了避免生成那些不可能产生最佳解的问题状态,要不断地利用限界函数(bounding function) 来处死那些实际上不可能产生所需解的活结点,以减少问题的计算量。
这种具有限界函数的深度优先生成法称为回溯法。
对于有n种可选物品的0/1背包问题,其解空间由长度为n的0-1 向量组成,可用子集数表示。
在搜索解空间树时,只要其左儿子结点是一个可行结点,搜索就进入左子树。
当右子树中有可能包含最优解时就进入右子树搜索。
2)复杂度分析:回溯法求解0/1背包问题的时间复杂度为:T(n) 0(2n)。
空间复杂度:有n个物品,即最多递归n层,存储物品信息就是一个一维数组,即回溯法求解0/1背包问题的空间复杂度为0(n) o2. 以动态规划法验证:1)基本思想:令V(i,j)表示在前i(1 i n)个物品中能够装入容量为j(1 j C) 的背包中的物品的最大值,则可以得到如下动态函数:V(i,0) V(0,j) 0V(i,j)V(i 1,j)(j W i)maxV(i 1, j),V(i 1, j wj y (j wj按照下述方法来划分阶段:第一阶段,只装入前1 个物品,确定在各种情况下的背包能够得到的最大价值;第二阶段,只装入前2 个物品,确定在各种情况下的背包能够得到的最大价值;以此类推,直到第n 个阶段。
最后,V n,C)便是在容量为C的背包中装入n个物品时取得的最大价值。
2)复杂度分析:动态规划法求解0/1 背包问题的时间复杂度为:T(n) O(n C) 。
给定n 个物品和一个背包。
物品i 的重量为w i ,价值为v i ,背包容量为c 。
问如何选择装入背包中的物品,使得装入背包的物品的价值最大?在装入背包时,每种物品i 只有两种选择,装入或者不装入,既不能装入多次,也不能只装入一部分。
因此,此问题称为0-1背包问题。
如果在装入背包时,物品可以切割,即可以只装入一部分,这种情况下的问题称为背包问题。
0-1类似于旅行售货员问题,用一个数组P[n]存放目前取得的最优解,用变量V表示其价值;再用一个数组N[n]来产生新的解,用变量NV表示其价值,用变量W表示其重量。
如果新解更优,则替代原来的最优解,否则就丢弃新解。
然后回溯寻找下一个新解。
为此,我们先回顾一下回溯法的一般递归算法:0-1 Try(s){ 做挑选候选者的准备; while (未成功且还有候选者){ 挑选下一个候选者next ; if (next 可接受){ 记录next ; if (满足成功条件){成功并输出结果} else Try(s+1); if (不成功)删去next 的记录;}}return 成功与否}考察的第s 个物品只需考两种情况,选或不选,即for (i=0; i<2; i++)下个候选就是i 。
物体s 可放入背包中;将s 放入背包中并修改权重与容量;s = = n 这里无论成功与否都要继续考察“记录”的逆操作0-1Try(s){for (i =0;i <2;i++)if (Accept(i)){Record(s,i);if (s ==n){if (better )TakeNewChoice();}else Try(s+1);Move-off(s,i);}return }(W+w[s]*i <= C) {(NV > V)0-1Record(s,i){N[s]=i;W=W+w[s]*i;NV=NV+v[s]*i;}Move-off(s,i){N[s]=0;W=W–w[s]*i;NV=NV–v[s]*i;}TakeNewChoice(){for(i=1;i<=n;i++){P[i]=N[i];V=NV;}}0-1Bag(n,C,w[n],v[n]){for(i=1;i<=n;i++){N[i]=0;P[i]=0;T[i]=1;} NV=0,W=0;V=0;Try(1);Output(P);}0-1先看看回溯法的一般的迭代形式:Backtrack(Tree T) {unfinish = true; L.Push(T.root); while (unfinish || L≠Φ) {a = L.Pop( ); if (a is the last mark ) backastep( );else if (a is good ) {Record(a);if (a is goal ) {unfinish = false; output( );}else if (a has sons ) L.Push-Sons(a)else Move-off(a);}}}要比较所有组合,无需此句。
实验目的是使学生消化理论知识,加深对讲授内容的理解,尤其是一些算法的实现及其应用,培养学生独立编程和调试程序的能力,使学生对算法的分析与设计有更深刻的认识。
上机实验一般应包括以下几个步骤:(1)、准备好上机所需的程序。
手编程序应书写整齐,并经人工检查无误后才能上机。
(2)、上机输入和调试自己所编的程序。
一人一组,独立上机调试,上机时出现的问题,最好独立解决。
(3)、上机结束后,整理出实验报告。
实验报告应包括:题目、程序清单、运行结果、对运行情况所作的分析。
实验八 回溯算法——0-1背包问题一、实验目的与要求1. 熟悉0-1背包问题的回溯算法。
2. 掌握回溯算法。
二、实验内容用回溯算法求解下列“0-1背包”问题:给定n 种物品和一个背包。
物品i 的重量是w i ,其价值为v i ,背包的容量为C 。
问应如何选择装入背包的物品,使得装入背包中物品的总价值最大?三、实验步骤1. 理解算法思想和问题要求;2. 编程实现题目要求;3. 上机输入和调试自己所编的程序;4. 验证分析实验结果;5. 整理出实验报告。
实验提示:(1)回溯算法求解0-1背包问题分析回溯法通过系统地搜索一个问题的解空间来得到问题的解。
为了实现回溯,首先需要针对所给问题,定义其解空间。
这个解空间必须至少包含问题的一个解(可能是最优的)。
然后组织解空间。
确定易于搜索的解空间结构。
典型的组织方法是图或树。
一旦定义了解空间的组织方法,即可按照深度优先策略从开始结点出发搜索解空间。
并在搜索过程中利用约束函数在扩展结点处剪去不满足约束的子树,用目标函数剪去得不到最优解的子树,避免无效搜索。
用回溯法解题的步骤:1)针对所给问题定义问题的解空间;2)确定易于搜索的解空间结构;3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效的搜索。
0-1背包问题的数学描述为:n 个物品,物品i 的重量是w i 、其价值为v i ,其中0≤i ≤n-1,背包的容量为C 。
0-1背包动态规划解决问题一、问题描述:有n个物品,它们有各自的重量和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?二、总体思路:根据动态规划解题步骤(问题抽象化、建立模型、寻找约束条件、判断是否满足最优性原理、找大问题与小问题的递推关系式、填表、寻找解组成)找出01背包问题的最优解以及解组成,然后编写代码实现.原理:动态规划与分治法类似,都是把大问题拆分成小问题,通过寻找大问题与小问题的递推关系,解决一个个小问题,最终达到解决原问题的效果。
但不同的是,分治法在子问题和子子问题等上被重复计算了很多次,而动态规划则具有记忆性,通过填写表把所有已经解决的子问题答案纪录下来,在新问题里需要用到的子问题可以直接提取,避免了重复计算,从而节约了时间,所以在问题满足最优性原理之后,用动态规划解决问题的核心就在于填表,表填写完毕,最优解也就找到。
过程:a)把背包问题抽象化(X1,X2,…,Xn,其中 Xi 取0或1,表示第i 个物品选或不选),V i表示第i 个物品的价值,W i表示第i 个物品的体积(重量);b) 建立模型,即求max(V1X1+V2X2+…+VnXn);c)约束条件,W1X1+W2X2+…+WnXn<capacity;d)定义V(i,j):当前背包容量j,前i 个物品最佳组合对应的价值;e)最优性原理是动态规划的基础,最优性原理是指“多阶段决策过程的最优决策序列具有这样的性质:不论初始状态和初始决策如何,对于前面决策所造成的某一状态而言,其后各阶段的决策序列必须构成最优策略”。
判断该问题是否满足最优性原理,采用反证法证明:假设(X1,X2,…,Xn)是01背包问题的最优解,则有(X2,X3,…,Xn)是其子问题的最优解,假设(Y2,Y3,…,Yn)是上述问题的子问题最优解,则理应有(V2Y2+V3Y3+…+V n Yn)+V1X1 〉(V2X2+V3X3+…+VnXn)+V1X1;而(V2X2+V3X3+…+VnXn)+V1X1=(V1X1+V2X2+…+VnXn),则有(V2Y2+V3Y3+…+VnYn)+V1X1 〉(V1X1+V2X2+…+VnXn);该式子说明(X1,Y2,Y3,…,Yn)才是该01背包问题的最优解,这与最开始的假设(X1,X2,…,Xn)是01背包问题的最优解相矛盾,故01背包问题满足最优性原理;f)寻找递推关系式,面对当前商品有两种可能性:第一,包的容量比该商品体积小,装不下,此时的价值与前i—1个的价值是一样的,即V(i,j)=V(i—1,j);第二,还有足够的容量可以装该商品,但装了也不一定达到当前最优价值,所以在装与不装之间选择最优的一个,即V(i,j)=max{V(i-1,j),V(i—1,j—w(i))+v (i) }其中V(i—1,j)表示不装,V(i-1,j-w(i))+v(i)表示装了第i个商品,背包容量减少w(i)但价值增加了v(i);由此可以得出递推关系式:1)j<w(i) V(i,j)=V(i—1,j)2)j〉=w(i) V(i,j)=max{ V(i—1,j),V(i-1,j-w(i))+v(i)}number=4,capacity=7四、构造最优解:最优解的构造可根据C列的数据来构造最优解,构造时从第一个物品开始。
回溯法解决01背包问题算法回溯法是一种常见的解决0-1背包问题的算法。
以下是使用Python编写的基于回溯法的0-1背包问题的解决方案:```pythondef knapsack(weights, values, capacity):n = len(weights)dp = [[0 for _ in range(capacity + 1)] for _ in range(n + 1)]for i in range(1, n + 1):for w in range(1, capacity + 1):if weights[i - 1] <= w:dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - weights[i - 1]] + values[i - 1])else:dp[i][w] = dp[i - 1][w]return dp[n][capacity]def backtrack(weights, values, capacity, i, w):if i == 0 or w == 0:returnif weights[i - 1] <= w:backtrack(weights, values, capacity, i - 1, w - weights[i - 1])print(f"Pick {values[i - 1]} with weight {weights[i - 1]}")backtrack(weights, values, capacity, i - 1, w)else:backtrack(weights, values, capacity, i - 1, w)def knapsack_backtrack(weights, values, capacity):backtrack(weights, values, capacity, len(weights), capacity)```在这个代码中,`knapsack`函数使用动态规划方法来解决问题,而`backtrack`函数使用回溯法。
算法分析与设计实验报告第五次附加实验
附录:
完整代码(回溯法)
//0-1背包问题回溯法求解
#include<iostream>
using namespace std;
template<class Typew,class Typep>
class Knap //Knap类记录解空间树的结点信息
{
template<class Typew,class Typep>
friend Typep Knapsack(Typep [],Typew [],Typew,int);
private:
Typep Bound(int i); //计算上界的函数
void Backtrack(int i); //回溯求最优解函数
Typew c; //背包容量
int n; //物品数
Typew *w; //物品重量数组¦
Typep *p; //物品价值数组
Typew cw; //当前重量
Typep cp; //当前价值
Typep bestp; //当前最后价值
};
template<class Typew,class Typep>
Typep Knapsack(Typep p[],Typew w[],Typew c,int n); //声明背包问题求解函数template <class Type>
inline void Swap(Type &a,Type &b); //声明交换函数
template<class Type>
void BubbleSort(Type a[],int n); //声明冒泡排序函数
int main()
{
int n ;//物品数
int c ;//背包容量
cout<<"物品个数为:";
cin>>n;
cout<<"背包容量为:";
cin>>c;
int *p = new int[n];//物品价值下标从1开始
int *w = new int[n];//物品重量下标从1开始
cout<<"物品重量分别为:"<<endl;
for(int i=1; i<=n; i++)
{
cin>>w[i];
}
cout<<"物品价值分别为:"<<endl;
for(int i=1; i<=n; i++) //以二元组(重量,价值)的形式输出每物品的信息{
cin>>p[i];
}
cout<<"物品重量和价值分别为:"<<endl;
for(int i=1; i<=n; i++) //以二元组(重量,价值)的形式输出每个物品的信息{
cout<<"("<<w[i]<<","<<p[i]<<") ";
}
cout<<endl;
cout<<"背包能装下的最大价值为:"<<Knapsack(p,w,c,n)<<endl; //输出结果system("pause");
return 0;
}
template<class Typew,class Typep>
void Knap<Typew,Typep>::Backtrack(int i)
{
if(i>n) //到达叶子节点
{
bestp = cp; //更新最优值
return;
}
if(cw + w[i] <= c) //进入左子树
{
cw += w[i];
cp += p[i];
Backtrack(i+1); //回溯
//回溯结束回到当前根结点
cw -= w[i];
cp -= p[i];
}
//进入右子树,条件是上界值比当前最优值大,否则就将右子树剪掉
if(Bound(i+1)>bestp)
{
Backtrack(i+1);
}
}
template<class Typew, class Typep>
Typep Knap<Typew, Typep>::Bound(int i)// 计算上界
{
Typew cleft = c - cw; // 剩余容量
Typep b = cp;
// 以物品单位重量价值递减序装入物品
while (i <= n && w[i] <= cleft)
{
cleft -= w[i];
b += p[i];
i++;
}
// 如果背包剩余容量不足以装下一个物品
if (i <= n)
{
b += p[i]/w[i] * cleft; //则将物品的部分装入到背包中
}
return b;
}
class Object //定义对象类,作用相当于结构体
{
template<class Typew,class Typep>
friend Typep Knapsack(Typep[],Typew [],Typew,int);
public:
int operator >= (Object a)const//符号重载函数,重载>=符号
{
return (d>=a.d);
}
private:
int ID; //编号
float d; //单位重量的价值
};
template<class Typew,class Typep>
Typep Knapsack(Typep p[],Typew w[],Typew c,int n)
{
//为Knap::Backtrack初始化
Typew W = 0;
Typep P = 0;
Object *Q = new Object[n];//创建Object类的对象数组¦
//初始化Object类的对象数组¦
for(int 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)//装入所有物品
{
return P;
}
//依物品单位重量价值降序排序
BubbleSort(Q,n);
Knap<Typew,Typep> K; //创建Knap的对象K
K.p = new Typep[n+1];
K.w = new Typew[n+1];
for(int i=1; i<=n; i++)
{
K.p[i] = p[Q[i-1].ID];
K.w[i] = w[Q[i-1].ID];
}
//初始化K
K.cp = 0;
K.cw = 0;
K.c = c;
K.n = n;
K.bestp = 0;
//回溯搜索
K.Backtrack(1);
delete []Q;
delete []K.w;
delete []K.p;
return K.bestp; //返回最优解
}
template<class Type>
void BubbleSort(Type a[],int n)
{
//记录一次遍历中是否有元素的交换
bool exchange;
for(int i=0; i<n-1;i++)
{
exchange = false ;
for(int j=i+1; j<=n-1; j++)
{
if(a[j]>=a[j-1])
{
Swap(a[j],a[j-1]);
exchange = true;
}
}
//如果这次遍历没有元素的交换,那么排序结束
if(exchange==false)
{
break ;
}
}
}
template <class Type>
inline void Swap(Type &a,Type &b) //交换函数{
Type temp = a;
a = b;
b = temp;
}。