依靠队列搜索——分支限界法及其应用:最大团问题、货郎问题

人生是需要做好规划的,就算是混这人生也要混出个名堂出来。
全国有大部分人知道考研,但是考出来干什么,走什么的路是真的没有想法。
计算机行业涉及到许许多多的领域,所以,大学期间要学计算机专业,一定要做好规划,想做什么才能去学什么。囫囵吞枣只会让你啥也没有学到,说明白点就只是让你明白个大概,而不能明白所以然。
所以要定好方向,,将来想干什么,然后在这条路上学懂专业,这样到了社会,才能给你这一席之地。

上一篇我们说了回溯法的限界思想,回溯法是一个基于深度优先搜索的算法,限界思想只是回溯法的剪枝操作。而这种思想是在分支限界法中提出来的,当然分支限界法一样是可以用剪枝操作的。

本篇主要介绍分支限界法及分支限界法的两大经典问题:最大团问题以及货郎问题

1、分支限界法

什么是分支限界法

分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树

在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。

此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所需的解或活结点表为空时为止。

正如标题所言,分支限界法是依靠队列进行搜索的。也就是说,分支限界法会用到队列,需要队列辅助来进行广度优先搜索。

分支限界法的两种方法

(1)队列式(FIFO)分支限界法
按照队列先进先出(FIFO)原则选取下一个节点为扩展节点。
(2)优先队列式分支限界法
按照优先队列中规定的优先级选取优先级最高的节点成为当前扩展节点。

分支限界法的搜索策略

在当前节点(扩展节点)处,先生成其所有的子节点(分支),然后再从当前的活节点(当前节点的子节点)表中选择下一个扩展节点。为了有效地选择下一个扩展节点,加速搜索的进程,在每一个活节点处,计算一个函数值(限界),并根据函数值,从当前活节点表中选择一个最有利的节点作为扩展节点,使搜索朝着解空间上有最优解的分支推进,以便尽快地找出一个最优解。分支限界法解决了大量离散最优化的问题。

对于分支限界法,我们来看两个经典例子:最大团问题和货郎问题

2、最大团问题

什么是最大团问题

给定一个无向图G=<V,E>,求G的最大团

我们首先来看两个概念:
如果从G中取了一部分点和一部分边,其中V’⊆V,E’⊆E。由这样顶点集和边集构成的图是原来图的子图。
因此有G的子图:G’=<V’,E’>,其中V’⊆V,E’⊆E

如果一个图有着跟G相同的点,而且这些点之间有边相连当且仅当在G里面他们没有边相连。那么这样的图是G的补图

当G的子图是一个完全子图(点与其他各点都有边相连的子图就是完全子图)的时候,称这个图是G中的团;而最大团是完全子图中顶点数最多的团

我们来看个例子:

我们看到1,3,4,5的点与边组成的图中

它们的点与其他各点都分别有边相连,而且它们又是原图的子图。故是一个团;然而顶点2与顶点3,5没有连接,所以顶点2将被抛去。故1,3,4,5是一个最大团。

最大团的应用有:编码、故障诊断、计算机视觉、聚类分析、经济学、移动通信、VLSI电路设计等。
下面我们用回溯法与分支限界法来解最大团问题

回溯法解最大团问题

首先对问题进行建模:
给定无向图G=<V,E>,其中顶点集V={1,2,3,…,n},边集为E,求G中的最大团
问题的解是<x1,x2,x3,…,xn>为0-1向量,xk=1当且仅当顶点k属于最大团。

我们看到对于这个问题的解是0,1向量的,因此最大团问题是一棵子集树。

我们首先确定约束条件:该顶点与当前团内每个顶点都有边相连

我们用当前已检索到的最大团的顶点数当作界

而对于代价函数的考虑就是目前的团可能扩张为极大团的顶点数上界
F=Cn+n-k,其中Cn为目前团的顶点数,k是结点层数。
也就是说代价函数就是当前团顶点数+总顶点数-第t个顶点=当前团总点数+剩余未考察的结点数
然而这个代价函数是一个粗略的代价函数,它裁掉的空间会比较少。

而子集树画法,大家可以结合最优装载问题的画法来画出最大团问题。注意代价函数与界的考虑,因为这里是极大化问题,所以F>B进行分支,而F<=B就可以裁掉,不用再往下画了
具体画法就不再画了

下面我们进行代码分析:
最大团问题是一棵子集树,所以代码逻辑编写就可以仿照最优装载问题那样进行编写。

