zoukankan      html  css  js  c++  java
  • Java实现旅行商问题

    1 问题描述
    何为旅行商问题?按照非专业的说法,这个问题要求找出一条n个给定的城市间的最短路径,使我们在回到触发的城市之前,对每个城市都只访问一次。这样该问题就可以表述为求一个图的最短哈密顿回路的问题。(哈密顿回路:定义为一个对图的每个顶点都只穿越一次的回路)

    很容易看出来,哈密顿回路也可以定义为n+1个相邻顶点v1,v2,v3,…,vn,v1的一个序列。其中,序列的第一个顶点和最后一个顶点是相同的,而其它n-1个顶点都是互不相同的。并且,在不失一般性的前提下,可以假设,所有的回路都开始和结束于相同的特定顶点。因此,可以通过生成n-1个中间城市的组合来得到所有的旅行线路,计算这些线路的长度,然后求取最短的线路。下图是该问题的一个小规模实例,并用该方法得到了它的解,具体如下:

    2 解决方案
    2.1 蛮力法

    此处使用蛮力法解决旅行商问题,取的是4个城市规模,并已经定义好各个城市之间的距离(PS:该距离使用二维数组初始化定义,此处的距离是根据图1中所示距离定义)。此处主要是在体验使用蛮力法解决该问题的思想,如要丰富成普遍规模问题,还请大家自己稍微修改一下哒。对于代码中如碰到不能理解的地方,可以参考文章末尾给出的参考资料链接,以及相关代码注解~

    具体代码如下:

    package com.liuzhen.chapterThree;
    
    public class TravelingSalesman {
        
        public int count = 0;     //定义全局变量,用于计算当前已行走方案次数,初始化为0
        public int MinDistance = 100;    //定义完成一个行走方案的最短距离,初始化为100(PS:100此处表示比实际要大很多的距离)
        public int[][] distance = {{0,2,5,7},{2,0,8,3},{5,8,0,1},{7,3,1,0}};   //使用二维数组的那个音图的路径相关距离长度
        /*
         * start为开始进行排序的位置
         * step为当前正在行走的位置
         * n为需要排序的总位置数
         * Max为n!值
         */
        public void Arrange(int[] A,int start,int step,int n,int Max){
            if(step == n){   // 当正在行走的位置等于城市总个数时
                ++count;           //每完成一次行走方案,count自增1
                printArray(A);     //输出行走路线方案及其总距离
            }
            if(count == Max)
                System.out.println("已完成全部行走方案!!!,最短路径距离为:"+MinDistance);  
            else{
                for(int i = start;i < n;i++){   
                    /*第i个数分别与它后面的数字交换就能得到新的排列,从而能够得到n!次不同排序方案
                     * (PS:此处代码中递归的执行顺序有点抽象,具体解释详见本人另一篇博客:)  
                     *算法笔记_017:递归执行顺序的探讨(Java)
    */
                    swapArray(A,start,i);
                    Arrange(A,start+1,step+1,n,Max);
                    swapArray(A,i,start);
                }
            }
        }
        
        //交换数组中两个位置上的数值
        public  void swapArray(int[] A,int p,int q){
            int temp = A[p];
            A[p] = A[q];
            A[q] = temp;
        }
        
        //输出数组A的序列,并输出当前行走序列所花距离,并得到已完成的行走方案中最短距离
        public void printArray(int[] A){
            for(int i = 0;i < A.length;i++)   //输出当前行走方案的序列
                System.out.print(A[i]+"  ");
            
            int tempDistance = distance[A[0]][A[3]];     //此处是因为,最终要返回出发地城市,所以总距离要加上最后到达的城市到出发点城市的距离
            for(int i = 0;i < (A.length-1);i++)   //输出当前行走方案所花距离
                tempDistance += distance[A[i]][A[i+1]];
            
            if(MinDistance > tempDistance)   //返回当前已完成方案的最短行走距离
                MinDistance = tempDistance;
            
            System.out.println("  行走路程总和:"+tempDistance);
        }
        
        public static void main(String[] args){
            int[] A = {0,1,2,3};
            TravelingSalesman test = new TravelingSalesman();
            test.Arrange(A,0,0,4,24);    //此处Max = 4!=24
        }
    }
    

    运行结果:

    1  2  3    行走路程总和:18
     1  3  2    行走路程总和:11
     2  1  3    行走路程总和:23
     2  3  1    行走路程总和:11
     3  2  1    行走路程总和:18
     3  1  2    行走路程总和:23
     0  2  3    行走路程总和:11
     0  3  2    行走路程总和:18
     2  0  3    行走路程总和:23
     2  3  0    行走路程总和:18
     3  2  0    行走路程总和:11
     3  0  2    行走路程总和:23
     1  0  3    行走路程总和:18
     1  3  0    行走路程总和:23
     0  1  3    行走路程总和:11
     0  3  1    行走路程总和:23
     3  0  1    行走路程总和:18
     3  1  0    行走路程总和:11
     1  2  0    行走路程总和:23
     1  0  2    行走路程总和:11
     2  1  0    行走路程总和:18
     2  0  1    行走路程总和:11
     0  2  1    行走路程总和:23
     0  1  2    行走路程总和:18
    已完成全部行走方案!!!,最短路径距离为:11
    

    2.2 减治法
    旅行商问题的核心,就是求n个不同城市的全排列,通俗一点的话,就是求1n的全排列。下面两种方法都是基于减治思想进行的,此处只实现求取1n的全排列。对于每一种排列,在旅行商问题中还得求取其相应路径长度,最后,在进行比较从而得到最短路径,对于求取最短路径的思想在2.1蛮力法中已经体现,此处不在重复,感兴趣的同学可以自己再动手实现一下~

    2.2.1 Johson-Trotter算法
    此处算法思想借用《算法设计与分析基础》第三版上讲解,具体如下:

    在这里插入图片描述

    在这里插入图片描述

    package com.liuzhen.chapter4;
    
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Iterator;
    
    public class Arrange {
        //使用JohnsonTrotter算法获取1~n的全排列
        public HashMap<Integer , String> getJohnsonTrotter(int n){
            HashMap<Integer , String> hashMap = new HashMap<Integer , String>();
            int count = 0;                //用于计算生成排列的总个数,初始化为0
            int[] arrayN = new int[n];
            int[] directionN = new int[n+1];      //directionN[i]用于标记1~n中数字i上的箭头方向,初始化值为0,表示箭头方向向左;值为1 表示箭头方向向右
            for(int i = 0;i < n;i++)
                arrayN[i] = i+1;
            String result = getArrayString(arrayN);
            hashMap.put(count, result);        //将原始排列添加到哈希表中
            while(judgeMove(arrayN,directionN)){      //存在一个移动元素
                int maxI = getMaxMove(arrayN,directionN);
                if(directionN[arrayN[maxI]] == 0)      //箭头指向左方
                    swap(arrayN,maxI,--maxI);
                if(directionN[arrayN[maxI]] == 1)       //箭头指向右方
                    swap(arrayN,maxI,++maxI);
                for(int i = 0;i < n;i++){               //调转所有大于arrayN[maxI]的数的箭头方向
                    if(arrayN[i] > arrayN[maxI]){
                        if(directionN[arrayN[i]] == 0)
                             directionN[arrayN[i]] = 1;
                        else
                            directionN[arrayN[i]] = 0;
                    }
                }
                count++;
                result = getArrayString(arrayN);
                hashMap.put(count, result);        //将得到的新排列添加到哈希表中
            }
            return hashMap;
        }
        //判断数组arrayN中是否存在可移动元素
        public boolean judgeMove(int[] arrayN,int[] directionN){
            boolean judge = false;
            for(int i = arrayN.length - 1;i >= 0;i--){
                if(directionN[arrayN[i]] == 0 && i != 0){     //当arrayN[i]数字上的箭头方向指向左边时
                    if(arrayN[i] > arrayN[i-1])
                        return true;
                }
                if(directionN[arrayN[i]] == 1 && i != (arrayN.length-1)){    //当arrayN[i]数字上的箭头方向指向右边时
                    if(arrayN[i] > arrayN[i+1])
                        return true;
                }
            }
            return judge;
        }
        //获取数组arrayN中最大的可移动元素的数组下标
        public int getMaxMove(int[] arrayN,int[] directionN){
            int result = 0;
            int temp = 0;
            for(int i = 0;i < arrayN.length;i++){
                if(directionN[arrayN[i]] == 0 && i != 0){     //当arrayN[i]数字上的箭头方向指向左边时
                    if(arrayN[i] > arrayN[i-1]){
                        int max = arrayN[i];
                        if(max > temp)
                            temp = max;
                    }
                }
                if(directionN[arrayN[i]] == 1 && i != (arrayN.length-1)){    //当arrayN[i]数字上的箭头方向指向右边时
                    if(arrayN[i] > arrayN[i+1]){
                        int max = arrayN[i];
                        if(max > temp)
                            temp = max;
                    }    
                }
            }
            for(int i = 0;i < arrayN.length;i++){
                if(arrayN[i] == temp)
                    return i;
            }
            return result;
        }
        //交换数组中两个位置上的数值
        public void swap(int[] array,int m,int n){
            int temp = array[m];
            array[m] = array[n];
            array[n] = temp;
        }
        //把数组array中所有元素按照顺序以字符串结果返回
        public String getArrayString(int[] array){
            String result = "";
            for(int i = 0;i < array.length;i++)
                result = result + array[i];
            return result;
        }
        
        public static void main(String[] args){
            Arrange test = new Arrange();
            HashMap<Integer , String> hashMap = test.getJohnsonTrotter(3);
            Collection<String> c1 = hashMap.values();
            Iterator<String> ite = c1.iterator();
            while(ite.hasNext())
                System.out.println(ite.next());
            System.out.println(hashMap);
            
        }
    }
    

    运行结果:

    132
    321
    213
    {0=123, 1=132, 2=312, 3=321, 4=231, 5=213}
    

    2.2.2 基于字典序的算法
    此处算法思想也借用《算法设计与分析基础》第三版上讲解,具体如下:

    在这里插入图片描述

    package com.liuzhen.chapter4;
    
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Iterator;
    
    public class Arrange1 {
        
        public HashMap<Integer,String> getLexicographicPermute(int n){
            HashMap<Integer,String> hashMap = new HashMap<Integer,String>();
            int count = 0;         //用于计算生成排列的总个数,初始化为0
            int[] arrayN = new int[n];
            for(int i = 0;i < n;i++)
                arrayN[i] = i+1;
            String result = getArrayString(arrayN);
            hashMap.put(count, result);        //将原始排列添加到哈希表中
            while(riseTogetherArray(arrayN)){     //数组中存在两个连续的升序元素
                int i = getMaxI(arrayN);     //找出使得ai<ai+1的最大i: ai+1>ai+2>...>an
                int j = getMaxJ(arrayN);     //找到使得ai<aj的最大索引j: j>=i,因为ai<ai+1
                swap(arrayN,i,j);
                reverseArray(arrayN,i+1,arrayN.length-1);
                result = getArrayString(arrayN);
                count++;
                hashMap.put(count, result);        //将新得到的排列添加到哈希表中
            }
            System.out.println("排列总个数count = "+(count+1));
            return hashMap;
        }
        //判断数组中是否 包含两个连续的升序元素
        public boolean riseTogetherArray(int[] arrayN){
            boolean result = false;
            for(int i = 1;i < arrayN.length;i++){
                if(arrayN[i-1] < arrayN[i])
                    return true;
            }
            return result;
        }
        //返回i:满足ai<ai+1,ai+1>ai+2>...>an(PS:an为数组中最后一个元素)
        public int getMaxI(int[] arrayN){
            int result = 0;
            for(int i = arrayN.length-1;i > 0;){
                if(arrayN[i-1] > arrayN[i])
                    i--;
                else
                    return i-1;
            }
            return result;
        }
        //返回j:ai<aj的最大索引,j>=i+1,因为ai<ai+1(此处i值为上面函数getMaxI得到值)
        public int getMaxJ(int[] arrayN){
            int result = 0;
            int tempI = getMaxI(arrayN);
            for(int j = tempI+1;j < arrayN.length;){
                if(arrayN[tempI] < arrayN[j]){
                    if(j == arrayN.length-1)
                        return j;
                    j++;
                }
                else
                    return j-1;
            }
            return result;
        }
        //交换数组中两个位置上的数值
        public void swap(int[] array,int m,int n){
            int temp = array[m];
            array[m] = array[n];
            array[n] = temp;
        }
        //将数组中a[m]到a[n]一段元素反序排列
        public void reverseArray(int[] arrayN,int m,int n){
            while(m < n){
                int temp = arrayN[m];
                arrayN[m++] = arrayN[n];
                arrayN[n--] = temp;
            }
        }
        //把数组array中所有元素按照顺序以字符串结果返回
        public String getArrayString(int[] array){
            String result = "";
            for(int i = 0;i < array.length;i++)
                result = result + array[i];
            return result;
        }
        
        public static void main(String[] args){
             Arrange1 test = new  Arrange1();
             HashMap<Integer,String> hashMap = test.getLexicographicPermute(3);
             Collection<String> c1 = hashMap.values();
             Iterator<String> ite = c1.iterator();
             while(ite.hasNext())
                System.out.println(ite.next());
             System.out.println(hashMap);
        }
    }
    

    运行结果:

    排列总个数count = 6
    132
    231
    321
    {0=123, 1=132, 2=213, 3=231, 4=312, 5=321}
    
  • 相关阅读:
    SAP S/4HANA extensibility扩展原理介绍
    SAP CRM系统订单模型的设计与实现
    使用nodejs代码在SAP C4C里创建Individual customer
    SAP Cloud for Customer Account和individual customer的区别
    Let the Balloon Rise map一个数组
    How Many Tables 简单并查集
    Heap Operations 优先队列
    Arpa’s obvious problem and Mehrdad’s terrible solution 思维
    Passing the Message 单调栈两次
    The Suspects 并查集
  • 原文地址:https://www.cnblogs.com/a1439775520/p/13078000.html
Copyright © 2011-2022 走看看