目录

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. 【算法设计与分析基础】蛮力法解决旅行商问题

算法笔记_018:旅行商问题(Java)的更多相关文章

  1. 学习Java 以及对几大基本排序算法(对算法笔记书的研究)的一些学习总结(Java对算法的实现持续更新中)

    Java排序一,冒泡排序! 刚刚开始学习Java,但是比较有兴趣研究算法.最近看了一本算法笔记,刚开始只是打算随便看看,但是发现这本书非常不错,尤其是对排序算法,以及哈希函数的一些解释,让我非常的感兴 ...

  2. 算法笔记_067:蓝桥杯练习 算法训练 安慰奶牛(Java)

    目录 1 问题描述 2 解决方案   1 问题描述 问题描述 Farmer John变得非常懒,他不想再继续维护供奶牛之间供通行的道路.道路被用来连接N个牧场,牧场被连续地编号为1到N.每一个牧场都是 ...

  3. 算法笔记_041:寻找和为定值的多个数(Java)

    目录 1 问题描述 2 解决方案 1 问题描述 输入两个整数n和sum,要求从数列1,2,3,...,n中随意取出几个数,使得它们的和等于sum,请将其中所有可能的组合列出来. 2 解决方案 上述问题 ...

  4. 算法笔记_023:拓扑排序(Java)

    目录 1 问题描述 2 解决方案 2.1 基于减治法实现 2.2 基于深度优先查找实现 1 问题描述 给定一个有向图,求取此图的拓扑排序序列. 那么,何为拓扑排序? 定义:将有向图中的顶点以线性方式进 ...

  5. 算法笔记_083:蓝桥杯练习 合并石子(Java)

    目录 1 问题描述 2 解决方案   1 问题描述 问题描述 在一条直线上有n堆石子,每堆有一定的数量,每次可以将两堆相邻的石子合并,合并后放在两堆的中间位置,合并的费用为两堆石子的总数.求把所有石子 ...

  6. 算法笔记_075:蓝桥杯练习 最短路(Java)

    目录 1 问题描述 2 解决方案 2.1 floyd算法解决 2.2 spfa算法解决   1 问题描述 问题描述 给定一个n个顶点,m条边的有向图(其中某些边权可能为负,但保证没有负环).请你计算从 ...

  7. 算法笔记_071:SPFA算法简单介绍(Java)

    目录 1 问题描述 2 解决方案 2.1 具体编码   1 问题描述 何为spfa(Shortest Path Faster Algorithm)算法? spfa算法功能:给定一个加权连通图,选取一个 ...

  8. 算法笔记_108:第四届蓝桥杯软件类省赛真题(JAVA软件开发本科A组)试题解答

     目录 1 世纪末的星期 2 振兴中华 3 梅森素数 4 颠倒的价牌 5 三部排序 6 逆波兰表达式 7 错误票据 8 带分数 9 剪格子 10 大臣的旅费 前言:以下试题解答代码部分仅供参考,若有不 ...

  9. 学习笔记(三)--->《Java 8编程官方参考教程(第9版).pdf》:第十章到十二章学习笔记

    回到顶部 注:本文声明事项. 本博文整理者:刘军 本博文出自于: <Java8 编程官方参考教程>一书 声明:1:转载请标注出处.本文不得作为商业活动.若有违本之,则本人不负法律责任.违法 ...

随机推荐

  1. POJ1741 Tree 树分治模板

    http://poj.org/problem?id=1741   题意:一棵n个点的树,每条边有距离v,求该树中距离小于等于k的点的对数.   dis[y]表示点y到根x的距离,v代表根到子树根的距离 ...

  2. python开发_tkinter

    Tkinter模块("Tk 接口")是Python的标准Tk GUI工具包的接口.Tk和Tkinter可以在大多数的Unix平台下使用, 同样可以应用在Windows和Macint ...

  3. PAT甲级1107. Social Clusters

    PAT甲级1107. Social Clusters 题意: 当在社交网络上注册时,您总是被要求指定您的爱好,以便找到一些具有相同兴趣的潜在朋友.一个"社会群体"是一群拥有一些共同 ...

  4. GNU诞生三十周年

    1983年9月27日,MIT人工智能实验室的Richard Stallman在新闻组宣布了雄 心勃勃的GNU(Gnu's Not Unix)操作系统计划,他计划创造一个Unix兼容的自由软件系统,包含 ...

  5. SQLiteOpenHelper 操作不成功

    SDK和ADT为22.6.2版本号 project为4.4.2 今天在练习SQLiteOpenHelper里,使用的是三个JAVA文件操作.DatabaseHelper.java,Const.java ...

  6. Hbulider 支持less保存自动编译

    设置less配置:文件写 .less,路径找lessc.cmd,找不到就下载,命令参数写:%FileName% %FileBaseName%.css这样以后就可以less文件自动生成css了!

  7. ou have not concluded your merge (MERGE_HEAD exists)

    今天获取git线上仓库代码报了这个错误: zhangzhi@moke:~/code/ktsg-api$ git pull You have not concluded your merge (MERG ...

  8. End of Life check fails with NullPointerException

    Checks if the running version of JIRA is approaching, or has reached End of Life. Details Type: Bug ...

  9. 实现windows操作系统和VB下Linux虚拟操作系统相互传取文件方式总结

    在windows上执行虚拟机跑的是Linux的操作系统,怎样才干在不同的操作系统之间传递文件呢? 这是本人切身体会到的,假设你没有好的方法的话.确实非常痛苦.下面是我个人的方法总结: 方法一.很好用的 ...

  10. Appium+python自动化15-在Mac上环境搭建

    前言 mac上搭建appium+python的环境还是有点复杂的,需要准备的软件 1.nodejs 2.npm 3.cnpm 4.appium 5.pip 6.Appium-Python-Client ...