首先到达叶结点,考虑当前团是否大于最大团,若大于则记录最优解和更新最大团个数

没有到达叶结点,则首先判断是否满足约束条件:选择的点和点集中的点是否是两两相连,若满足则纳入该点,进行递归。紧接着进入右子树,进入右子树前,将刚才纳入的点抛弃掉,进入右子树去递归。别忘了在进入右子树之前要增加一个限界判定,即F>B。也就是当前团总点数+剩余未考察的结点数>当前最大团的顶点数。

大家思考下如何判断选择的点和点集中的点是否是两两相连。那就是从每一个点到第t个点进行检索,看看每个点是否在团里面且查一下是否有边,如果在有一个点在团里面并且与这个第t个点没有边连接,那么这个第t个点是不能纳入当前团里面的,设置成flag=false。
下面上代码

public static int numPoint;  //当前顶点数
	public static int bestPoint=-0x3f3f3f3f;  //当前最大顶点数
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//设置是否连线:自己和自己连的是0,没有边的是-1,其他的都是1
		int a[][]=new int[][] {
			{0,1,1,1,1},
			{1,0,-1,1,-1},
			{1,-1,0,1,1},
			{1,1,1,0,1},
			{1,-1,1,1,0}
		};
		
		int x[]=new int[a.length];
		int bestx[]=new int[a.length];
		
		MaximumcliqueProblem(a, x, bestx, 0);
		CreateNode createNode=new CreateNode(a);
		System.out.print("最大团点集是:");
		for(int i=0;i<bestx.length;i++)
			if(bestx[i]==1)
				System.out.print(i+1+" ");
		
		System.out.println("最大团顶点数为:"+bestPoint);
		
	}

	//回溯法与限界思想求解最大团问题
	public static void MaximumcliqueProblem(int a[][],int x[],int bestx[],int t) {
		//到达叶节点
		if(t==a.length) {  //如果到达了叶节点,判断当前顶点数>最大顶点数
			if(numPoint>bestPoint) {  //如果大于,就记录最优解,然后把numPoint赋给bestPoint
				for(int i=0;i<bestx.length;i++)
					bestx[i]=x[i];
				bestPoint=numPoint;
			}
			return;
		}
		
		//没有到达叶节点,首先判断选择的点和点集中的点是否是两两相连
		Boolean flag=true;
		for(int j=0;j<t;j++) 
			//如果存在两个点(另外一个点必须在选择的点集里)没有边连接。则这个点不应该纳入点集里
			if(x[j]==1&&a[t][j]!=1) {
				flag=false;
				break;
			}
		//若flag为true,则纳入该点到点集里
		if(flag) {
			x[t]=1;
			numPoint++;
			MaximumcliqueProblem(a, x, bestx, t+1);
			numPoint--;
		}
		
		//设置代价函数:当前团顶点数+总顶点数-第t个顶点>界的值
		if(numPoint+a.length-t+1>bestPoint) {
			//如果大于则递归进入右子树,否则进行剪枝操作
			x[t]=0;
			MaximumcliqueProblem(a, x, bestx, t+1);
		}
	}

然后我们思考用分支限界法解最大团问题

分支限界法解最大团问题

我们将用基于优先队列的分支限界法

分支限界法要有三个基本类:活结点类、结点属性类(如果是基于优先队列的话,里面要有根据界进行排序)、入队类(添加节点到队列里面)

结点属性类是要包括:活结点类、界、与问题有关的当前解、深度

首先定义活结点类:父结点和左子树结点。

定义一个活结点属性类,根据问题将类中的属性有活结点类、当前最大顶点数上界、当前顶点数、深度,还有一个根据上界的排序

再设置一个入队类,定义队列和将点集添加到队列的方法

以上就是分支限界法的准备

下面先思考结点属性的界应该定义什么?
这个界是和限界思想的界不一样的,结点属性的界是由这个结点计算出来的可能产生的最大团顶点数,例如问题中先考虑顶点1的话,如果纳入顶点1,那么这个界就是包含这个点的最大团顶点的个数n,也就是所有点;如果不纳入顶点1,那就是舍弃这个点的最大团顶点的个数n-1。

推广一下,就是和代价函数差不多。就是如果纳入第k个点,那么由结点计算出来的界就是
当前团的顶点数+n-k+1,注意:这个1纳入该点的情况;当然如果不纳入第k个点,那么就是
当前团的顶点数+n-k,这就是不包含第k个点的情况。

