算法分析设计回溯法求解装载问题实验报告

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

Backtrack(i+1); } r+=w[i]; } Type* Initiate() { 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;indeBiblioteka Baidu<=n;index++) { printf("w[%d]=%d ",index,w[index]); } printf("\n"); return w; } int main() { int i; Initiate(); //计算最优载重量 Backtrack(1); for(i=1;i<=n;i++) { printf("%d ",w[i]);
四、算法实现
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)//搜索右子树 {
五、总结
由此,我们可以总结出回溯法的一般步骤: ( 1 )针对所给问题,定义问题的解空间; ( 2 )确定易于搜索的解空间结构; ( 3 )以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。 通过 DFS 思想完成回溯,完整过程如下: (1)设置初始化的方案(给变量赋初值,读入已知数据等)。 (2)变换方式去试探,若全部试完则转(7)。 (3)判断此法是否成功(通过约束函数),不成功则转(2)。 (4)试探成功则前进一步再试探。 (5)正确方案还未找到则转(2)。 (6)已找到一种方案则记录并打印。 (7)退回一步(回溯),若未退到头则转(2)。 (8)已退到头则结束或打印无解。 可以看出,回溯法的优点在于其程序结构明确,可读性强,易于理解,而且通过对问题的分 析可以大大提高运行效率。但是,对于可以得出明显的递推公式迭代求解的问题,还是不要 用回溯法,因为它花费的时间比较长。
二、描述问题
有一批共 n 个集装箱要装上 2 艘载重量分别为 c1 和 c2 的轮船,其中集装箱 i 的重量为 n wi ,且
w
i 1
i
c1 c 2 ,要求确定是否有一个合理的装载方案可将这 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 执行时间复杂度为 n O(2 )。另外 Backtrack 还需要额外 O(n)递归栈空间。 3、可能的改进
可以再加入一个上界函数来剪去已经不含最优解的子树。 设 Z 是解空间树第 i 层上的一个当 前扩展结点,curw 是当前载重量,maxw 是已经得到的最优载重量,如果能在当前结点确定 curw+剩下的所有载重量 ≤ maxw 则可以剪去些子树。 所以可以引入一个变量 r 表示剩余的 所有载重量。虽然改进后的算法时间复杂度不变,但是平均情况下改进后算法检查 结点数 较少。 进一步改进: (1) 首先运行只计算最优值算法,计算最优装载量,再运行 backtrack 算法,并在算法 中将 bestw 置为 W,在首次到叶节点处终止。 (2) 在算法中动态更新 bestw。 每当回溯一层, 将 x[i]存入 bestx[i].从而算法更新 bestx n 所需时间为 O(2 )。
六、附录(源码)
#include<stdlib.h>
#include<stdio.h> #include<iostream.h> typedef int Status; typedef int Type; int n=0; //集装箱数 Type *x=(Type*)malloc((50)*sizeof(Type));//当前解 Type *bestx=(Type*)malloc((50)*sizeof(Type));//当前最优解 Type c=0, //第一艘轮船的载重量 cw=0, //当前载重量 bestw=0, //当前最优载重量 r=0, *w=(Type*)malloc((50)*sizeof(Type)); //集装箱重量数组 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;
(1) (1)
,xi
(2)
,…,xi
(mi)
(mi-1)
,|Si| =mi,i=1,2,…,n。从根开始,
让 T 的第 I 层的每一个结点都有 mi 个儿子。这 mi 个儿子到它们的双亲的边,按从左到右的 ,xi+1
(2)
,…,xi+1
,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 的根到该
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; }
叶子结点的路径上依次的 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 的一个解。
回溯法求解装载问题
一、方法一般原理
回溯法也称为试探法,该方法首先暂时放弃关于问题规模大小的限制,并将问题的候选 解按某种顺序逐一枚举和检验。当发现当前候选解不可能是解时,就选择下一个候选解;倘 若当前候选解除了还不满足问题规模要求外, 满足所有其他要求时, 继续扩大当前候选解的 规模,并继续试探。如果当前候选解满足包括问题规模在内的所有要求时,该候选解就是问 题的一个解。在回溯法中,放弃当前候选解,寻找下一个候选解的过程称为回溯。扩大当前 候选解的规模,以继续试探的过程称为向前试探。 可用回溯法求解的问题 P,通常要能表达为:对于已知的由 n 元组(x1,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 次序,分别带权 xi+1
相关文档
最新文档