由于旅行商问题是最小化问题,所以我们需要找下界。
这里我们每次选择最佳下界的节点进行扩展,用到最小堆。
开始的时候,我们这样计算下界lb:对于每一个城市i,求出从城市i到最近的两个城市的距离之和si,求出总和s
每次加入一条新的边,我们需要考虑把这条边放进去。对于这条边涉及到的顶点,我们要把这条边算入这两个顶点最近的两个城市间的距离。比如我们加入边a-d,那么a的两个城市有一个必须是d,d的两个城市有一个必须是a。
我们用序列来表示已经处理的边,(如a, a-b, a-b-c, a-b-c-d)
注意顶点分三种情况:
-
1.没有在序列里面,直接加min(最小)和min2(第二小)
-
2.在序列边上(开头或结尾)说明只有一条边纳入
如果这条边正好是该顶点最小的边,那就这条边+第二小。
如果不是,就这条边+最小。 -
3.在序列内部,那就直接加两条边。
在实际处理过程中,我们可以用迭代方式来做,对于某一序列a-b-c-d,我们加入一个顶点e,形成a-b-c-d-e,注意到
d由序列边上到内部,e由序列外部进入序列内部。
对于d,我们把上次d的两条边的长度删掉(由于不知道是多少,所以我们要在上一次的时候就记下来,省的费劲再算),把这次d的两条边的长度加上。
对于e,我们删掉e的min和min2,把这次e的两条边的长度加上。
其他的也同理,除了第一次和最后一次特殊处理,只需要关心这两个点的改变。
为了方便实用min和min2,我们可以在开始的时候就计算出来。
最后求出来了再/2得到结果
首先是Node代码
package df;
import java.util.ArrayList;
public class Node {
ArrayList<Integer>arr=new ArrayList<Integer>();
int lb;//这里没有/2取上整,而是直接用
int last_number;//指序列最后一个顶点的两条边的数据
int set[];//为1表示已纳入
public Node(ArrayList<Integer> arr, int lb, int last_number,int[]set) {
super();
this.arr = arr;
this.lb = lb;
this.last_number = last_number;
this.set=set;
}
}
下面是TSP代码
package df;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.PriorityQueue;
public class TSP {
public static void set_edge(int m[][],int i,int value,int j)
{
m[i][j]=value;
m[j][i]=value;
}
public static Node tsp_tree(int m[][])
{
int n=m.length;
//小顶堆
PriorityQueue<Node>pq=new PriorityQueue<Node>(new Comparator<Node>() {
@Override
public int compare(Node n1,Node n2) {
if (n1.lb > n2.lb) {return 1;}
if (n1.lb < n2.lb) {return -1;}
return 0;
}}
);
//min_index[i]指i连接的最小边的另一个顶点
//min2_index[i]指i连接的第二小边的另一个顶点
//本来应该初始化为-1,不过由于下面一定会改变,
//所以不用初始化了
int min_index[]=new int[n];
int min2_index[]=new int[n];
//我们要同时记录两个最小值的位置就行,最小值可以用矩阵直接求
int min,min2;
for(int i=0;i<n;i++)
{
min=Integer.MAX_VALUE;
min2=Integer.MAX_VALUE;
for(int j=0;j<n;j++)
if(m[i][j]<min)
{
min2=min;
min=m[i][j];
min2_index[i]=min_index[i];
min_index[i]=j;
}
else if(m[i][j]<min2)
{
min2=m[i][j];
min2_index[i]=j;
}
}
/*注意分三种情况:
* 1.没有在已处理里面,直接加min和min2
* 2.在已处理里面,有一条边被处理
* 如果这条边正好是最小值,那就这条边+第二小
* 如果不是,就这条边+第一小
* 3.两条边都被处理,那就直接加两条边
*/
//为了方便,假设从0开始,且1(b)在2(c)前面
ArrayList<Integer> arr=new ArrayList<Integer>();
arr.add(0);
int lb=0;
for(int i=0;i<n;i++)
{
lb+=m[i][min_index[i]];
lb+=m[i][min2_index[i]];
}
int last_number=0;
last_number+=m[0][min_index[0]];
last_number+=m[0][min2_index[0]];
int set[]=new int[n];
set[0]=1;
Node node0=new Node(arr,lb,last_number,set);
pq.add(node0);
int max=Integer.MAX_VALUE;
//可以乱找一个值作为初始answer,只要lb足够大
Node answer=new Node(arr,max,last_number,set);
while(pq.size()!=0)
{
Node node=pq.poll();
//if(Math.ceil(node.lb/2.0)<answer.lb)
if(node.lb<answer.lb)
{
for(int i=0;i<n;i++)
{
if(node.set[i]==0)
{
if(node.set[1]!=0||i!=2)
//1在2前面
{
//每次计算lb,只需要看上次最后一个顶点、本次加入的顶点
//假设路径还没有填充完整
lb=node.lb;
//处理第一个顶点
int need=node.arr.get(node.arr.size()-1);
if(node.arr.size()==1)
{//need==0,只包含0
if(min_index[need]==i)
;
else
{
//最小值抵消了
//lb=lb-m[0][min_index[0]]-m[0][min2_index[0]];
//lb=lb+m[0][min_index[0]]+m[i][0];
lb=lb-m[need][min2_index[need]]+m[i][need];
}
}
else
{
lb=lb-node.last_number;
lb=lb+m[node.arr.get(node.arr.size()-2)][need]
+m[need][i];
}
//处理第二个顶点
if(need==min_index[i])
{
//lb加减所以不变
last_number=m[i][min_index[i]]+m[i][min2_index[i]];
}
else
{
//最小值抵消了
//lb=lb-m[i][min_index[i]]-m[i][min2_index[i]];
//lb=lb+m[i][min_index[i]]+m[i][need];
lb=lb-m[i][min2_index[i]]+m[i][need];
last_number=m[i][min_index[i]]+m[i][need];
}
arr=new ArrayList<Integer>();
arr.addAll(node.arr);
arr.add(i);
set=node.set.clone();
set[i]=1;
//if说明已经确定了,最后没有加入的点直接加入,然后再放上起点
if(arr.size()==n-1)
{
int j;
//检查set,加入最后一个点j,以及起点0
for( j=0;j<n;j++)
if(set[j]==0)
break;
set[j]=1;
need=arr.get(arr.size()-1);
lb=lb-last_number;
lb=lb+m[arr.get(arr.size()-2)][need]
+m[need][j];
lb=lb-m[j][min_index[j]]-m[j][min2_index[j]];
lb=lb+m[need][j]+m[j][0];
//下面要处理有关0的线段
if(arr.get(1)==min_index[0])
{
lb=lb-m[0][min2_index[0]];
lb=lb+m[j][0];
}
else
{
lb=lb-m[0][min_index[0]];
lb=lb+m[j][0];
}
arr.add(j);
arr.add(0);
if(lb<answer.lb)
answer=new Node(arr,lb,last_number,set);
}
else
{
Node no=new Node(arr,lb,last_number,set);
pq.add(no);
}
}
}
}
}
}
return answer;
}
public static void main(String[] args) {
int [][]m=new int[5][5];
for(int i=0;i<m.length;i++)
m[i][i]=Integer.MAX_VALUE;
set_edge(m,0,3,1);
set_edge(m,0,1,2);
set_edge(m,0,5,3);
set_edge(m,0,8,4);
set_edge(m,1,6,2);
set_edge(m,1,7,3);
set_edge(m,1,9,4);
set_edge(m,2,4,3);
set_edge(m,2,2,4);
set_edge(m,3,3,4);
Node answer=tsp_tree(m);
//可以除以2得到路径长度
System.out.println(answer.lb/2);
}
}