为什么要这么算?
你从第1个点开始搜索,要准备搜索第2个点的时候,是要生成第2个点的数据,换句话说就是要把第2个点的信息要添加到队列里面去。所以说我算的界算的是在第2个点可能出现的最大团顶点数。如果这里实在理解不了,就跳过。就记住,这个界就是纳入或者不纳入该点,对于下一个点可能产生的最大团顶点数

然后再介绍一下活结点的父结点和左子树结点。在代码中是把父结点的信息传给分支出来子结点,也就是子结点保存父结点的信息。如果是根节点则父结点是null。这样做就是为了回溯。可以沿着树枝逐步向上回溯所有的结点信息。

那么左子树结点是干什么的呢?这个是为了求解需要的,你想想,纳入该点说明走的是左子树;因为子集树中不是走左子树就是走右子树,就是非0即1的现象。因此,纳入该点就让左子树为true;否则就让左子树为false。

所有信息介绍完了之后,代码编写就很简单了。

如果没有到达叶结点,那就先判断约束条件,如果满足约束条件,则将结点纳入,并添加到队列里面;再考虑右子树的时候,应用限界思想直接将结点添加到队列里。然后由队列的出队方法,遍历下一个结点。

这里为什么说是广度优先搜索?为什么说基于优先队列考虑?
当我们遍寻一个结点的时候,是同时将结点的左右分支添加到队列里面去了。然后基于界的降序排序,也就是说先考虑结点的界比较大的。而结点的界比较大的一般都是在同一层。

所以说通过队列的先进先出原则可以达到广度优先搜索;而依赖于对结点的界进行排序,可以先确定原问题的最大上界,这样就是基于优先队列考虑。

如果大家对分支限界法代码分析看不懂,就直接去看代码。有不会的再在底下评论吧。毕竟分支限界的代码就是比较繁琐。

下面上代码

public class GraphNodes {
	GraphNodes parents;  //父结点
	boolean leftchild;  //左子树
	
	public GraphNodes() {
		// TODO Auto-generated constructor stub
	}

	public GraphNodes(GraphNodes parents, boolean leftchild) {
		super();
		this.parents = parents;
		this.leftchild = leftchild;
	}
	
	
}

public class HeapNodes implements Comparable{
	GraphNodes nodes;
	int upBound;  //当前最大顶点数上界
	int numpoint;  //当前顶点数
	int depth;  //深度
	
	public HeapNodes() {
		// TODO Auto-generated constructor stub
	}
	
	
	public HeapNodes(GraphNodes nodes, int upBound, int numpoint, int depth) {
		super();
		this.nodes = nodes;
		this.upBound = upBound;
		this.numpoint = numpoint;
		this.depth = depth;
	}


	//将上界值降序排序,目的就是基于广度遍历,将同一界(同一层)的先遍历
	@Override
	public int compareTo(Object o) {
		// TODO Auto-generated method stub
		int i=((HeapNodes)o).upBound;
		if(upBound<i) return 1;
		else if(upBound==i) return 0;
		return -1;
	}
}

import java.util.Collections;
import java.util.LinkedList;

public class CreateNode {
	int a[][];  //创建图的邻接矩阵
	static LinkedList<HeapNodes> list;  //创建优先队列
	
	public CreateNode() {
		// TODO Auto-generated constructor stub
	}

	public CreateNode(int[][] a) {
		super();
		this.a = a;
		list=new LinkedList<HeapNodes>();
	}
	
	//添加结点
	public static void addNode(int upBound,int numpoint,int depth,GraphNodes nodes,boolean leftchild) {
		GraphNodes graphNodes=new GraphNodes(nodes, leftchild);  //实例化图结点
		HeapNodes heapNodes=new HeapNodes(graphNodes, upBound, numpoint, depth);
		list.add(heapNodes);  //将结点信息添加进去
		Collections.sort(list);
	}
}

public static int numPoint;  //当前顶点数
	public static int bestPoint=-0x3f3f3f3f;  //当前最大顶点数
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//设置是否连线:自己和自己连的是0,没有边的是-1,其他的都是1
		int a[][]=new int[][] {
			{0,1,1,1,1},
			{1,0,-1,1,-1},
			{1,-1,0,1,1},
			{1,1,1,0,1},
			{1,-1,1,1,0}
		};
		
		int bestx[]=new int[a.length];
		
		CreateNode createNode=new CreateNode(a);
		MaximumcliqueProblem2(a, bestx, 0);
		
		System.out.print("最大团点集是:");
		for(int i=0;i<bestx.length;i++)
			if(bestx[i]==1)
				System.out.print(i+1+" ");
		
		System.out.println("最大团顶点数为:"+bestPoint);
		
	}

