算法筆記_018:旅行商問題(Java)


目錄

1 問題描述

2 解決方案

2.1 蠻力法

2.2 減治法

2.2.1 Johson-Trotter算法

2.2.2 基於字典序的算法

 


1 問題描述

何為旅行商問題?按照非專業的說法,這個問題要求找出一條n個給定的城市間的最短路徑,使我們在回到觸發的城市之前,對每個城市都只訪問一次。這樣該問題就可以表述為求一個圖的最短哈密頓回路的問題。(哈密頓回路:定義為一個對圖的每個頂點都只穿越一次的回路)

 

很容易看出來,哈密頓回路也可以定義為n+1個相鄰頂點v1,v2,v3,...,vn,v1的一個序列。其中,序列的第一個頂點和最后一個頂點是相同的,而其它n-1個頂點都是互不相同的。並且,在不失一般性的前提下,可以假設,所有的回路都開始和結束於相同的特定頂點。因此,可以通過生成n-1個中間城市的組合來得到所有的旅行線路,計算這些線路的長度,然后求取最短的線路。下圖是該問題的一個小規模實例,並用該方法得到了它的解,具體如下:

 

圖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
    }
}

 

運行結果:

0  1  2  3    行走路程總和:18
0  1  3  2    行走路程總和:11
0  2  1  3    行走路程總和:23
0  2  3  1    行走路程總和:11
0  3  2  1    行走路程總和:18
0  3  1  2    行走路程總和:23
1  0  2  3    行走路程總和:11
1  0  3  2    行走路程總和:18
1  2  0  3    行走路程總和:23
1  2  3  0    行走路程總和:18
1  3  2  0    行走路程總和:11
1  3  0  2    行走路程總和:23
2  1  0  3    行走路程總和:18
2  1  3  0    行走路程總和:23
2  0  1  3    行走路程總和:11
2  0  3  1    行走路程總和:23
2  3  0  1    行走路程總和:18
2  3  1  0    行走路程總和:11
3  1  2  0    行走路程總和:23
3  1  0  2    行走路程總和:11
3  2  1  0    行走路程總和:18
3  2  0  1    行走路程總和:11
3  0  2  1    行走路程總和:23
3  0  1  2    行走路程總和:18
已完成全部行走方案!!!,最短路徑距離為:11

 

2.2 減治法

旅行商問題的核心,就是求n個不同城市的全排列,通俗一點的話,就是求1~n的全排列。下面兩種方法都是基於減治思想進行的,此處只實現求取1~n的全排列。對於每一種排列,在旅行商問題中還得求取其相應路徑長度,最后,在進行比較從而得到最短路徑,對於求取最短路徑的思想在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);
        
    }
}

運行結果:

123
132
312
321
231
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
123
132
213
231
312
321
{0=123, 1=132, 2=213, 3=231, 4=312, 5=321}

 

 

 參考資料:

        1. 【算法設計與分析基礎】蠻力法解決旅行商問題

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM