西北工业大学算法设计实验2
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
实验二:回溯法VS分支定界法
一、问题分析
回溯法可以处理货郎担问题,分支定界法也可以处理货郎担问题,回溯法和分支定界法哪个算法处理货郎担问题效率更高呢?
实现回溯法、分支定界法,以及不同的界值函数(课上讲过的或者自己新设计的),通过随机产生10个不同规模的算例(城市数量分别为10,20,40,80,100,120,160,180,200,500,或者其它规模),比较回溯法和分支定界法在相同界值函数下的执行效率。另外,分别比较回溯法和分支定界法在不同界值函数下的执行效率。
二、算法基本思想
1、回溯法
从初始状态出发,搜索其所能到达的所有“状态”,
当一条路走到尽头,再后退一步或若干步,从另外一种状态出发,继续搜索,直到所有的路径都搜索过。这种不断前进、不断回溯寻找解的方法叫回溯法。回溯法通常将问题解空间组织成“树”结构,采用系统的方法搜索解空间树,从而得到问题解。
搜索策略:
深度优先为主,也可以采用广度优先、函数优先、广度深度结合等。
避免无效搜索策略:
约束函数:在扩展结点处剪去不满足约束条件的子树
界限函数:在扩展结点处剪去得不到最优解的子树
2、分支限界法
分支界限法类似与回溯法,也是在问题解空间中搜索问题解的一种算法。
分支界限法与回溯法思想对比:
求解目标:回溯法的可以用于求解目标是找出解空间树中满足约束条件的所有解,而分支限界法的求解目标通常是找出满足约束条件的一个解或最优解。
搜索方式的不同:回溯法主要以深度优先的方式搜索解空间树,而分支限界法则
主要以广度优先或以最小耗费优先的方式搜索解空间树。
在分支限界法中,每个活结点只有一次机会成为扩展结点。一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。
此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所需的解或活结点表为空时为止。
三、算法设计
1、回溯法
TSP问题的目的是得到一条路径,即一个解向量(X1,X2...Xn),为排列树问题。
对所有城市进行编号后,按大小顺序存储于数组path中,构造一个交换函数swap();对数组path进行遍历,判断当前城市与目标城市是否连通,若连通,通过swap函数对当前节点和目标城市进行交换,即树的节点拓展。若不连通则恢复,并进入下一次的循环,循环到叶子节点时,判断叶是否与初始节点相连,并计算代价cost是否小于当前最小代价bestc,若小于,则更新bestc,再返回上一节点,知道遍历完树中的所有节点。
2、分支限界法
因为问题是经典的TSP问题,所以确定问题的解空间树为排列树。
问题解的表示:可以将问题的解表示成一个n元式 [x1,x2,…,xn]。
使用优先级队列实现最小耗费优先求解。
界函数的确定:首先利用贪心的方法获得一个较优的上界。对于当前路径下的扩展的过程中,每一步需要存储的当前的结点的下界。其中的第二部分需要计算的是当前路径的起始结点以及终止结点各自与仍未访问过的结点中的结点只存存在的最小代价。
结点的扩展过程如下:根据优先级从队列中选取优先级最高的元素,当结点不是叶子结点的父节点时,扩展该结点的所有子节点,在扩展的过程中需要根据计算所得的下界与当前求解得到的最优解进行对比,如果下界大于当前的最优解则对相应的子节点时进行剪枝处理,否则扩展该子节点,将其加入到队列中。当当前所访问的结点为叶子结点的父节点时,判断当前费用+当前结点到叶子结点的费
用+叶子结点到起始结点的费用之和是否优于最优解,如果是则更新最优解,并将当前的解加入到队列中。如果不是则继续取队列中的元素进行扩展。但取出的元素为叶子节点或者队列中的元素为空的时候,则搜索结束,输出当前的最优解。
四、算法实现
1、回溯法
核心代码:
public void backtrack(int depth){//depth深度
if(depth==size){
if(map[path[depth-1]][path[depth]] != -1 &&
map[path[depth]][path[1]]!= -1
&& cost +map[path[depth]][path[1]] bestp=path.clone(); bestc = cost + map[path[depth]][path[1]]; //bestc = cost +a[path[i-1]][path[i]]+a[path[i]][path[1]]; //重复计算了上一边 } }else{ for(int j =depth;j<=size;j++){ if(map[path[depth]][path[j]]!=-1){ swap(path,depth+1,j); cost +=map[path[depth]][path[depth+1]]; //System.out.println(Arrays.toString(x)+":"+cost); backtrack(depth+1); cost -=map[path[depth]][path[depth+1]]; swap(path,depth+1,j); } } } } 2、分支限界法 核心代码: public float fzxj(int[]m){ int n=m.length-1;//节点数 LinkedList //minOut[i]=i的最小出边费用 float[]minOut=new float[n+1]; float minSum=0;//最小出边费用和 for(int i=1;i<=n;i++){//针对每个节点,找到最小出边 //计算minOut[i]和minSum float min=Float.MAX_VALUE; for(int j=1;j<=n;j++){ if(map[i][j] min=map[i][j]; } if(min==Float.MAX_VALUE) return Float.MAX_VALUE; minOut[i]=min; minSum+=min; } //初始化 int[]x=new int[n]; for(int i=0;i x[i]=i+1; TSPnode enode=new TSPnode(0,0,minSum,0,x); float bestc=Float.MAX_VALUE; //搜索排列空间树 while(enode!=null&&enode.p //非叶节点 x=enode.path; if(enode.p==n-2){ //当前扩展结点是叶节点的父节点 //再加两条边构成回路 //所构成回路是否优于当前最优解 if(map[x[n-2]][x[n-1]]!=-1&&map[x[n-1]][1]!=-1&&enode.cost+ map[x[n-2]][x[n-1]]+map[x[n-1]][1] //找到费用更小的回路 bestc=enode.cost+map[x[n-2]][x[n-1]]+map[x[n-1]][1]; enode.cost=bestc;