分支定界解决旅行商问题

由于旅行商问题是最小化问题,所以我们需要找下界。
这里我们每次选择最佳下界的节点进行扩展,用到最小堆。
开始的时候,我们这样计算下界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得到结果

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)
			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);
		

	}

}

你可能感兴趣的:(java,算法)