算法笔记_018:旅行商问题(Java)
目录
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}
参考资料:
算法笔记_018:旅行商问题(Java)的更多相关文章
- 学习Java 以及对几大基本排序算法(对算法笔记书的研究)的一些学习总结(Java对算法的实现持续更新中)
Java排序一,冒泡排序! 刚刚开始学习Java,但是比较有兴趣研究算法.最近看了一本算法笔记,刚开始只是打算随便看看,但是发现这本书非常不错,尤其是对排序算法,以及哈希函数的一些解释,让我非常的感兴 ...
- 算法笔记_067:蓝桥杯练习 算法训练 安慰奶牛(Java)
目录 1 问题描述 2 解决方案 1 问题描述 问题描述 Farmer John变得非常懒,他不想再继续维护供奶牛之间供通行的道路.道路被用来连接N个牧场,牧场被连续地编号为1到N.每一个牧场都是 ...
- 算法笔记_041:寻找和为定值的多个数(Java)
目录 1 问题描述 2 解决方案 1 问题描述 输入两个整数n和sum,要求从数列1,2,3,...,n中随意取出几个数,使得它们的和等于sum,请将其中所有可能的组合列出来. 2 解决方案 上述问题 ...
- 算法笔记_023:拓扑排序(Java)
目录 1 问题描述 2 解决方案 2.1 基于减治法实现 2.2 基于深度优先查找实现 1 问题描述 给定一个有向图,求取此图的拓扑排序序列. 那么,何为拓扑排序? 定义:将有向图中的顶点以线性方式进 ...
- 算法笔记_083:蓝桥杯练习 合并石子(Java)
目录 1 问题描述 2 解决方案 1 问题描述 问题描述 在一条直线上有n堆石子,每堆有一定的数量,每次可以将两堆相邻的石子合并,合并后放在两堆的中间位置,合并的费用为两堆石子的总数.求把所有石子 ...
- 算法笔记_075:蓝桥杯练习 最短路(Java)
目录 1 问题描述 2 解决方案 2.1 floyd算法解决 2.2 spfa算法解决 1 问题描述 问题描述 给定一个n个顶点,m条边的有向图(其中某些边权可能为负,但保证没有负环).请你计算从 ...
- 算法笔记_071:SPFA算法简单介绍(Java)
目录 1 问题描述 2 解决方案 2.1 具体编码 1 问题描述 何为spfa(Shortest Path Faster Algorithm)算法? spfa算法功能:给定一个加权连通图,选取一个 ...
- 算法笔记_108:第四届蓝桥杯软件类省赛真题(JAVA软件开发本科A组)试题解答
目录 1 世纪末的星期 2 振兴中华 3 梅森素数 4 颠倒的价牌 5 三部排序 6 逆波兰表达式 7 错误票据 8 带分数 9 剪格子 10 大臣的旅费 前言:以下试题解答代码部分仅供参考,若有不 ...
- 学习笔记(三)--->《Java 8编程官方参考教程(第9版).pdf》:第十章到十二章学习笔记
回到顶部 注:本文声明事项. 本博文整理者:刘军 本博文出自于: <Java8 编程官方参考教程>一书 声明:1:转载请标注出处.本文不得作为商业活动.若有违本之,则本人不负法律责任.违法 ...
随机推荐
- 【51Nod 1363】最小公倍数之和
http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1363 \[ \begin{aligned} &\sum_{i=1 ...
- 【贪心大水题】BZOJ3410-[Usaco2009 Dec]Selfish Grazing 自私的食草者
[题目大意] 给出n个区间,问最多选取多少个区间使得它们互相不重叠. [思路] 水题quq改善心情用.按照右端点大小排序,每次更新上一次的右端点,如果当前左端点大于上次右端点可取. #include& ...
- bzoj 4097: [Usaco2013 dec]Vacation Planning
4097: [Usaco2013 dec]Vacation Planning Description Air Bovinia is planning to connect the N farms (1 ...
- 精通android体系架构、mvc、常见的设计模式、控制反转(ioc)
1.请看某个著名的it公司一则招聘信息的其中一条要求:“熟悉android系统架构及相关技术,1年以上实际android平台开发经验:”,里面非常明确的说道要求熟练android系统架构,这从某种程度 ...
- UESTC 2015dp专题 G 邱老师玩游戏 背包dp
邱老师玩游戏 Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://acm.uestc.edu.cn/#/contest/show/65 Descr ...
- PAT甲级1018. Public Bike Management
PAT甲级1018. Public Bike Management 题意: 杭州市有公共自行车服务,为世界各地的游客提供了极大的便利.人们可以在任何一个车站租一辆自行车,并将其送回城市的任何其他车站. ...
- 在pcDuino上使用蓝牙耳机玩转音乐
1.资源 pcDuino板子一个.HDMI to VGA线一条.电源线一条.USB hub一个.显示器.鼠标.键盘.蓝牙适配器.蓝牙耳机. 2.资源已经到位,让我们开始吧 1.在ubuntu上安装蓝牙 ...
- Mosfet Bi-Directional Switch NMOS PMOS Back to Back
http://www.electronic-products-design.com/geek-area/electronics/mosfets/using-mosfets-as-general-swi ...
- C# 中的回车换行符
在 C# 中,我们用字符串 "\r\n" 表示回车换行符. string str = "第一行\r\n第二行"; 但是我们更推荐 Environment.New ...
- matlab中m文件与m函数的学习与理解
1. m文件与m函数的区别 所谓 MATLAB 程序,大致分为两类: M 脚本文件 (M-Script) 和 M 函数 (M-function), 它们均是普通的 ASCII 码构成的文件. M 脚本 ...