//分支限界法求解最大团问题
	public static void MaximumcliqueProblem2(int a[][],int bestx[],int t) {
		GraphNodes nodes=null;
		//搜索子集空间树
		while(t!=a.length) {  //如果没有到达叶子结点,先由该结点追溯到左子树中leftchild是否为true(结点是否在点集中)
			boolean flag=true;
			GraphNodes nodes2=nodes;
			for(int i=t-1;i>=0;i--) {  //遍寻前面遍历的点集,因为是向上追溯,所以i从t-1开始与第t个点是否连接
			    if(nodes2.leftchild&&a[t][i]!=1) {  //左孩子已经为true,说明已经在点集里了。然后和第t个点判断是否连接
			    	flag=false;
			    	break;
			    }
			    nodes2=nodes2.parents;  //进行完判断之后回溯父结点
			}
			
			//如果flag为true,则将其结点纳入点集中,如果numPoint+1>bestPoint,则更新解
			if(flag) {
				CreateNode.addNode(numPoint+a.length-t, numPoint+1, t+1, nodes, true);
				if(numPoint+1>bestPoint)
					bestPoint=numPoint+1;
			}
			
			//考虑右子树:广度遍历,直接将右子树结点添加到队列中去
			//设置代价函数:当前顶点数+总顶点数-第t个顶点>界的值
			if(numPoint+a.length-t+1>bestPoint)
				CreateNode.addNode(numPoint+a.length-t-1, numPoint, t+1, nodes, false);
			
			//广度遍历找到下一个点
			//poll():弹起队列中第一个值并删掉
			HeapNodes heapNodes=CreateNode.list.poll();
			nodes=heapNodes.nodes;  //取下一个结点
			numPoint=heapNodes.numpoint;  //取下一个的当前顶点数
			t=heapNodes.depth;  //取下一层结点
		}
		//构造最优解
		for(int j=a.length-1;j>=0;j--) {
			//如果结点的leftchild为true,说明结点可以纳入最优解中
			bestx[j]=nodes.leftchild?1:0;
			//寻求父结点
			nodes=nodes.parents;
		}
	}
最大团问题分析

结合子集树和代码能够得出时间复杂度是O(n2^n)。

以上就是最大团问题的内容

3、货郎问题

什么是货郎问题

货郎问题又被叫做货郎担问题、旅行售货员问题

输入有穷个城市的集合C={c1,c2,…,cn},距离d(ci,cj)=d(cj,ci)∈Z+,1<=i<j<=n
解:1,2,3,…,n的排列k1,k2,…,kn使得:
翻译过来就是:有一个旅行商从一个去城市旅游,任何两个城市之间有不一样的距离。现在从第1个城市出发,将所有城市旅行一遍后再回到第一个城市。问题的解是寻求一个更好的排列顺序,使得走的路径最短。

下面举个例子:

我们看到从每个结点之间都有距离,下面寻求一挑条从结点1出发一条可以游览所有城市的最短回路

经过计算能够找到1->2->4->3->1的这一条回路,不仅包含了所有城市,而且是可行解中最短的路径

下面用回溯法和分支限界法来解货郎问题

回溯法解货郎问题

因为这是一个排列问题,走过第一条路,就要从剩下的路里面进行选择,所以货郎问题的搜索空间是一棵排列树。

约束条件:如果令B表示前k个城市已经排列好了,那么k+1个城市的选择就是从未考虑的城市集合里面进行选择。

我们可以把当前得到的最短巡回路线的长度作为界

代价函数:
我们将已经选定好的k个城市的路线加起来的值l1
然后再找到下一条从第k个城市出发的最短边长l2
最后再分别找到剩下未考虑过的点到所有点(除自己之外)最短距离之和l3

代价函数就是l1+l2+l3
比如我们看到这个图

从1出发来计算代价函数。
我们先看到由于还没有走,也就是还在原点位置上,即l1=0
我们寻找一条从顶点1出发的最短边长,由图中可以看到<1,4>=4,即l2=4
最后我们要从剩下未选择的点中,分别找到这些点与所有点的最短距离,然后求和
由图中可以看到
从顶点2出发的<2,4>=2
从顶点3出发的<3,4>=7
从顶点4出发的<4,2>=2
l3=2+7+2=11
所以代价函数就是11+4+0=15

