数据结构回溯法求装载问题

合集下载

试用带限界的回溯法求解装船问题的实例n=4,w=[8,6,2,

试用带限界的回溯法求解装船问题的实例n=4,w=[8,6,2,

试用带限界的回溯法求解装船问题的实例n=4,w=[8,6,2,
1.试用带限界的回溯法求解装船问题的实例:n= 4,w= [ 8 , 6 ,
2 ,
3 ],c1 = 12,。

要求(1)写出两种限界方法 (2)展开的部分状态空间树(使用定长元组表示)并标明相应的参数。

2.使用带限界的回溯法求解以下带截止期的调度问题的实例:
n=4,作业集:{(5,1,1),(10,3,2),(6,2,1),(5,3,1)},每个3元组的第一个分量为罚款额,第二个分量为截止期,第三个分量为作业的执行时间。

要求:
1)给出限界条件;
2)画出展开的部分状态空间树,并标明相应的参数值.
3.试用分枝限界法求解以下最小罚款额调度问题:令三元组(p i,d i,t i)表示作业的罚款额、截止期和执行时间,4个作业分别为(8,2,1)、(4,2,1)、(9,3,2)、(2,1,1)。

要求:写出所使用的限界函数,画出分枝限界过程中展开的部分状态空间树。

(使用变长元组表示)。

分支限界法求解装载问题实验报告

分支限界法求解装载问题实验报告
void EnQueue(int wt,int& bestw, int i, int n, Queue*E,Queue*&bestE, bool ch) // 该函数负责加入活结点 { // 如果不是叶结点,则将结点权值 wt 加入队列 Q
if (i == n) {
if(wt == bestw) {
friend Type MaxLoading_three(Type *,Type,int,int *); private:
QNode* parent;// 指向父结点的指针 bool LChild; //左儿子标志 Type weight; //结点所相应的载重量 };
四、算法实现
void EnQueue(int wt,int& bestw, int i, int n, Queue*E,Queue*&bestE, bool ch) // 该函数负责加入活结点 { // 如果不是叶结点,则将结点权值 wt 加入队列 Q
} 构造最优解: 为了在算法结束后能方便地构造出与最优值相应的最优解,算法必须存储相应子集树中从活 结点到根结点的路径。为此目的,可在每个结点处设置指向其父结点e> class QNode {
friend void EnQueue(queue<QNode <Type> * >&,Type,int,int,Type,QNode <Type> *,QNode <Type> * &,int*,bool);
if (IsEmpty()) break; if(i<n) Add(-1, NULL, 0); // 同层结点的尾部 Delete(E); // 取下一扩展结点 i++; // 进入下一层 r -= w[i]; } Ew = E->weight; } //构造当前最优解 for(j = n -1; j>0; j--) { bestx[j] = bestE->LChild; bestE = bestE ->parent; } return 0; }

第5章 回溯法(1-例子)

第5章 回溯法(1-例子)

n; // 作业数};
8
} //end Backtrack
旅行售货员问题
9
旅行售货员问题
解空间树 —— 排列树 剪枝函数:当搜索到第i 层,图G中存在从顶点1经i个 顶点到某其他顶点的一条路 径,且x[1:i]的费用和大于当前 已获得的最优值时,剪去该子 树的搜索。 算法效率:
O((n-1)!)*O(n) =O(n!)
cleft -= w[i];
b += p[i];
i++;
} // 装满背包
if (i <= n) b += p[i]/w[i] * cleft;
return b;
4
}
0-1背包问题
例:n=4,c=7,p=[9,10,7,4],w=[3,5,2,1] 解空间树如下:
物品 1 物品 2 物品 3 物品 4
class Flowshop { friend Flow(int**, int, int []);
f+=f2[i];
private:
if (f < bestf) {
void Backtrack(int i);
Swap(x[i], x[j]);
int **M, // 各作业所需的处理时间
Backtrack(i+1);
(2)将剩余的集装箱装上第二艘轮船。
将第一艘轮船尽可能装满等价于选取全体集装箱的一个子集,
使该子集中集装箱重量之和最接近c1。由此可知,装载问题等
价于以下特n殊的0-1背包问题。
max wi xi i 1
用回溯法设计解装载问题的O(2n)计
n
s.t. wi xi c1
算时间算法。

回溯-装载问题

回溯-装载问题

姓名:
班级:
学号:
一.问题描述:
有一批共n个集装箱要装上2艘载重量分别为c1和c2的轮船,其中集装箱i的重量为wi,且 。
装载问题要求确定:是否有一个合理的装载方案可将这n个集装箱装上这2艘轮船。如果有,找出一种装载方案。
二.算法:
先尽可能多得把集装箱装上第一艘船。
利用回溯算法遍历解空间树。
三个集装箱的解空间树如下:
if(Value > MaxValue)
return 1;
return 0;
}
回溯函数:
void load(int Level,int Weight,int Value)
{
if(Level > Amount)//到达叶子节点
{
if(Value > MaxValue)//如果解优于之前的最优解
{
MaxValue = Value;
五.测试
五个集装箱:
结果正确!
六个集装箱:
结果正确!
一.问题描述:
给定n种物品和一背包.物品i的重量为 ,其价值为 ,背包容量为c.问应如何选择装入背包中的物品,使得装入背包中的物品的总价值最大.
二.算法
三个物品的解空间如下:
基本的剪枝条件和遍历的方法与装载问题相同。但是,在遍历“a”节点的左子树时,可以发现,进入“a”节点的左子树后,即使把2号3号物品的总价值加在一起也不可能超过遍历“a”节点的俄右子树时计算出的最大价值“9”,所以遍历,“a”节点的左子树时毫无意义的。
load(Level + 1,Weight);
}
寻找路径算法:
void FindWay(int Level,int Weight,int LeftWeight)

装载问题(回溯法)

装载问题(回溯法)

装载问题(回溯法)1、问题有n个集装箱要装上2艘载重量分别为c1和c2的轮船,其中集装箱i的重量为wi,且∑wi <= c1 + c2。

问是否有⼀个合理的装载⽅案,可将这n个集装箱装上这2艘轮船。

如果有,找出⼀种装载⽅案。

2、解析c1和c2是相互独⽴的,即如何在c1或者c2上,放置物品是互不⼲扰的。

但是假如先在c1上放置货物,则因剩下的货物不同,会使c2上装载的货物的情况发⽣变化。

因此,为了保证c1上装载的情况,满⾜c1和c2将所有的货物都装下,c1上应该尽可能的多放物品。

3、设计1 #include<bits/stdc++.h>2using namespace std;3const int num=100;4int n,c1,c2,w[num];// n个集装箱,A,B货轮载重量分别为C1,C2,W[i],第i个集装箱的重量5int cw,bw,rw;//cw,当前集装箱货物重量;bw,最优载重重量,rw,剩余集装箱重量;6int x[num],bx[num];//x[],A货轮的当前结果;bx[],A货轮的最优结果;7void BackTrack(int i) {8//处理完了前n个集装箱;9if(i>n){10if(cw>bw){//cw,⽬前A中装了cw重量的集装箱;11//更新最优解;12 bw=cw;13for(int i=1;i<=n;i++) bx[i]=x[i];14 }15return;16 }17//rw表⽰处理完第i个之后(选或不选),还剩下rw-w[i]重量的集装箱未处理;18 rw-=w[i];19if(cw+w[i]<=c1){//cw,第i个货箱之前的重量 + 第i个货箱⼩于A的最⼤重量C1;20 cw+=w[i];//加上21 x[i]=1;//标记i被选22 BackTrack(i+1);23 cw-=w[i];//减去重量24 x[i]=0;//撤销标记;25 }26//不选择第i个物品的话;27//if cw:表⽰[1:i)的数据 rw:表⽰(i,n]的数据,不包括第i个的数据28//如果不包括第i的数据的和(cw+rw)⼤于⽬前最优解bw,则可以递归下去;29if(cw+rw > bw){30 x[i]=0;31 BackTrack(i+1);32 }3334//处理完第i个物品当前的情况了;35//因为再上⼀层,有两种情况;36//1;选择第i物品;37//2:不选择第i个物品38//如果⽬前处理的是上⼀层第1种情况,那么我们就有必要加上这个w[i];39//否则会影响上⼀层处理第2种情况;40 rw+=w[i];41return ;42}43int main(){44 scanf("%d%d%d",&n,&c1,&c2);45for(int i=1;i<=n;i++) {46 scanf("%d",&w[i]);47 rw+=w[i];//rw表⽰⽬前最优集装箱的剩余重量;48 }49//递归回溯50 BackTrack(1);51//bw表⽰A货轮装下的货物重量;剩余的重量 > B可以放下的最多,则不可;52if(rw-bw>c2){53 printf("没有装载⽅案\n");54 }else{55 printf("货轮A:\n");56for(int i=1;i<=n;i++) {57if(bx[i]) {58 printf("%d ",i);59 }60 }61 printf("\n货轮B:\n");62for(int i=1;i<=n;i++) {63if(0==bx[i]) {64 printf("%d ",i);65 }66 }67 }68return0;69 }4、分析最坏情况要遍历图中所有结点,算法的时间复杂度为O(2")。

数据结构回溯法求装载问题概论

数据结构回溯法求装载问题概论

数据结构与算法分析回溯法求解装载问题回溯法求解装载问题一、方法一般原理基本思想:在回溯法中,每次扩大当前部分解时,都面临一个可选的状态集合,新的部分解救通过在该集合中进行选择结构而成的。

这样的状态集合,结构上是一颗多叉树,每个树结点代表一个可能的部分解,她的儿子是在他的基础上生成其他部分解。

树根为初始状态。

这样的状态集合,称为状态空间树。

回溯法对任一解的生成,一般都采用逐步扩大解的方式。

每前进一部,都试图在当前部分解的基础上扩大该部分解。

它在问题的状态空间树中,从开始结点(根结点)出发,一深度优先搜索整个状态空间。

这个开始结点成为活结点,同时也成为当前的扩展结点。

在当前扩展结点处,搜索向纵深方向移动一个新的结点。

这个新的结点为新的活结点,并成为当前的中结点。

如果在当前扩展结点处不能再向纵深方向移动,则当前的扩展结点就成为死结点。

此时,应往回移动(回溯)至最近的活结点处,并使这个活结点成为当前扩展结点。

回溯法以这种工作方式递归地在状态空间中搜索,直到找到所要求的解或解空间中以无活结点时为止。

回溯法与穷举法有某些联系,他们都是基于试探。

穷举法要将一个解的各个部分全都生成后,才检查是否满足条件,若不满足,则直接放弃该完整解、然后再尝试另一个可能的完整解,没有沿着一个可能的完整解的各个部分逐步回退生成解的过程。

而对于回溯法,一个解的各个部分是逐步生成的,当发现当前生成的某部分不满足约束条件,就放弃该部所做的工作,退到上一步进行新的尝试,而不是放弃整个解重来。

一般来说,回溯法要比穷举法效率高一些。

可用回溯法求解的问题P,通常要能表达为:对于已知的由n元组(1,x2,…,xn)组成的一个状态空间E={(x1,x2,…,xn)∣xi∈Si,i=1,2,…,n},给定关于n元组中的一个分量的一个约束集D,要求E中满足D的全部约束条件的所有n元组。

其中Si是分量xi的定义域,且|Si|有限,i=1,2,…,n。

我们称E中满足D的全部约束条件的任一n元组为问题P的一个解。

回溯算法装载问题

回溯算法装载问题

实验六 回溯算法〔2学时〕一、实验目的与要求1、掌握装载问题的回溯算法;2、初步掌握回溯算法;二、实验题有一批共n 个集装箱要装上2艘载重量分别为c1和c2的轮船,其中集装箱i 的重量为wi ,且 装载问题要求确定是否有一个合理的装载方案可将这个集装箱装上这2艘轮船。

如果有,找出一种装载方案。

三、实验提示void backtrack (int i){// 搜索第i 层结点if (i > n) // 到达叶结点更新最优解bestx,bestw;return;r -= w[i];if (cw + w[i] <= c) {// 搜索左子树x[i] = 1;cw += w[i];backtrack (i + 1);cw -= w[i]; }if (cw + r > bestw) {x[i] = 0; // 搜索右子树backtrack (i + 1); }r += w[i];}四、实验代码方法1:import java.util.*;/*** 回溯法解决装载问题* author Administrator**/public class demo {public static int n; //集装箱数public static int first_weight; //第一艘载重量public static int beautif_weight; //当前最优载重量public static int[] arr_weight; //集装箱重量数组public static int[] **; //public static int[] best**;public static int maxLoadingRE(int[] w, int c, int[] bestx) {//递归回溯 n = w.length;first_weight = c;beautif_weight = 0;211c c w n i i +≤∑=arr_weight = w;best** = bestx;** = new int[n];int r = 0; //剩余集装箱重量,未进展装载的重量for (int i = 0; i < n; i++) {r += arr_weight[i];}trackback(0, 0, r);return beautif_weight;}//到达层数,目前装载的重量,未装载的重量private static void trackback(int i, int cw, int r) {if (i == n) {//到达叶结点for (int j = 0; j < n; j++) {best**[j] = **[j];}beautif_weight = cw;return; //只是一次出栈操作,栈非空还要继续执行}if (cw + arr_weight[i] <= first_weight) { //已装载的加上要装载的小于第一个的载重量**[i] = 0; //0代表装在第一个上,1代表装在第二个上trackback(i + 1, cw + arr_weight[i], r); //试图装载下一个集装箱,r是针对第一个装的重量,因此装在第一个里不需要减,但装在第二个时就要减去该重量}if (r - arr_weight[i] > beautif_weight) { //已装载的加上要装载的已经大于第一个的载重量,并且用总的载重量r减去当前要装载的还比最好的载重量大**[i] = 1; //放到第二个上trackback(i + 1, cw, r - arr_weight[i]);}}public static int maxLoading(int[] w, int c, int[] bestx) {int i = 0; //当前层int n = w.length; //层总数int[] x = new int[n]; //x[0, i]为当前选择路径Arrays.fill(x, -1); //初始化为-1,0表示选择第一个,1表示选择第二个int bestw = 0; //当前最优装载重量int[] cw = new int[n]; //当前载重量int[] r = new int[n]; //剩余集装箱容量int tor = 0;for (int item : w) {//item取出w中的值,进展相加tor += item;}r[0] = tor;//要装载的重量cw[0] = 0;//搜索子树while (i > -1) {do {x[i] += 1;if (x[i] == 0) { //选择放在第一个〔左子树〕if (cw[i] + w[i] <= c) {if (i < n - 1) {cw[i + 1] = cw[i] + w[i];r[i + 1] = r[i];}break; //能放下就直接跳出这个do-while循环}}else { //选择放在第二个〔右子树〕if (r[i] - w[i] > bestw) {//剪枝函数,没有最优解好的话x[i]会自增到2,不会进入下面的if (x[i] < 2)if (i < n - 1) {r[i + 1] = r[i] - w[i];cw[i + 1] = cw[i];}break;}}} while (x[i] < 2); //对于放不下的在这里判断后才能取右子树if (x[i] < 2) {if (i == n - 1) {for (int j = 0; j < n; j++) {bestx[j] = x[j];}if (x[i] == 0) {bestw = cw[i] + w[i];}else {bestw = cw[i];}}else {i++;x[i] = -1;}}else {//当x[i]=2时,说明已经遍历完两个叶节点,应向上一层继续遍历其它节点i--;}}return bestw;}public static void main(String[] args) {int[] w = {0,10,40,40};int n = w.length;int c = 50;int[] bestx = new int[n];System.out.println("重量分别为:");for(int ws:w){System.out.print(","+ws);}System.out.println("\n");int bestw = maxLoadingRE(w, c, bestx);System.out.println("回溯选择结果为: " + bestw); System.out.println(Arrays.toString(bestx));}}方法2:public class demo2 {public static void main(String[] args) {int n=3,m;int c=50,c2=50;int w[]={0,10,40,40};int bestx[]=new int[w.length];demo2 demo2=new demo2();m=demo2.MaxLoading(w, c, n, bestx);System.out.println("轮船的载重量分别为:");System.out.println("c(1)="+c+",c(2)="+c2);System.out.println("待装集装箱重量分别为:");System.out.print("w(i)=");for (int i=0;i<=n;i++){System.out.print(","+w[i]);}System.out.println(");System.out.println("最优装载量为:");System.out.println("m(1)="+m);System.out.print("x(i)=");for (int i=0;i<=n;i++){System.out.print("+bestx[i]);}System.out.println(");int m2=0;for (int j=1;j<=n;j++){m2=m2+w[j]*(1-bestx[j]);}System.out.println("回溯选择结果为:"+m2);if(m2>c2){System.out.println("因为m(2)大于c(2),所以原问题无解!");}}int MaxLoading(int w[],int c,int n,int bestx[])//迭代回溯法,返回最优载重量及其相应解,初始化根结点{int i=1;//当前层,x[1:i-1]为当前路径int x[]=new int[n+1];int bestw=0; //当前最优载重量int cw=0; //当前载重量int r=0; //剩余集装箱重量for (int j=1;j<=n;j++){r+=w[j];}while(true)//搜索子树{while(i<=n &&cw+w[i]<=c)//进入左子树{r-=w[i];cw+=w[i];x[i]=1;i++;}if (i>n)//到达叶结点{for (int j=1;j<=n;j++){bestx[j]=x[j];}bestw=cw;}else//进入右子树{r-=w[i];x[i]=0; i++;}while (cw+r<=bestw){ //剪枝回溯i--;while (i>0){r+=w[i];i--;}//从右子树返回if (i==0){return bestw;}x[i]=0;cw-=w[i];i++;}}}}五、实验结果六、实验总结。

回溯法解决集装箱问题c语言程序

回溯法解决集装箱问题c语言程序

回溯法解决集装箱问题c语言程序集装箱问题是一个经典的组合优化问题,其目标是找到一种最优的方式将一组不同大小的集装箱装入一艘货船中。

在这个问题中,我们需要考虑集装箱的尺寸、重量以及货船的容量限制。

回溯法是一种常用的解决组合优化问题的方法,下面我们将介绍如何使用C语言编写回溯法解决集装箱问题的程序。

首先,我们需要定义一个结构体来表示集装箱的属性,包括尺寸和重量。

代码如下:```ctypedef struct {int size;int weight;} Container;```接下来,我们需要定义一个函数来计算货船的最大装载量。

这个函数将采用回溯法的思想,通过递归的方式遍历所有可能的装载方式,并返回最优解。

代码如下:```cint maxLoad(Container containers[], int n, int capacity, int index, int currentSize, int currentWeight) {// 如果已经遍历完所有集装箱,返回当前的装载重量if (index == n) {return currentWeight;}// 如果当前集装箱的尺寸或重量超过了货船的容量限制,跳过该集装箱if (currentSize + containers[index].size > capacity || currentWeight + containers[index].weight > capacity) {return maxLoad(containers, n, capacity, index + 1, currentSize, currentWeight);}// 尝试将当前集装箱装入货船int load = maxLoad(containers, n, capacity, index + 1, currentSize + containers[index].size, currentWeight + containers[index].weight);// 尝试不将当前集装箱装入货船int notLoad = maxLoad(containers, n, capacity, index + 1, currentSize, currentWeight);// 返回装载重量较大的方案return load > notLoad ? load : notLoad;}```最后,我们可以在主函数中调用上述函数来解决集装箱问题。

最佳调度问题(回溯法)

最佳调度问题(回溯法)

最佳调度问题(回溯法)⼀、实验内容运⽤回溯法解决0-1背包问题(或装载问题、或批处理作业调度、或旅⾏售货员问题)使⽤回溯法解决批处理作业调度问题⼆、所⽤算法基本思想及复杂度分析1.算法基本思想从⼀条路往前⾛,能进则进,不能进则退回来,换⼀条路再试。

确定了解空间的组织结构后,回溯法从根节点出发,以深度优先搜索⽅式搜索整个解空间。

回溯法以这种⼯作⽅式递归地在解空间中搜索,直到找到所要求的解或解空间所有解都被遍历过为⽌。

2.问题分析及算法设计问题分析:(1)给定n个作业的集合{J1,J2,…,Jn}。

每个作业必须先由机器1处理,然后由机器2处理。

作业Ji需要机器j的处理时间为tji。

(2)对于⼀个确定的作业调度,设Fji是作业i在机器j上完成处理的时间。

(3)所有作业在机器2上完成处理的时间和称为该作业调度的完成时间和。

(4)批处理作业调度问题要求对于给定的n个作业,制定最佳作业调度⽅案,使其完成时间和达到最⼩。

算法设计:算法复杂度分析由于回溯算法在每⼀个结点处耗费O(1)计算时间,故在最坏情况下,整个算法的时间复杂性为O(n!).三、源程序核⼼代码及注释(截图)四、运⾏结果五、调试和运⾏程序过程中产⽣的问题及解决⽅法,实验总结(5⾏以上)调试和运⾏程序时没有多⼤的问题,主要是调试的步骤多,容易看错,从⽽理解错,解决⽅法:我是为每⼀个变量都添加了监视,⼀个⼀个的调试下去,再结合⾃⼰画的草图,⼀个的去分析,当正确的分析出⼀个分⽀后,就会发现,后⾯基本上都是重复的实现着前⾯的基本操作。

经过这次实验对于回溯法解问题时,⾸先应该明确问题的解空间,⼀般说来,解任何问题都有⼀个⽬标,在约束条件下使⽬标达到最优的可⾏解称为该问题的最优解。

#include<bits/stdc++.h>using namespace std;int n, k;int a[25];//任务完成的时间int x[25];//当前任务完成的时间int result = 410;//完成全部任务的最早时间void Backtrack(int num, int t){if (num > n)//到达叶⼦节点{if (t < result){//将最少时间赋值给resultresult = t;}}if (t >= result){//当⼤于时,直接跳出递归return;}for (int i = 0; i < k; i++){if (x[i] + a[num] < result){//看是否剪枝//当前任务完成时间+前⾯任务完成时间⼩于总时间时x[i] += a[num];//继续搜索下去Backtrack(num + 1, max(t, x[i]));//回溯x[i] -= a[num];//返回最初的状态}}}int main(){cin >> n >> k;for (int i = 0; i < n; i++){cin >> a[i];}Backtrack(0, 0);cout << result << endl;return 0;}。

算法——分支限界法(装载问题)

算法——分支限界法(装载问题)

算法——分⽀限界法(装载问题)对⽐回溯法回溯法的求解⽬标是找出解空间中满⾜约束条件的所有解,想必之下,分⽀限界法的求解⽬标则是找出满⾜约束条件的⼀个解,或是满⾜约束条件的解中找出使某⼀⽬标函数值达到极⼤或极⼩的解,即在某种意义下的最优解。

另外还有⼀个⾮常⼤的不同点就是,回溯法以深度优先的⽅式搜索解空间,⽽分⽀界限法则以⼴度优先的⽅式或以最⼩耗费优先的⽅式搜索解空间。

分⽀限界法的搜索策略在当前节点(扩展节点)处,先⽣成其所有的⼉⼦节点(分⽀),然后再从当前的活节点(当前节点的⼦节点)表中选择下⼀个扩展节点。

为了有效地选择下⼀个扩展节点,加速搜索的进程,在每⼀个活节点处,计算⼀个函数值(限界),并根据函数值,从当前活节点表中选择⼀个最有利的节点作为扩展节点,使搜索朝着解空间上有最优解的分⽀推进,以便尽快地找出⼀个最优解。

分⽀限界法解决了⼤量离散最优化的问题。

选择⽅法1.队列式(FIFO)分⽀限界法队列式分⽀限界法将活节点表组织成⼀个队列,并将队列的先进先出原则选取下⼀个节点为当前扩展节点。

2.优先队列式分⽀限界法优先队列式分⽀限界法将活节点表组织成⼀个优先队列,并将优先队列中规定的节点优先级选取优先级最⾼的下⼀个节点成为当前扩展节点。

如果选择这种选择⽅式,往往将数据排成最⼤堆或者最⼩堆来实现。

例⼦:装载问题有⼀批共n个集装箱要装上2艘载重量分别为c1,c2的轮船,其中集装箱i的重量为wi,且要求确定是否有⼀个合理的装载⽅案可将这n个集装箱装上这2艘轮船。

可证明,采⽤如下策略可以得到⼀个最优装载⽅案:先尽可能的将第⼀艘船装满,其次将剩余的集装箱装到第⼆艘船上。

代码如下://分⽀限界法解装载问题//⼦函数,将当前活节点加⼊队列template<class Type>void EnQueue(Queue<Type> &Q, Type wt, Type &bestw, int i, int n){if(i == n) //可⾏叶结点{if(wt>bestw) bestw = wt ;}else Q.Add(wt) ; //⾮叶结点}//装载问题先尽量将第⼀艘船装满//队列式分⽀限界法,返回最优载重量template<class Type>Type MaxLoading(Type w[],Type c,int n){//初始化数据Queue<Type> Q; //保存活节点的队列Q.Add(-1); //-1的标志是标识分层int i=1; //i表⽰当前扩展节点所在的层数Type Ew=0; //Ew表⽰当前扩展节点的重量Type bestw=0; //bestw表⽰当前最优载重量//搜索⼦集空间树while(true){if(Ew+w[i]<=c) //检查左⼉⼦EnQueue(Q,Ew+w[i],bestw,i,n); //将左⼉⼦添加到队列//将右⼉⼦添加到队列即表⽰不将当前货物装载在第⼀艘船EnQueue(Q,Ew,bestw,i,n);Q.Delete(Ew); //取下⼀个节点为扩展节点并将重量保存在Ewif(Ew==-1) //检查是否到了同层结束{if(Q.IsEmpty()) return bestw; //遍历完毕,返回最优值Q.Add(-1); //添加分层标志Q.Delete(Ew); //删除分层标志,进⼊下⼀层i++;}}}算法MaxLoading的计算时间和空间复杂度为O(2^n).上述算法可以改进,设r为剩余集装箱的重量,当Ew+r<=bestw的时候,可以将右⼦树剪去。

回溯法——装载问题

回溯法——装载问题

回溯法——装载问题问题描述: 有⼀批共n个集装箱要装上2艘载重量分别为c1和c2的轮船,其中集装箱i的重量是wi,且不能超,即Σwi<=c1+c2。

算法思想: ——在给定的装载问题有解的情况下 最优装载⽅案:⾸先将第⼀艘轮船尽可能的装满; 然后将剩余的集装箱装上第⼆艘轮船。

将第⼀艘轮船尽可能的装满等价于选取全体集装箱的⼀个⼦集,使该⼦集中集装箱重量之和最接近c1。

算法设计: 先考虑装载⼀艘轮船的情况,依次讨论每个集装箱的装载情况,共分为两种,要么装(1),要么不装(0),因此很明显其解空间树可以⽤⼦集树来表⽰。

在算法Maxloading中,返回不超过c的最⼤⼦集和,但是并没有给出到达这个最⼤⼦集和的相应⼦集,稍后完善。

在算法Maxloading中,调⽤递归函数Backtrack(1)实现回溯搜索。

Backtrack(i)搜索⼦集树中的第i层⼦树。

在算法Backtrack中,当i>n时,算法搜索到叶结点,其相应的载重量为cw,如果cw>bestw,则表⽰当前解优于当前的最优解,此时应该更新bestw。

算法Backtrack动态地⽣成问题的解空间树。

在每个结点处算法花费O(1)时间。

⼦集树中结点个数为O(2^n),故Backtrack所需的时间为O(2^n)。

另外Backtrack还需要额外的O(n)的递归栈空间。

算法描述:1 template <class Type>2class Loading3 {4 friend Type MaxLoading(Type [],Type,int);5private:6void Backtrack(int i);7int n; //集装箱数⽬8 Type * w, //集装箱重量数组9 c, //第⼀艘轮船的载重量10 cw, //当前载重量11 bestw; //当前最优载重量12 };1314 template <class Type>15void Loading<Type>::Backtrack(int i) //回溯搜索16 { //搜索第i层结点17if(i>n) //到达叶结点18 {19if(cw>bestw)20 bestw = cw;21return;22 }23if(cw+w[i] <= c) //搜索⼦树24 {25 cw += w[i]; //当前载重量增加正考虑对象的重量26 Backtrack(i+1); 27 cw -= w[i]; //递归返回上⼀层时,记得减去刚考虑对象的集装箱重量28 }29 Backtrack(i+1); //递归搜索第i+1层30 }3132 template <class Type>33 Type MaxLoading(Type w[],Type c,int n) //返回最优载重量34 {35 Loading<Type> X; //初始化36 X.w = w;37 X.c = c;38 X.n = n;39 X.bestw = 0; //当前最优载重量的初值赋为040 X.cw = 0;41 X.Backtrack(1); //计算最优载重量————调⽤递归函数,实现回溯搜索42return X.bestw;43 }上界函数: 引⼊剪枝函数,⽤于剪去不含最优解的⼦树:即当cw(当前载重量)+r(未考察对象的总重量)<bestw(当前的最优载重量)时当前⼦树不可能包含最优解,直接减掉。

计算机算法设计与分析 回溯法之装载问题 实验报告

计算机算法设计与分析 回溯法之装载问题 实验报告

××实验报告纸计算机科学与工程学院(院、系)网络工程专业071班组计算机算法设计与分析课学号2007102878 姓名121实验日期2010.教师评定回溯法之装载问题1、实验目的:掌握解决装载问题的算法;初步掌握回溯法的应用。

2、算法分析:装载问题:有一批共n个集装箱要装上2艘载重量分别为c1和c2的轮船,其中集装箱i的重量为wi,且∑wi<=c1+c2。

要求确定是否有一个合理的装载方案可将这n个集装箱装上这2艘轮船。

如果有,找出一种装载方案。

3、程序代码:#include<iostream>using namespace std;template<class Type>class Loading{friend Type MaxLoading(Type [],Type,int,int []);private:void Backtrack(int i);int n, //集装箱数*x, //当前解*bestx; //当前最优解Type *w, //集装箱重量数组c, //第一艘轮船的载重量cw, //当前载重量bestw, //当前最优载重量r; //剩余集装箱重量};template<class Type>void Loading<Type>::Backtrack(int i){ if(i>n){if(cw>bestw) {for(int j=1;j<=n;j++) bestx[j]=x[j];bestw=cw;} r eturn;}r-=w[i];if(cw+w[i]<=c) {x[i]=1; cw+=w[i]; Backtrack(i+1); cw-=w[i];}if(cw+r>bestw) {x[i]=0; Backtrack(i+1);}r+=w[i];}template<class Type>Type MaxLoading(Type w[],Type c,int n,int bestx[]){ Loading<Type>X;X.x=new int[n+1]; X.w=w; X.c=c; X.n=n;X.bestx=bestx; X.bestw=0; X.cw=0; X.r=0;for(int i=1;i<=n;i++) X.r+=w[i];X.Backtrack(1); delete [ ] X.x; cout<<"所取物品:";for(i=1;i<=n;i++) cout<<bestx[i]<<" ";return X.bestw;}void main(){ int w[6],c,n=5,bestx[6];cout<<"输入五个物品重量:";for(int i=1;i<6;i++) cin>>w[i];cout<<"输入第一艘轮船的载重量:";cin>>c;cout<<endl<<"最大装载重量为:"<<MaxLoading(w,c,n,bestx)<<endl;}4、时间复杂度分析:时间复杂度为O(2^n)5、总结:通过学习装载问题,掌握了该算法的基本思想要素和方法。

回溯法 装载问题

回溯法 装载问题

将n个集装箱装上载重量为c1和c2的轮船,其中集装箱总重量<c1+c2,使用回溯法求出最优装载方案。

二、实验流程图:input.txtoutput.txt四、源程序:package javaapplication1; import java.io.*;public class Main { static int n;static int []w;static int c1,c2;static int cw;static int bestw;static int r;static int []x;static int []bestx;public static int maxloading(int[]ww,int cc,int[]xx){w=ww;c1=cc;bestw=0;cw=0;x=new int[n+1];bestx=xx;r=0;for(int i=1;i<=n;i++)r+=w[i];backtrack(1);return bestw;}private static void backtrack(int i){if(i>n){ if(cw>bestw){for(int j=1;j<=n;j++)bestx[j]=x[j];bestw=cw; }return;}r-=w[i];if(cw+w[i]<=c1){x[i]=1;cw+=w[i];backtrack(i+1);cw-=w[i];}if(cw+r>bestw){x[i]=0;backtrack(i+1);}r+=w[i]; }public static void main(String[] args) throws IOException {BufferedReader read =new BufferedReader(new InputStreamReader(new FileInputStream("input.txt")));String a=new String();a=read.readLine();n=Integer.parseInt(a);System.out.println("集装箱个数: "+n);x=new int[n+1];String[]b=new String[n];a=read.readLine();System.out.println("集装箱重量: "+a);b=a.split(",");w=new int[n+1];for(int i=1;i<=n;i++){w[i]=Integer.parseInt(b[i-1]);}a=read.readLine();c1=Integer.parseInt(a);a=read.readLine();c2=Integer.parseInt(a);System.out.println("轮船载重量: "+c1+","+c2);int result= maxloading(w,c1,x);int max,temp;for(int i=1;i<3;i++){for(int j=2;j<3;j++){if(w[i]>w[j]){temp=w[i];w[i]=w[j];w[j]=temp;}}}if((w[3]>c1)&&(w[3]>c2)){System.out.println("都不可装");}else{System.out.println("轮船1装载的集装箱:");for (int u=1;u<n+1;u++)if(bestx[u]==1)System.out.println(u+" ");if(r>(result+c2))System.out.println("轮船1可装:"+result+" "+"轮船2装不完.");else{System.out.println("轮船2装载的集装箱:");for (int u=1;u<n+1;u++)if(bestx[u]==0)System.out.println(u+" ");System.out.println("最优装载--轮船1:"+result+" "+"轮船2:"+(r-result));}}PrintWriter print=new PrintWriter(new OutputStreamWriter(new FileOutputStream("output.txt")));if((w[3]>c1)&&(w[3]>c2)){print.println("都不可装。

回溯法求解装载问题Java代码

回溯法求解装载问题Java代码

//#include "stdafx.h"import java.io.*;//class MaxHeap;//class HeapNode;class Zhuang{class bbnode{/*friend void AddLiveNode(MaxHeap&,int*,int,bool,int);friend int MaxLoading(int*,int,int,int *); *///friend class AdjacencyGraph;private int *parent; // 指向父节点的指针public boolean Lchild; //左儿子节点标志}class HeapNode{/*friend void AddLiveNode(MaxHeap&,bbnode *,int,bool,int);friend int MaxLoading(int*,int,int,int*);friend class MaxHeap;*/public operator int() const{ return uweight;}private int *ptr; //指向活节点在子集树中相应节点的指针public int uweight; // 活节点的优先级--上界public int level; // 活节点在子集树中所处的层序号}class MaxHeap{/*friend void AddLiveNode(MaxHeap&,bbnode *,int,bool,int);friend int Maxloading(int*,int,int,int*);*/private int m;public int sz;HeapNode *Hx;public MaxHeap(int s){sz=s;Hx=new HeapNode[sz];m=0;}~MaxHeap(){delete [] Hx;}void insert(HeapNode N){Hx[m+1]=N;Hx[m+1].ptr=N.ptr;Hx[m+1].uweight=N.uweight;Hx[m+1].level=N.level;m++;}HeapNode DeleteMax(HeapNode &N){int p;int temp=0;for(p=1;p <=m;p++){if(Hx[p].uweight >Hx[temp].uweight)temp=p; }//N=Hx[temp];N.ptr=Hx[temp].ptr;N.uweight=Hx[temp].uweight;N.level=Hx[temp].level;if(temp>=m)m--;else{for(int j=temp;j<m;j++){Hx[j]=Hx[j+1];}m--;}return N;}}void AddLiveNode(MaxHeap&H,int *E,int wt,boolean ch,int lev){ //将活节点加入到最大堆H中int *b=new int;b->parent=E;b->Lchild=ch; //cout <<"b->Lchild" <<b->Lchild <<endl;HeapNode N;N.uweight=wt;N.level=lev;N.ptr=b;H.insert(N); //cout <<N.level <<endl;}// template <class int>int MaxLoading(int w[],int c,int n,int bestw[]){//定义最大堆容量为50MaxHeap H(50);//定义剩余重量数组rint *r=new int[n+1];r[n]=0;for(int j=n-1;j>0;j--)r[j]=r[j+1]+w[j+1];//初始化int i=1; //当前扩展结点所处的层int *E=0; //当前扩展结点int Ew=0; //扩展结点所相应的载重量//搜索子集空间树while(i!=n+1){ //非叶结点//检查当前扩展结点的儿子结点if(Ew+w[i] <=c){ // 左儿子为可行节点AddLiveNode(H,E,Ew+w[i]+r[i],true,i+1);}// 右儿子节点AddLiveNode(H,E,Ew+r[i],false,i+1);//取下一扩展结点HeapNode N;H.DeleteMax(N);i=N.level;E=N.ptr;Ew=N.uweight-r[i-1];//cout <<"N.weight" <<N.uweight <<Ew <<endl;}//构造当前最优解for(int x=n;x>0;x--){bestw[x]=E->Lchild; //cout <<bestw[x];E=E->parent;}return Ew;}///////////////////////////////////////////////////////////////////*void main(){int n,c;cout <<"请输入要装载的物品的个数:" <<endl;cin>>n;int *bestw=new int[n+1];int *w=new int[n+1];cout <<"请输入每个物品的重量:" <<endl;for(int j=1;j <=n;j++)cin>>w[j];cout <<"请输入船最大能容纳的重量:" <<endl;cin>>c;int max=MaxLoading(w,c,n,bestw);cout <<"装载问题的最优解为:"<<endl;for(int k=1;k <=n;k++)cout <<bestw[k] <<"\t";cout <<endl;cout <<"最优装载量为:"<<endl;cout <<max <<endl;cout<<endl;} */public static void main(String [] args){int n=3,c=9;int *bestw=new int[n+1];int *w={3,5,4};int max=MaxLoading(w,c,n,bestw);System.out.println("装载问题的最优解为:");for(int k=1;k <=n;k++)System.out.println(" "+bestw[k]);System.out.println("最优装载量为:"+max);}}。

装载问题5种解决方案

装载问题5种解决方案

算法分析与设计2016/2017(2)实验题目装载问题5种解法学生姓名学生学号学生班级任课教师提交日期2017计算机科学与技术学目录一问题定义 (2)二解决方案 (2)1优先队列式分支限界法求解 (2)1。

3运行结果 (12)2队列式分支限界法求解 (12)2。

1算法分析 (12)2。

2代码 (13)2.3测试截图 (21)3回朔法-迭代 (21)3.1算法分析 (21)3。

2代码 (21)3.3测试截图 (25)4回朔法-递归 (25)4。

1算法分析 (25)4。

2代码 (25)4.3测试截图 (30)5贪心算法 (30)5.1算法分析 (30)5.2代码 (30)5。

3测试截图 (33)一问题定义有一批共有 n 个集装箱要装上两艘载重量分别为 c1 和 c2 的轮船,其中集装箱 i 的重量为 w[i],且重量之和小于(c1 + c2)。

装载问题要求确定是否存在一个合理的装载方案可将这 n 个集装箱装上这两艘轮船。

如果有,找出一种装载方案.二解决方案1优先队列式分支限界法求解1.1算法分析活结点x在优先队列中的优先级定义为从根结点到结点x的路径所相应的载重量再加上剩余集装箱的重量之和。

优先队列中优先级最大的活结点成为下一个扩展结点.以结点x为根的子树中所有结点相应的路径的载重量不超过它的优先级。

子集树中叶结点所相应的载重量与其优先级相同。

在优先队列式分支限界法中,一旦有一个叶结点成为当前扩展结点,则可以断言该叶结点所相应的解即为最优解。

此时可终止算法.1.2代码1。

2-1////MaxHeap.htemplate<class T〉class MaxHeap{public:MaxHeap(int MaxHeapSize = 10);~MaxHeap() {delete []heap;}int Size() const {return CurrentSize;}T Max(){ //查找if (CurrentSize == 0){throw OutOfBounds();}return heap[1];}MaxHeap<T>& Insert(const T&x); //增MaxHeap<T>& DeleteMax(T& x);//删void Initialize(T a[],int size, int ArraySize);private:int CurrentSize,MaxSize;T *heap;// element array};template<class T〉MaxHeap〈T〉::MaxHeap(int MaxHeapSize){// Max heap constructor。

算法分析期末考试集答案(套)

算法分析期末考试集答案(套)

算法分析期末考试集答案(套)《算法分析与设计》⼀、解答题 1. 机器调度问题。

问题描述:现在有n 件任务和⽆限多台的机器,任务可以在机器上得到处理。

每件任务的开始时间为s i ,完成时间为f i ,s i问题实例:若任务占⽤的时间范围是{[1,4],[2,5],[4,5],[2,6],[4,7]},则按时完成所有任务最少需要⼏台机器?(提⽰:使⽤贪⼼算法)画出⼯作在对应的机器上的分配情况。

2. 已知⾮齐次递归⽅程:f (n)bf (n 1)g(n)f (0)c =-+??=? ,其中,b 、c 是常数,g(n)是n 的某⼀个函数。

则f(n)的⾮递归表达式为:nnn i i 1f (n)cb b g(i)-==+∑。

现有Hanoi 塔问题的递归⽅程为:h(n)2h(n 1)1h(1)1=-+??=? ,求h(n)的⾮递归表达式。

解:利⽤给出的关系式,此时有:b=2, c=1, g(n)=1, 从n 递推到1,有:n 1n 1n 1i i 1n 1n 22n h(n)cbb g(i)22 (22121)----=--=+=+++++=-∑3. 单源最短路径的求解。

问题的描述:给定带权有向图(如下图所⽰)G =(V,E),其中每条边的权是⾮负实数。

另外,还给定V 中的⼀个顶点,称为源。

现在要计算从源到所有其它各顶点的最短路长度。

这⾥路的长度是指路上各边权之和。

这个问题通常称为单源最短路径问题。

解法:现采⽤Dijkstra 算法计算从源顶点1到其它顶点间最短路径。

请将此过程填⼊下表中。

4. 请写出⽤回溯法解装载问题的函数。

装载问题:有⼀批共n 个集装箱要装上2艘载重量分别为c1和c2的轮船,其中集装箱i 的重量为wi ,且121nii w c c=≤+∑。

装载问题要求确定是否有⼀个合理的装载⽅案可将这n 个集装箱装上这2艘轮船。

如果有,找出⼀种装载⽅案。

解:void backtrack (int i){// 搜索第i 层结点if (i > n) // 到达叶结点更新最优解bestx,bestw;return; r -= w[i];if (cw + w[i] <= c) {// 搜索左⼦树43 2 1 100 30 maxint10 - {1} 初始 dist[5] dist[4] dist[3] dist[2] u S 迭代x[i] = 1;cw += w[i];backtrack(i + 1);cw -= w[i]; }if (cw + r > bestw) {x[i] = 0; // 搜索右⼦树backtrack(i + 1); }r += w[i];}5. ⽤分⽀限界法解装载问题时,对算法进⾏了⼀些改进,下⾯的程序段给出了改进部分;试说明斜线部分完成什么功能,以及这样做的原因,即采⽤这样的⽅式,算法在执⾏上有什么不同。

回溯法:最大装载问题(使用递归,不做任何优化)

回溯法:最大装载问题(使用递归,不做任何优化)

回溯法:最⼤装载问题(使⽤递归,不做任何优化)// 16x1.cpp : Defines the entry point for the console application.//#include "stdafx.h"// 回溯法,解空间分为排列数和⼦集树,前者是不同节点顺序的排列,后者是⼀个(0,1,...)的向量⼦集// 最⼤装载问题,是⼀个NP问题,⽬前只计算第⼀艘船,属于⼦集树// 有⼏个货物,⼦集树就有⼏层,当前题⽬为5层// 我感觉递归还是太过于精巧和经凑,很难挖空⼼思⾃⼰写出来,多熟悉别⼈现有的程序是⼀个好办法。

#include<iostream.h>template<class T>class Loading {friend GetMaxLoading(T [], T, int);private:void Compute(int i);int n; // 货箱数量T *w, // 货箱重量数组c, // 第⼀艘船的容量cw, // 当前的装载重量bestw; // ⽬前最优装载重量};// 重要特性:在遍历的时候,就得到了结果。

使⽤全局变量保存了最⼤装载值// 此函数⼀共只有⼀处return// 运算过程:// 第⼀、第⼆层能放下货物,⾛的是if语句的条件分⽀,然后开始计算第三层。

// 第三层因为不满⾜if语句条件,直接⾛的是if语句后⾯的语句,什么值都不改变。

// 第四、第五层直接⾛的是if语句后⾯的语句,什么值都不改变。

// 第六层返回,于是第五、第四、第三都直接返回,相当于第⼆层的Compute(i+1);语句计算完毕(但第⼆层整个过程没有计算完毕,后⾯还有其它语句)// 此时,相当于第⼆层x=1的情况的⼦树计算完毕了,但还要在这层继续计算x=0的⼦树。

// 所以把当前cw值恢复成不装当前货物时候的值,即程序继续往下执⾏cw -= w[i];语句// 然后相当于,在第⼆层不装货物的情况下,开始遍历计算第三层的值。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

数据结构与算法分析回溯法求解装载问题回溯法求解装载问题一、方法一般原理基本思想:在回溯法中,每次扩大当前部分解时,都面临一个可选的状态集合,新的部分解救通过在该集合中进行选择结构而成的。

这样的状态集合,结构上是一颗多叉树,每个树结点代表一个可能的部分解,她的儿子是在他的基础上生成其他部分解。

树根为初始状态。

这样的状态集合,称为状态空间树。

回溯法对任一解的生成,一般都采用逐步扩大解的方式。

每前进一部,都试图在当前部分解的基础上扩大该部分解。

它在问题的状态空间树中,从开始结点(根结点)出发,一深度优先搜索整个状态空间。

这个开始结点成为活结点,同时也成为当前的扩展结点。

在当前扩展结点处,搜索向纵深方向移动一个新的结点。

这个新的结点为新的活结点,并成为当前的中结点。

如果在当前扩展结点处不能再向纵深方向移动,则当前的扩展结点就成为死结点。

此时,应往回移动(回溯)至最近的活结点处,并使这个活结点成为当前扩展结点。

回溯法以这种工作方式递归地在状态空间中搜索,直到找到所要求的解或解空间中以无活结点时为止。

回溯法与穷举法有某些联系,他们都是基于试探。

穷举法要将一个解的各个部分全都生成后,才检查是否满足条件,若不满足,则直接放弃该完整解、然后再尝试另一个可能的完整解,没有沿着一个可能的完整解的各个部分逐步回退生成解的过程。

而对于回溯法,一个解的各个部分是逐步生成的,当发现当前生成的某部分不满足约束条件,就放弃该部所做的工作,退到上一步进行新的尝试,而不是放弃整个解重来。

一般来说,回溯法要比穷举法效率高一些。

可用回溯法求解的问题P,通常要能表达为:对于已知的由n元组(1,x2,…,xn)组成的一个状态空间E={(x1,x2,…,xn)∣xi∈Si,i=1,2,…,n},给定关于n元组中的一个分量的一个约束集D,要求E中满足D的全部约束条件的所有n元组。

其中Si是分量xi的定义域,且|Si|有限,i=1,2,…,n。

我们称E中满足D的全部约束条件的任一n元组为问题P的一个解。

解问题P的最朴素的方法就是枚举法,即对E中的所有n元组逐一地检测其是否满足D的全部约束,若满足,则为问题P的一个解。

但显然,其计算量是相当大的。

我们发现,对于许多问题,所给定的约束集D具有完备性,即i元组(x1,x2,…,xi)满足D中仅涉及到x1,x2,…,xi的所有约束意味着j(j<i)元组(x1,x2,…,xj)一定也满足D中仅涉及到x1,x2,…,xj的所有约束,i=1,2,…,n。

换句话说,只要存在0≤j≤n-1,使得(x1,x2,…,xj)违反D中仅涉及到x1,x2,…,xj的约束之一,则以(x1,x2,…,xj)为前缀的任何n元组(x1,x2,…,xj,xj+1,…,xn)一定也违反D中仅涉及到x1,x2,…,xi的一个约束,n≥i>j。

因此,对于约束集D具有完备性的问题P,一旦检测断定某个j元组(x1,x2,…,xj)违反D中仅涉及x1,x2,…,xj的一个约束,就可以肯定,以(x1,x2,…,xj)为前缀的任何n元组(x1,x2,…,xj,xj+1,…,xn)都不会是问题P的解,因而就不必去搜索它们、检测它们。

回溯法正是针对这类问题,利用这类问题的上述性质而提出来的比枚举法效率更高的算法。

回溯法首先将问题P的n元组的状态空间E表示成一棵高为n的带权有序树T,把在E中求问题P的所有解转化为在T中搜索问题P的所有解。

树T类似于检索树,它可以这样构造:设Si中的元素可排成xi (1),xi (2),…,xi (mi-1),|Si| =mi,i=1,2,…,n。

从根开始,让T的第I层的每一个结点都有mi个儿子。

这mi个儿子到它们的双亲的边,按从左到右的次序,分别带权xi+1 (1),xi+1 (2),…,xi+1 (mi),i=0,1,2,…,n-1。

照这种构造方式,E中的一个n元组(x1,x2,…,xn)对应T中的一个叶子结点,T的根到这个叶子结点的路径上依次的n条边的权分别为x1,x2,…,xn,反之亦然。

另外,对于任意的0≤i≤n-1,E中n元组(x1,x2,…,xn)的一个前缀I元组(x1,x2,…,xi)对应于T中的一个非叶子结点,T的根到这个非叶子结点的路径上依次的I条边的权分别为x1,x2,…,xi,反之亦然。

特别,E中的任意一个n元组的空前缀(),对应于T的根。

因而,在E中寻找问题P的一个解等价于在T中搜索一个叶子结点,要求从T的根到该叶子结点的路径上依次的n条边相应带的n个权x1,x2,…,xn满足约束集D的全部约束。

在T中搜索所要求的叶子结点,很自然的一种方式是从根出发,按深度优先的策略逐步深入,即依次搜索满足约束条件的前缀1元组(x1i)、前缀2元组(x1,x2)、…,前缀I元组(x1,x2,…,xi),…,直到i=n为止。

在回溯法中,上述引入的树被称为问题P的状态空间树;树T上任意一个结点被称为问题P的状态结点;树T上的任意一个叶子结点被称为问题P的一个解状态结点;树T上满足约束集D的全部约束的任意一个叶子结点被称为问题P 的一个回答状态结点,它对应于问题P的一个解。

二、描述问题有一批共n个集装箱要装上2艘载重量分别为c1和c2的轮船,其中集装箱i的重量为,且,要求确定是否有一个合理的装载方案可将这n个集装箱装上这2艘轮船。

如果有,请给出该方案。

三、由原理得到的算法、算法的复杂度、改进1、可得算法回溯法解装载问题时,用子集树表示解空间最合适。

void Backtrack(int t){if(t>n)Output(x);else{for(int i=0; i<z; i++){x[t] = i;if(Constraint(t) && Bound(t))Backtrack(t+1);}}}Maxloading调用递归函数backtrack实现回溯。

Backtrack(i)搜索子集树第i层子树。

i>n时,搜索至叶节点,若装载量>bestw,更新bestw。

当i<=n时,扩展节点Z是子集树内部节点。

左儿子节点当cw+w[i]<=c时进入左子树,对左子树递归搜索。

右儿子节点表示x[i]=0的情形。

2、时间复杂度Backtrack动态的生成解空间树。

每个节点花费O(1)时间。

Backtrack执行时间复杂度为O(2n)。

另外Backtrack还需要额外O(n)递归栈空间。

3、可能的改进可以再加入一个上界函数来剪去已经不含最优解的子树。

设Z是解空间树第i层上的一个当前扩展结点,curw是当前载重量,maxw是已经得到的最优载重量,如果能在当前结点确定curw+剩下的所有载重量≤maxw 则可以剪去些子树。

所以可以引入一个变量r表示剩余的所有载重量。

虽然改进后的算法时间复杂度不变,但是平均情况下改进后算法检查结点数较少。

进一步改进:(1)首先运行只计算最优值算法,计算最优装载量,再运行backtrack算法,并在算法中将bestw置为W,在首次到叶节点处终止。

(2)在算法中动态更新bestw。

每当回溯一层,将x[i]存入bestx[i].从而算法更新bestx所需时间为O(2n)。

四、算法实现int Backtrack(int i)//搜索第i层节点{int j_index; //如果到达叶结点,则判断当前的cw,如果比前面得到的最优解bestw好,则替换原最优解。

if(i>n){if(cw>bestw){for(j_index=1; j_index<=n; j_index++)bestx[j_index]=x[j_index]; bestw=cw;}return 1;} //搜索子树r-=w[i];if(cw+w[i]<=c)//搜索左子树,如果当前剩余空间可以放下当前物品也就是,cw + w[ i ] <= c{x[i]=1;cw+=w[i];//把当前载重cw += w[ i ]Backtrack(i+1);//递归访问其左子树,Backtrack( i + 1 ) cw-=w[i];/ /访问结束,回到调用点,cw - = w[ i ]}if(cw+r>bestw)//搜索右子树{x[i]=0;Backtrack(i+1);}r+=w[i];}int maxloading(int mu[],int c,int n,int *mx){loading x;x.w=mu;x.x=mx;x.c=c;x.n=n;x.bestw=0;x.cw=0;x.Backtrack(1);return x.bestw;}五、总结由此,我们可以总结出回溯法的一般步骤:(1)针对所给问题,定义问题的解空间;(2)确定易于搜索的解空间结构;(3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。

通过DFS思想完成回溯,完整过程如下:(1)设置初始化的方案(给变量赋初值,读入已知数据等)。

(2)变换方式去试探,若全部试完则转(7)。

(3)判断此法是否成功(通过约束函数),不成功则转(2)。

(4)试探成功则前进一步再试探。

(5)正确方案还未找到则转(2)。

(6)已找到一种方案则记录并打印。

(7)退回一步(回溯),若未退到头则转(2)。

(8)已退到头则结束或打印无解。

可以看出,回溯法的优点在于其程序结构明确,可读性强,易于理解,而且通过对问题的分析可以大大提高运行效率。

但是,对于可以得出明显的递推公式迭代求解的问题,还是不要用回溯法,因为它花费的时间比较长。

附录(源码)#INCLUDE<STDLIB.H>#INCLUDE<STDIO.H>#INCLUDE<IOSTREAM.H>TYPEDEF INT S TATUS;TYPEDEF INT T YPE;INT N=0;//集装箱数T YPE *X=(T YPE*)MALLOC((50)*SIZEOF(T YPE));//当前解T YPE *BESTX=(T YPE*)MALLOC((50)*SIZEOF(T YPE));//当前最优解T YPE C=0,//第一艘轮船的载重量CW=0,//当前载重量BESTW=0,//当前最优载重量R=0,*W=(T YPE*)MALLOC((50)*SIZEOF(T YPE));//集装箱重量数组INT B ACKTRACK(INT I)//搜索第I层节点{INT J_INDEX;//如果到达叶结点,则判断当前的CW,如果比前面得到的最优解BESTW好,则替换原最优解。

IF(I>N){IF(CW>BESTW){FOR(J_INDEX=1; J_INDEX<=N; J_INDEX++)BESTX[J_INDEX]=X[J_INDEX]; BESTW=CW;}RETURN 1;}//搜索自树R-=W[I];IF(CW+W[I]<=C)//搜索左子树,如果当前剩余空间可以放下当前物品也就是,CW + W[ I ]<= C{X[I]=1;CW+=W[I];//把当前载重CW += W[ I ]B ACKTRACK(I+1);//递归访问其左子树,B ACKTRACK( I +1)CW-=W[I];//访问结束,回到调用点,CW -= W[ I ]}IF(CW+R>BESTW)//搜索右子树{X[I]=0;B ACKTRACK(I+1);}R+=W[I];}T YPE*I NITIATE(){INT INDEX=1;PRINTF("输入集装箱个数:");SCANF("%D",&N);PRINTF("输入轮船载重量:");SCANF("%D",&C);WHILE(INDEX<=N)//数组从1号单元开始存储{PRINTF("输入集装箱%D的重量:",INDEX);SCANF("%D",&W[INDEX]);INDEX++;}BESTW =0;CW =0;R =0;FOR(INDEX =1;INDEX <= N; INDEX++)R += W[INDEX];//初始时R为全体物品的重量和PRINTF("N=%D C=%D CW=%D BESTW=%D R=%D\N",N,C,CW,BESTW,R); FOR(INDEX=1;INDEX<=N;INDEX++){PRINTF("W[%D]=%D ",INDEX,W[INDEX]);}PRINTF("\N");RETURN W;}INT MAIN(){INT I;I NITIATE();//计算最优载重量B ACKTRACK(1);FOR(I=1;I<=N;I++){PRINTF("%D ",W[I]);PRINTF("%D\N",BESTX[I]);}RETURN BESTW;}结果图货物的总重量为48,轮船的总重量为34,所以所用货物都装上了轮船。

相关文档
最新文档