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}

Java实现旅行商问题的更多相关文章

  1. Spark案例分析

    一.需求:计算网页访问量前三名 import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} /* ...

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

    双调欧几里得旅行商问题是一个经典动态规划问题.<算法导论(第二版)>思考题15-1 旅行商问题描述:平面上n个点,确定一条连接各点的最短闭合旅程.这个解的一般形式为NP的(在多项式时间内可 ...

  3. 算法笔记_018:旅行商问题(Java)

    目录 1 问题描述 2 解决方案 2.1 蛮力法 2.2 减治法 2.2.1 Johson-Trotter算法 2.2.2 基于字典序的算法   1 问题描述 何为旅行商问题?按照非专业的说法,这个问 ...

  4. 模拟退火算法SA原理及python、java、php、c++语言代码实现TSP旅行商问题,智能优化算法,随机寻优算法,全局最短路径

    模拟退火算法SA原理及python.java.php.c++语言代码实现TSP旅行商问题,智能优化算法,随机寻优算法,全局最短路径 模拟退火算法(Simulated Annealing,SA)最早的思 ...

  5. Java基础常见英语词汇

    Java基础常见英语词汇(共70个) ['ɔbdʒekt] ['ɔ:rientid]导向的                             ['prəʊɡræmɪŋ]编程 OO: object ...

  6. 百度之星资格赛——Disk Schedule(双调旅行商问题)

    Disk Schedule Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) To ...

  7. 基于粒子群算法求解求解TSP问题(JAVA)

    一.TSP问题 TSP问题(Travelling Salesman Problem)即旅行商问题,又译为旅行推销员问题.货郎担问题,是数学领域中著名问题之一.假设有一个旅行商人要拜访n个城市,他必须选 ...

  8. 基于贪心算法求解TSP问题(JAVA)

    概述 前段时间在搞贪心算法,为了举例,故拿TSP来开刀,写了段求解算法代码以便有需之人,注意代码考虑可读性从最容易理解角度写,没有优化,有需要可以自行优化! 详细 代码下载:http://www.de ...

  9. 专业英语词汇(Java)

    abstract (关键字)             抽象 ['.bstr.kt] access                            vt.访问,存取 ['.kses]‘(n.入口, ...

随机推荐

  1. SpringData:关联查询

    一.查询方式 1.导航式查询 使用“对象.属性”   进行查询:对于多的查询, 默认就是延迟加载,添加注解@Transactional 在OneToMany 注解中需要添加属性   fetch:值:F ...

  2. PAT 1006 Sign In and Sign Out (25分) 字符串比较

    题目 At the beginning of every day, the first person who signs in the computer room will unlock the do ...

  3. 初试stm32嵌入式开发遇到的巨坑

    开发板使用的是st官方的stm32F207ZG nucleo,遇到的问题是在keil中报错: no target connected 到网上找答案,都是说gpio口的问题,让按着reset键改debu ...

  4. 这道LeetCode题究竟有什么坑点,让它的反对是点赞的9倍?

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是LeetCode专题的第38篇文章,我们一起来看看第65题,Valid Number. 曾经我们聊到过算法当中的一个类别--模拟题.所 ...

  5. Spark_Transformation和Action算子

    Transformation 和 Action 常用算子 ​ 一.Transformation        1.1 map        1.2 filter        1.3 flatMap  ...

  6. JDBC基本使用方法

    JDBC基本使用方法 JDBC固定步骤: 加载驱动 String url="jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true& ...

  7. [原创][开源] SunnyUI.Net 开发日志:ListBox 增加跟随鼠标滑过高亮

    QQ群里,寸目说,ListBox鼠标移动时,当前行需要焦点,我想了想,不难实现啊 不就是在鼠标移动时重绘Item嘛,何况选中的Item已经改了颜色了. 见UIListBox代码: protected ...

  8. 201771010120 苏浪浪 《面向对象程序设计(java)》第11周学习总结

    实验十一   集合 1.实验目的与要求 (1) 掌握Vetor.Stack.Hashtable三个类的用途及常用API: (2) 了解java集合框架体系组成: (3) 掌握ArrayList.Lin ...

  9. [!] Unable to find a pod with name, author, summary, or description matching `AFNetworking`

    大量的答案是删除~/Library/Caches/CocoaPods/search_index.json  没有起作用 有用答案: https://blog.csdn.net/qq_35827461/ ...

  10. poj3249 拓扑找最长路

    Test for Job Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 11230   Accepted: 2651 Des ...