• 欧几里得旅行商问题 java与c++实现


    双调欧几里得旅行商问题是一个经典动态规划问题。《算法导论(第二版)》思考题15-1

    旅行商问题描述:平面上n个点,确定一条连接各点的最短闭合旅程。这个解的一般形式为NP的(在多项式时间内可以求出)

    J.L. Bentley 建议通过只考虑双调旅程(bitonictour)来简化问题,这种旅程即为从最左点开始,严格地从左到右直至最右点,然后严格地从右到左直至出发点。下图(b)显示了同样的7个点的最短双调路线。在这种情况下,多项式的算法是可能的。事实上,存在确定的最优双调路线的O(n*n)时间的算法。

    上图中,a是最短闭合路线,这个路线不是双调的。b是最短双调闭合路线


    以上截取来自:http://blog.csdn.net/xiajun07061225/article/details/8092247



    /**
     * 欧几里得旅行商(travelling salesman problem)问题
     *   分解模型:
     *        一个人从Pi严格递减的走到P1,然后再严格递增的回到Pj,求总路径的最小值,其中P1,P2,...,Pj的
     *        所有节点都要走一次,求最短的路径;
     *        对于1 <= i <= j <= n, 我们定义P(i, j)是一条包含了P1, P2, P3 ...Pj的途径
     *        这条路径可以分成2部分:递减序列与递增序列:起点是Pi(1 <= i <= j),拐点是P1,
     *        终点是Pj, P[i, j]为其最小值;路径为(Pi-_>Pm-->P1-->Pn-->Pj,其中1<m<i<n<j) 
     *   动态规划方程:
     *   	  p[i,j]为最小值,dist(m,n)为m与n的直接距离
     *        p[1,2]=dist(1,2)
     *        i<j-1时,从P(i+1)到Pj之间的点必然出现在递增序列中
     *              p[i,j]=p[i,j-1]+dist(j-1,j)
     *        i=j-1时,假设P1到Pi之间的一个中间节点Pk,使得p[k,i]+dist(k,j)最小,也就是说,找到了
     *              将P1到Pj的所有节点走一遍的最短路径
     *              p[i,j]=min{p[k,i]+dist(k,j)}(其中1<=k<=i)
     *        p[k,i]又可以按照以上方式继续找出最小路径
     *        
     *        对于欧几里得旅行商问题,要求的最短路径是p[n,n]
     *        p[n,n]=p[n-1,n]+dist(n-1,n);
     *       
     *   
     */
    </pre><pre name="code" class="html">

    以下是C++实现,来自:http://blog.csdn.net/xiajun07061225/article/details/8092247

    #include <iostream>
    #include <cmath>
    #include <iomanip>
    
    using namespace std;
    
    const int n = 7;//点的数目
    const int MaxVal = 999999;
    const int MaxLen = 201;
    
    struct tagPoint{
    	double x,y;
    };
    
    //计算点i和点j之间的直线距离
    double distance(tagPoint *points,int i,int j)
    {
    	return sqrt((points[i].x - points[j].x) * (points[i].x - points[j].x) +
    		(points[i].y - points[j].y) * (points[i].y - points[j].y));
    }
    
    double DP(tagPoint *points,int n)
    {
    	double b[MaxLen][MaxLen];//记录最短路径的长度
    	//计算所有情况下的b[i][j],1 <= i <= j
    	//初始化
    	b[1][2] = distance(points,1,2);
    	for (int j = 3;j <= n;++j)
    	{
    		//i < j-1
    		for (int i = 1;i <= j - 2;++i)
    		{
    			b[i][j] = b[i][j - 1] + distance(points,j - 1,j);
    		}
    		//i = j - 1,b[i][j] = min(b[k][j - 1] + distance(k,j));
    		b[j - 1][j] = MaxVal;
    		for (int k = 1;k <= j - 2;++k)
    		{
    			double temp = b[k][j - 1] + distance(points,k,j);
    			if (temp < b[j - 1][j])
    			{
    				b[j - 1][j] = temp;
    			}
    		}
    	}
    
    	b[n][n] = b[n - 1][n] + distance(points,n - 1,n);
    
    	return b[n][n];
    }
    
    int main()
    {
    	int NUM;
    	while(cin >> NUM)
    	{
    		tagPoint *points = new tagPoint[NUM + 1];
    		for (int i = 1;i <= NUM;++i)
    		{
    			cin >> points[i].x;
    			cin >> points[i].y;
    		}
    		double minDis = DP(points,NUM);
    		//设置输出格式:精确到小数点后2位
    		cout.setf(ios::fixed);
    		cout << setprecision(2) << minDis << endl;
    	}
    }


    以下是java实现,基本思想是一样的。

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.util.Scanner;
    
    import org.omg.CORBA.PRIVATE_MEMBER;
    
    /**
     * @date 2014-09-20
     * @author acer
     * 欧几里得旅行商(travelling salesman problem)问题
     *   分解模型:
     *        一个人从Pi严格递减的走到P1,然后再严格递增的回到Pj,求总路径的最小值,其中P1,P2,...,Pj的
     *        所有节点都要走一次,求最短的路径;
     *        对于1 <= i <= j <= n, 我们定义P(i, j)是一条包含了P1, P2, P3 ...Pj的途径
     *        这条路径可以分成2部分:递减序列与递增序列:起点是Pi(1 <= i <= j),拐点是P1,
     *        终点是Pj, P[i, j]为其最小值;路径为(Pi-_>Pm-->P1-->Pn-->Pj,其中1<m<i<n<j) 
     *   动态规划方程:
     *   	  p[i,j]为最小值,dist(m,n)为m与n的直接距离
     *        p[1,2]=dist(1,2)
     *        i<j-1时,从P(i+1)到Pj之间的点必然出现在递增序列中
     *              p[i,j]=p[i,j-1]+dist(j-1,j)
     *        i=j-1时,假设P1到Pi之间的一个中间节点Pk,使得p[k,i]+dist(k,j)最小,也就是说,找到了
     *              将P1到Pj的所有节点走一遍的最短路径
     *              p[i,j]=min{p[k,i]+dist(k,j)}(其中1<=k<=i)
     *        p[k,i]又可以按照以上方式继续找出最小路径
     *        
     *        对于欧几里得旅行商问题,要求的最短路径是p[n,n]
     *        p[n,n]=p[n-1,n]+dist(n-1,n);
     *        解释:Pn-->Pn,如果第一个点是Pn-1,那么上面的式子显然成立;
     *        	      如果第一个点是Pk,(1<=k<=n-2),那么Pn-1一定在Pk-->Pn的递增序列中,所以
     *            p[n,n]=p[k,n]+dist(k,n)=p[k,n-1]+dist(n-1,n)+dist(k,n);
     *            其中p[n-1,n]=p[k,n-1]+dist(k,n)
     *            所以 p[n,n]=p[n-1,n]+dist(n-1,n)必然成立;
     *       
     *   
     */
    public class TSProblem {
    
    	
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		String  line=null;//读取每一行的输入数据
    		int[][] pointXY;//记录横纵坐标
    		
    		System.out.print("请输入点的个数N:");
    		InputStreamReader isr=new InputStreamReader(System.in);
    		BufferedReader bReader=new BufferedReader(isr);
    		try {
    			line=bReader.readLine().toString();
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		int n=Integer.parseInt(line);//记录点的个数
    		pointXY=new int[n][2];
    		
    		System.out.println("请输入每个点的坐标,坐标格式为:x,y  (以EOF结束输入)");
    		Scanner scn=new Scanner(System.in);
    		int i=0;
    		while(!"EOF".equals(line=scn.nextLine())){
    			String[] p=line.split(",");
    			int j=0;
    			for(String str:p){
    				pointXY[i][j++]=Integer.parseInt(str);			
    			}
    			i++;
    			if(i>=n){
    				System.out.println("输入的点数已经足够,自动结束输入");
    				break;//退出输入
    			}
    			
    		}
    		
    		//System.out.println("ok");
    		int length=pointXY.length;
    		TSProblem tsp=new TSProblem(pointXY,length);
    		
    		
    		
    
    	}
    	
    	public TSProblem(int[][] points,int length){
    		//points中存储所有的节点,乱序排列
    		//1、首先将所有的节点按照x坐标顺序排列,可选用多种排序方式,这里采用快排,时间复杂度为o(nlogn)	
    		points=this.QuickSort(points, 0, length-1);
    //		//打印排序后的点
    //		for(int i=0;i<length;i++){
    //			System.out.println(points[i][0]+","+points[i][1]);
    //		}
    		
    		//2、找到最短路径
    		double leastDis=DP(points, length);
    		
    		System.out.println("最短路径为:"+leastDis);
    		
    		
    		
    	}
    	
    	
    	//计算最短旅行路径
    	double DP(int[][] points,int length){
    		
    		//p用于记录最短路径的长度
    		double[][] p=new double[length][length];
    		p[0][1]=dist(points, 0, 1);
    		for(int j=2;j<length;j++){
    			//case1:i<j-1
    			// p[i,j]=p[i,j-1]+dist(j-1,j)
    			for(int i=0;i<j-1;i++){
    				p[i][j]=p[i][j-1]+dist(points, i, j);
    			}
    			
    			//case2:i=j-1
    			// p[i,j]=min{p[k,i]+dist(k,j)}(其中1<=k<=i)
    			p[j-1][j]=Float.MAX_VALUE;
    			double temp=0;
    			for(int k=0;k<j-1;k++){
    				temp=p[k][j-1]+dist(points, k, j);
    				if(temp<p[j-1][j]){
    					p[j-1][j]=temp;
    				}
    			}
    			
    			
    		}
    		p[length-1][length-1]=p[length-2][length-1]+dist(points, length-2, length-1);
    		
    		//返回Pn-->Pn,即最短路径
    		return p[length-1][length-1];
    		
    	}
    	
    	//计算两点之间的直接距离dist(i,j)
    	double dist(int[][] point ,int i,int j){
    		double distance=0;
    		distance=Math.sqrt(Math.pow((point[j][0] -point[i][0]),2)
    				           +Math.pow((point[j][1]-point[i][1]),2));
    		
    		return distance;		
    	}
    	
    	//快排
    	int[][] QuickSort(int[][] num,int left,int right){
    		/**
    		 * 1、先从数列中取出一个数作为基数
    		 * 2、大数置于该数的右边,小数置于该数的左边
    		 * 3、重复步骤1,2
    		 */
    		if(left<right){
    			int tempX=num[left][0];//设置基数,不唯一
    			int tempY=num[left][1];
    			int i=left,j=right;
    			while(i<j){
    				while(i<j&&num[j][0]>=tempX)
    					j--;	//右边的数小于基数时退出
    				if(i<j){
    					//如果在右边找到一个数小于基数,num[i]此时为空,将该数填到num[i]
    					num[i][0]=num[j][0];
    					num[i][1]=num[j][1];
    					i++;
    				}
    				while(i<j&&num[i][0]<tempX)
    					i++;	//左边的数大于基数时退出
    				if(i<j){
    					//如果在左边找到一个数大于基数,num[j]此时为空,将该数填到num[j]
    					num[j][0]=num[i][0];
    					num[j][1]=num[j][1];
    					j--;
    				}
    							
    			}//当i=j时退出
    			//num[i]为空值,将基数填入
    			num[i][0]=tempX;
    			num[i][1]=tempY;
    			
    			QuickSort(num, left, i-1);//左边快排
    			QuickSort(num, i+1, right);//右边快排		
    			
    		}
    		//返回已经排好序的数组
    		return num;	
    	}
    	
    }
    
    
    








    
       
    
    
  • 相关阅读:
    软件工程第二次作业
    第一次作业
    理论物理特训-02
    左神-06 二叉树
    哲学通论之人-05(end)
    哲学通论之人-04
    左神-05 二分搜索(多看几遍)
    哲学通论之人-01
    航空母舰-04(end)
    题解 P3126 【[USACO15OPEN]回文的路径Palindromic Paths】
  • 原文地址:https://www.cnblogs.com/wennian/p/5036925.html
Copyright © 2020-2023  润新知