博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
算法笔记_018:旅行商问题(Java)
阅读量:5270 次
发布时间:2019-06-14

本文共 8360 字,大约阅读时间需要 27 分钟。

目录

 


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:此处代码中递归的执行顺序有点抽象,具体解释详见本人另一篇博客:) *
*/                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    行走路程总和:180  1  3  2    行走路程总和:110  2  1  3    行走路程总和:230  2  3  1    行走路程总和:110  3  2  1    行走路程总和:180  3  1  2    行走路程总和:231  0  2  3    行走路程总和:111  0  3  2    行走路程总和:181  2  0  3    行走路程总和:231  2  3  0    行走路程总和:181  3  2  0    行走路程总和:111  3  0  2    行走路程总和:232  1  0  3    行走路程总和:182  1  3  0    行走路程总和:232  0  1  3    行走路程总和:112  0  3  1    行走路程总和:232  3  0  1    行走路程总和:182  3  1  0    行走路程总和:113  1  2  0    行走路程总和:233  1  0  2    行走路程总和:113  2  1  0    行走路程总和:183  2  0  1    行走路程总和:113  0  2  1    行走路程总和:233  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
getJohnsonTrotter(int n){ HashMap
hashMap = new HashMap
(); 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
hashMap = test.getJohnsonTrotter(3); Collection
c1 = hashMap.values(); Iterator
ite = c1.iterator(); while(ite.hasNext()) System.out.println(ite.next()); System.out.println(hashMap); }}

运行结果:

123132312321231213{
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
getLexicographicPermute(int n){ HashMap
hashMap = new HashMap
(); 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+2>...>an int j = getMaxJ(arrayN); //找到使得ai
=i,因为ai
< array.length;i++) result = result + array[i]; return result; } public static void main(String[] args){ Arrange1 test = new Arrange1(); HashMap
hashMap = test.getLexicographicPermute(3); Collection
c1 = hashMap.values(); Iterator
ite = c1.iterator(); while(ite.hasNext()) System.out.println(ite.next()); System.out.println(hashMap); }}

运行结果:

排列总个数count = 6123132213231312321{
0=123, 1=132, 2=213, 3=231, 4=312, 5=321}

 

 

 参考资料:

        1. 

 

转载于:https://www.cnblogs.com/liuzhen1995/p/6371481.html

你可能感兴趣的文章
iOS 项目的编译速度提高
查看>>
机房收费系统——报表
查看>>
How to unshelve many shelves at same time
查看>>
table中checkbox选择多行
查看>>
动态链接库
查看>>
Magento开发文档(三):Magento控制器
查看>>
使用Docker官方的Django包【转】
查看>>
SuperSocket 学习
查看>>
给培训学校讲解ORM框架的课件
查看>>
此实现不是 Windows 平台 FIPS 验证的加密算法的一部分
查看>>
性能调优攻略
查看>>
线段树模板讲解
查看>>
ie6解决png图片透明问题
查看>>
瞬间的永恒
查看>>
docker overlay网络实现
查看>>
2019-8-5 考试总结
查看>>
jquery javascript 回到顶部功能
查看>>
JS中实现字符串和数组的相互转化
查看>>
用格式工厂将mts文件转换成其它格式flv,mpg失败
查看>>
web service和ejb的区别
查看>>