当然这是个极小化问题,所以要让F<B。

为什么要这样计算l1和l2想必没什么问题吧,l3不仅要考虑剩下未走过的最短路径,还要考虑一种情况就是回路的情况。所以结点和1路径情况是也要考虑进去的。

而这样选择代价函数是整个路径的最短情况,就是说不会再有一种情况比这个还要短。如果F>=B,说明剩下考虑的路段,就再也没有可以更新最短路径的情况了。

这样,我们就可以画出原问题的排列树了

下面我们看下代码分析:
排列树的代码逻辑,我们已经在圆排列问题提到过一次了。
首先看是否到达叶子结点
如果到达叶子结点,则判断是否可以更新解

如果没有到达,则用for循环进行遍历
如果两个点有边且代价函数<界,如果我们要确定第k个位置放哪个城市,就把城市编号和k进行交换,这样就将城市放到了你考虑的第k个位置上了。

当然最后别忘了再换回去,因为下一步要搜索另一个分支。

限界函数的写法,可以参考上面的介绍进行编写

具体代码如下:

public static int cl;  //当最线路长度
	public static int minl=0x3f3f3f3f;  //找到的最短路径
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int n=4;  //城市数
		//设置路线长度:自己和自己连的是0,没有边的是-1,其他的都是自己长度
		int a[][]=new int[][] {
			{0,5,9,4},
			{5,0,13,2},
			{9,13,0,7},
			{4,2,7,0}
			};
			
		int x[]=new int[n];  //存储当前路径
		int bestx[]=new int[n];  //记录当前最优解
		
		for(int i=0;i<n;i++)
			x[i]=i;
		
		ReBackTravelling2(a, x, bestx, 1);
		
		System.out.print("最短路径是:");
		
		if(minl>0) 
		for(int i=0;i<bestx.length;i++)
			System.out.print((bestx[i]+1)+"->");
		System.out.println(bestx[0]+1);
		
		System.out.println("最短路径长度是:"+minl);
	}

//交换函数
	public static void swap(int x[],int i,int j) {
		int temp;
		temp=x[i];
		x[i]=x[j];
		x[j]=temp;
	}
	
	//设定代价函数
	public static int Bound(int a[][],int x[],int t) {
		int min1=0x3f3f3f3f,min2=0x3f3f3f3f,tempsum=0;
		//代价函数:(cl+标号t-1与标号t~n-1的最短长度+剩下标号与所有点距离最短的路径之和)<界的值
		for(int i=t;i<a.length;i++) {
			if(a[x[t-1]][x[i]]>0&&a[x[t-1]][x[i]]<min1)
				min1=a[x[t-1]][x[i]];
			for(int j=0;j<a.length;j++)
				if(a[x[i]][x[j]]>0&&a[x[i]][x[j]]<min2)
					min2=a[x[i]][x[j]];
			tempsum+=min2;
		}
		return cl+min1+tempsum;
	}
	
	//回溯法与限界思想解货郎问题
	public static void ReBackTravelling2(int a[][],int x[],int bestx[],int t) {
		if(t==a.length) {  //到达叶节点
			if((a[x[t-1]][0]>0)&&(cl+a[x[t-1]][0]<minl)) {
				for(int j=0;j<x.length;j++)
					bestx[j]=x[j];
				minl=cl+a[x[t-1]][0];
			}
			return;
		}
		
		//如果没有到达叶节点
		for(int i=t;i<a.length;i++) {  //调用Bound()进剪枝操作
			if((a[x[t-1]][x[i]]>0)&&(cl+a[x[t-1]][x[i]]<minl)&&Bound(a, bestx, t)<minl) {  //计算标号是t-1到标号i的路线上
				//交换下标位置,将i与t互换,使下一条要选择的标号放到t的位置上
				swap(x,i,t);
				cl+=a[x[t-1]][x[t]];  //因为下一个要选择到标号已经到了t的位置上,自然t-1到标号i换成了t-1到标号t
				ReBackTravelling(a, x, bestx, t+1);
				cl-=a[x[t-1]][x[t]];
				swap(x,i,t);  //进入下一层循环,下一层循环是进入右子树。首先将加过的长度还回去,然后将刚刚交换的路线标号,重新回到原位置
			}
		}
	}
分支限界法解货郎问题

关于分支限界法如何求解,和上边的最大团问题差不多一样。这里就不再介绍了,大家可以参考最大团问题和回溯法解货郎问题的思路来用分支限界法编写货郎问题。

下面直接上代码

public class BBTSP {

	float[][] a;//图G的邻接矩阵

	public BBTSP(float[][] a){

		this.a=a;

	}

	public static class HeapNode implements Comparable{

		float lcost;//子树费用的下界

		float cc;//当前费用

		float rcost;//x[s:n-1]中顶点最小出边费用和

		int s;//根节点到当前节点的路径为x[0:s]

		int[] x;//需要进一步搜索的顶点是x[s+1:n-1]

		

		//构造方法

		public HeapNode(float lc,float ccc,float rc,int ss,int[] xx){

			lcost=lc;

			cc=ccc;

			s=ss;

			x=xx;

		}

		public int compareTo(Object x){

			float xlc=((HeapNode) x).lcost;

			if(lcost<xlc) return -1;

			if(lcost==xlc) return 0;

			return 1;

		}

	}

	

	public float bbTsp(int[] v){

		int n=v.length-1;//节点数

		LinkedList<HeapNode> heap=new LinkedList<HeapNode>();

		//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(a[i][j]<Float.MAX_VALUE&&a[i][j]<min)

					min=a[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<n;i++)

			x[i]=i+1;

		HeapNode enode=new HeapNode(0,0,minSum,0,x);

		float bestc=Float.MAX_VALUE;

		

		//搜索排列空间树

		while(enode!=null&&enode.s<n-1){

			//非叶节点

			x=enode.x;

			if(enode.s==n-2){

				//当前扩展结点是叶节点的父节点

				//再加两条边构成回路

				//所构成回路是否优于当前最优解

				if(a[x[n-2]][x[n-1]]!=-1&&a[x[n-1]][1]!=-1&&enode.cc+a[x[n-2]][x[n-1]]+a[x[n-1]][1]<bestc){

					//找到费用更小的回路

					bestc=enode.cc+a[x[n-2]][x[n-1]]+a[x[n-1]][1];

					enode.cc=bestc;

					enode.lcost=bestc;

					enode.s++;

					heap.add(enode);

					Collections.sort(heap);

				}

			}else{//内部结点

				//产生当前扩展结点的儿子结点

				for(int i=enode.s+1;i<n;i++){

					if(a[x[enode.s]][x[i]]!=-1){

						//可行儿子结点

						float cc=enode.cc+a[x[enode.s]][x[i]];

						float rcost=enode.rcost=minOut[x[enode.s]];

						float b=cc+rcost;//下界

						if(b<bestc){

							//子树可能含有最优解,结点插入最小堆

							int[] xx=new int[n];

							for(int j=0;j<n;j++)

								xx[j]=x[j];

							xx[enode.s+1]=x[i];

							xx[i]=x[enode.s+1];

							HeapNode node=new HeapNode(b,cc,rcost,enode.s+1,xx);

							heap.add(node);

							Collections.sort(heap);

						}

					}

				}

				

			}

			

			//取下一个扩展结点

			enode=heap.poll();

		}

		//将最优解复制到v[1...n]

		for(int i=0;i<n;i++)

			v[i+1]=x[i];

		return bestc;

	}

	public static void main(String[] args) {

		//int n=4;

		//float[][] a={{0,0,0,0,0},{0,-1,30,6,4},{0,30,-1,5,10},{0,6,5,-1,20},{0,4,10,20,-1}};//a下标从1开始,0用来凑数;-1表示不同,1表示连通

		int n=5;

		float[][] a={{0,0,0,0,0,0},{0,-1,5,-1,7,9},{0,5,-1,10,3,6},{0,-1,10,-1,8,-1},{0,7,3,8,-1,4},{0,9,6,-1,4,-1}};//a下标从1开始,0用来凑数;-1表示不同,1表示连通

		BBTSP b=new BBTSP(a);

		int []v=new int[n+1];

		System.out.println("最短回路长为:"+b.bbTsp(v));

		System.out.print("最短回路为:");

		for(int i=1;i<=n;i++){

			System.out.print(v[i]+" ");

		}

	}

}
货郎问题的分析

因为货郎问题的搜索空间是一棵排列树,所以通过搜索空间和代码分析得出的时间复杂度就是O(n!)

以上就是货郎问题的内容

                 凡事预则立,不预则废《礼记·中庸》
  • 12
    点赞
  • 75
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值