要求:
TSP 算法(Traveling Salesman Problem)是指给定 n 个城市和各个城市之间的距离,要
求确定一条经过各个城市当且仅当一次的最短路径,它是一种典型的优化组合问题,其最优
解得求解代价是指数级的。TSP 问题代表一类优化组合问题,在实际工程中有很多应用,如
计算机联网、电子地图、交通诱导等,具有重要的研究价值。遗传算法和禁忌搜所算法都是
是一种智能优化算法,具有全局的优化性能、通用性强。这种算法一般具有严密的理论依据,
理论上可以在一定的时间内找到最优解或近似最优解,广泛应用于计算机科学优化调度问题、
组合优化问题。通过阅读书籍以及科技文献,研究遗传算法或 禁忌搜索算法的基本原理,
研究TSP 问题并提出常见解决方案。要求在此基础上,编程实现以智能优化算法来解决 TSP
问题,并给出相应实验结果和算法分析。
遗传算法:
package org.wucl.ga;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.wucl.City;
/**
* 遗传算法解决TSP问题
*
* 1.个体编码方案 整数编码:对城市进行编号,每个城市分别用0到n-1之间不同的整数表示,n个整数的一个排列就代表了旅行商问题的一个可能解
*
* 2.交配方法 常规交配法,交配概率为100%
*
* 3.变异方法 打乱变异,使用逆序方式,变异概率为10%
*
* 4.新种群构成的方法 用轮盘赌求出子代种群
*
* 5.算法结束的条件 当代数打到200时,结束算法
*
* @author wucl(lailaiwcl@163.com)
*
*/
public class Genetic {
List<City> vec = new ArrayList<City>();
int n = 0;
int num;
double optimal; // 记录最优解
int best[] = new int[n]; // 记录最终路径
public void getStrategy() {
try {
Reader reader = new InputStreamReader(Genetic.class
.getClassLoader().getResourceAsStream("data.txt"));
BufferedReader br = new BufferedReader(reader);
String line = br.readLine();
while (line != null) {
String[] sa = line.split(" ");
vec.add(new City(sa[0], Double.parseDouble(sa[1]), Double
.parseDouble(sa[2])));
line = br.readLine();
}
n = vec.size();
} catch (IOException e) {
e.printStackTrace();
}
num = 4 * n * n; // 初始个体数为4*n*n;
int a[][] = new int[num][n];
for (int i = 0; i < num; i++) // 随机生成num个个体
{
a[i] = random(n, n);
}
optimal = getValue(a[0])[1];
for (int i = 0; i < 200; i++) {
a = nextGen(a);
}
System.out.print("最佳路径:");
for (int i = 0; i < n - 1; i++)
System.out.print(vec.get(best[i]).name + "->");
System.out.println(vec.get(best[n - 1]).name);
System.out.println("最佳长度:" + optimal);
}
public int[] random(int m, int t) // 生成互不相同的随机数序列的函数
{
Random r = new Random();
int k = 0;
int tmp[] = new int[m];
for (int i = 0; i < m; i++) {
while (true) {
boolean flag = true;
tmp[i] = r.nextInt(t);
k++;
for (int j = 0; j < k - 1; j++) {
if (tmp[i] == tmp[j]) {
k--;
flag = false;
break;
}
}
if (flag == true)
break;
}
}
return tmp;
}
public int[][] Crossove(int a[][]) // 交配函数
{
int A[][] = new int[num][n];
int k = 0;
for (int i = 0; i < num; i = i + 2) {
Random r = new Random();
int loc = r.nextInt(n - 1);
for (int j = 0; j <= loc; j++) {
A[k][j] = a[i][j];
A[k + 1][j] = a[i + 1][j];
}
int m1 = loc + 1, m2 = m1;
for (int t = 0; t < n; t++) {
if (!is_exist(loc, a[i + 1][t], a[i])) {
A[k][m1] = a[i + 1][t];
m1++;
}
if (!is_exist(loc, a[i][t], a[i + 1])) {
A[k + 1][m2] = a[i][t];
m2++;
}
}
if (r.nextInt(100) < 10) {
A[k] = variation(A[k]);
}
if (r.nextInt(100) < 10) {
A[k + 1] = variation(A[k + 1]);
}
k += 2;
}
return A;
}
public int[][] nextGen(int a[][]) // 生成新种群的函数
{
int A[][] = new int[num][n];
double p[] = new double[num];
double value[] = new double[num];
double sum = 0;
for (int i = 0; i < num; i++) {
double tmp[] = new double[2];
tmp = getValue(a[i]);
value[i] = tmp[0];
if (optimal > tmp[1]) {
optimal = tmp[1];
best = a[i];
}
sum += value[i];
}
for (int i = 0; i < num; i++) {
p[i] = value[i] / sum;
}
int k = 0;
for (int i = 0; i < num; i++) {
double s = 0;
int j = 0;
double r = Math.random();
while (true) {
if (s >= r)
break;
s += p[j];
j++;
}
A[k] = a[j - 1];
k++;
}
return Crossove(A);
}
public boolean is_exist(int loc, int x, int b[]) {
for (int i = 0; i <= loc; i++) {
if (x == b[i])
return true;
}
return false;
}
public int[] variation(int a[]) // 变异函数
{
int b[] = new int[n];
Random r = new Random();
int x = r.nextInt(n);
int y = r.nextInt(n);
while (y == x)
y = r.nextInt(n);
if (x < y) {
int k = 0;
for (int i = 0; i < n; i++) {
if (i < x)
b[i] = a[i];
else if (i > y)
b[i] = a[i];
else {
b[i] = a[y - k];
k++;
}
}
} else {
int yy = y, k = 1;
for (int i = 0; i < n; i++) {
if (i <= yy) {
b[i] = a[y];
y--;
} else if (i >= x) {
b[i] = a[n - k];
k++;
} else
b[i] = a[i];
}
}
return b;
}
public double[] getValue(int a[]) // 获取路径值的函数
{
double length = 0;
int k = a.length - 1;
for (int i = 0; i < k; i++) {
double tmp1 = Math.pow(vec.get(a[i]).x - vec.get(a[i + 1]).x, 2);
double tmp2 = Math.pow(vec.get(a[i]).y - vec.get(a[i + 1]).y, 2);
length += Math.sqrt(tmp1 + tmp2);
}
double tmp1 = Math.pow(vec.get(a[0]).x - vec.get(a[k]).x, 2);
double tmp2 = Math.pow(vec.get(a[0]).y - vec.get(a[k]).y, 2);
length += Math.sqrt(tmp1 + tmp2);
double f[] = new double[2];
f[0] = 1 / Math.pow(length, 8);
f[1] = length;
return f;
}
public static void main(String argv[]) {
new Genetic().getStrategy();
}
}
禁忌搜索算法(1):单线程实现
package org.wucl.ts;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Random;
import org.wucl.ga.Genetic;
/**
* 单线程实现禁忌搜索算法解决TSP问题
*
*
* @author wucl(lailaiwcl@163.com)
*
*/
public class Tabu {
private int MAX_GEN;// 迭代次数
private int N;// 每次搜索邻居个数
private int ll;// 禁忌长度
private int cityNum; // 城市数量,编码长度
private double[][] distance; // 距离矩阵
private int bestT;// 最佳出现代数
private int[] Ghh;// 初始路径编码
private int[] bestGh;// 最好的路径编码
private double bestEvaluation;
private int[] LocalGhh;// 当代最好编码
private double localEvaluation;
private int[] tempGhh;// 存放临时编码
private double tempEvaluation;
private int[][] jinji;// 禁忌表
private int t;// 当前代数
private Random random;
public Tabu() {
}
/**
*
* constructor of Tabu
*
* @param n
*
* 城市数量
* @param g
*
* 运行代数
* @param c
*
* 每次搜索邻居个数
*
* @param m
*
* 禁忌长度
*
**/
public Tabu(int n, int g, int c, int m) {
cityNum = n;
MAX_GEN = g;
N = c;
ll = m;
}
/**
* 初始化Tabu算法类
* @param filename 数据文件名,该文件存储所有城市节点坐标数据
* @throws IOException
*/
private void init() throws IOException {
// 读取数据
int[] x;
int[] y;
String strbuff;
Reader reader = new InputStreamReader(Genetic.class.getClassLoader().getResourceAsStream("data.txt"));
BufferedReader data = new BufferedReader(reader);
distance = new double[cityNum][cityNum];
x = new int[cityNum];
y = new int[cityNum];
for (int i = 0; i < cityNum; i++) {
strbuff = data.readLine();
String[] strcol = strbuff.split(" ");
x[i] = Integer.valueOf(strcol[1]);// x坐标
y[i] = Integer.valueOf(strcol[2]);// y坐标
}
// 计算距离矩阵
//System.out.println("距离矩阵为:");
for (int i = 0; i < cityNum - 1; i++) {
distance[i][i] = 0; // 对角线为0
for (int j = 0; j < cityNum; j++) {
distance[i][j] = Math
.sqrt(((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j])
* (y[i] - y[j])));
//System.out.print(Math.ceil(distance[i][j]) + " ");
}
//System.out.println();
}
distance[cityNum - 1][cityNum - 1] = 0;
Ghh = new int[cityNum];
bestGh = new int[cityNum];
bestEvaluation = Integer.MAX_VALUE;
LocalGhh = new int[cityNum];
localEvaluation = Integer.MAX_VALUE;
tempGhh = new int[cityNum];
tempEvaluation = Integer.MAX_VALUE;
jinji = new int[ll][cityNum];
bestT = 0;
t = 0;
random = new Random(System.currentTimeMillis());
}
// 初始化编码Ghh
void initGroup() {
int i, j;
Ghh[0] = random.nextInt(65535) % cityNum;
for (i = 1; i < cityNum;)// 编码长度
{
Ghh[i] = random.nextInt(65535) % cityNum;
for (j = 0; j < i; j++) {
if (Ghh[i] == Ghh[j]) {
break;
}
}
if (j == i) {
i++;
}
}
}
// 复制编码体,复制编码Gha到Ghb
public void copyGh(int[] Gha, int[] Ghb) {
for (int i = 0; i < cityNum; i++) {
Ghb[i] = Gha[i];
}
}
public double evaluate(int[] chr) {
double len = 0;
// 编码,起始城市,城市1,城市2...城市n
for (int i = 1; i < cityNum; i++) {
len += distance[chr[i - 1]][chr[i]];
}
// // 城市n,起始城市
len += distance[chr[cityNum - 1]][chr[0]];
// for (int i = 1; i < cityNum; i++) {
// System.out.print(chr[i] + ",");
// }
// System.out.println("-------------" + len);
return len;
}
// 邻域交换,得到邻居
public void Linju(int[] Gh, int[] tempGh) {
int i, temp;
int ran1, ran2;
for (i = 0; i < cityNum; i++) {
tempGh[i] = Gh[i];
}
ran1 = random.nextInt(65535) % cityNum;
ran2 = random.nextInt(65535) % cityNum;
while (ran1 == ran2) {
ran2 = random.nextInt(65535) % cityNum;
}
temp = tempGh[ran1];
tempGh[ran1] = tempGh[ran2];
tempGh[ran2] = temp;
}
// 判断编码是否在禁忌表中
public int panduan(int[] tempGh) {
int i, j;
int flag = 0;
for (i = 0; i < ll; i++) {
flag = 0;
for (j = 0; j < cityNum; j++) {
if (tempGh[j] != jinji[i][j]) {
flag = 1;// 不相同
break;
}
}
if (flag == 0)// 相同,返回存在相同
{
// return 1;
break;
}
}
if (i == ll)// 不等
{
return 0;// 不存在
} else {
return 1;// 存在
}
}
// 解禁忌与加入禁忌
public void jiejinji(int[] tempGh) {
int i, j, k;
// 删除禁忌表第一个编码,后面编码往前挪动
for (i = 0; i < ll - 1; i++) {
for (j = 0; j < cityNum; j++) {
jinji[i][j] = jinji[i + 1][j];
}
}
// 新的编码加入禁忌表
for (k = 0; k < cityNum; k++) {
jinji[ll - 1][k] = tempGh[k];
}
}
public void solve() {
int nn;
// 初始化编码Ghh
initGroup();
copyGh(Ghh, bestGh);// 复制当前编码Ghh到最好编码bestGh
bestEvaluation = evaluate(Ghh);
while (t < MAX_GEN) {
nn = 0;
localEvaluation = Integer.MAX_VALUE;
while (nn < N) {
Linju(Ghh, tempGhh);// 得到当前编码Ghh的邻域编码tempGhh
if (panduan(tempGhh) == 0)// 判断编码是否在禁忌表中
{
// 不在
tempEvaluation = evaluate(tempGhh);
if (tempEvaluation < localEvaluation) {
copyGh(tempGhh, LocalGhh);
localEvaluation = tempEvaluation;
}
nn++;
}
}
if (localEvaluation < bestEvaluation) {
bestT = t;
copyGh(LocalGhh, bestGh);
bestEvaluation = localEvaluation;
}
copyGh(LocalGhh, Ghh);
// 解禁忌表,LocalGhh加入禁忌表
jiejinji(LocalGhh);
t++;
}
System.out.print("最佳长度出现代数:");
System.out.println(bestT);
System.out.print("最佳长度:");
System.out.println(bestEvaluation);
System.out.print("最佳路径:");
for (int i = 0; i < cityNum; i++) {
System.out.print(bestGh[i] + "->");
}
}
public static void main(String[] args) throws IOException {
System.out.println("Start....");
Tabu tabu = new Tabu(30,200000, 200, 20);
tabu.init();
long t1 = System.currentTimeMillis();
tabu.solve();
System.out.println();
System.out.println("计算用时:" + (System.currentTimeMillis() - t1));
}
}
禁忌搜索算法(2):多线程是实现
package org.wucl.ts;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Random;
import org.wucl.ga.Genetic;
/**
* 多线程调度实现禁忌搜索算法解决TSP问题
*
*
* @author wucl(lailaiwcl@163.com)
*
*/
public class Tabu2 {
public static int MAX_GEN;// 迭代次数
private int N;// 每次搜索邻居个数
private int ll;// 禁忌长度
private int cityNum; // 城市数量,编码长度
private double[][] distance; // 距离矩阵
public int bestT;// 最佳出现代数
private int[] Ghh;// 初始路径编码
public int[] bestGh;// 最好的路径编码
public double bestEvaluation;
private int[] LocalGhh;// 当代最好编码
public double localEvaluation;
private int[] tempGhh;// 存放临时编码
private double tempEvaluation;
private static int[][] jinji;// 禁忌表
private static int t;// 当前代数
private Random random;
private Object obj1 = new Object();
private Object obj2 = new Object();
public Tabu2() {
}
/**
*
* constructor of Tabu
*
* @param n
*
* 城市数量
* @param g
*
* 运行代数
* @param c
*
* 每次搜索邻居个数
*
* @param m
*
* 禁忌长度
*
**/
public Tabu2(int n, int c, int m) {
cityNum = n;
N = c;
ll = m;
}
/**
* 初始化Tabu算法类
* @param filename 数据文件名,该文件存储所有城市节点坐标数据
* @throws IOException
*/
private void init() throws IOException {
// 读取数据
int[] x;
int[] y;
String strbuff;
Reader reader = new InputStreamReader(Genetic.class.getClassLoader()
.getResourceAsStream("data.txt"));
BufferedReader data = new BufferedReader(reader);
distance = new double[cityNum][cityNum];
x = new int[cityNum];
y = new int[cityNum];
for (int i = 0; i < cityNum; i++) {
strbuff = data.readLine();
String[] strcol = strbuff.split(" ");
x[i] = Integer.valueOf(strcol[1]);// x坐标
y[i] = Integer.valueOf(strcol[2]);// y坐标
}
// 计算距离矩阵
// System.out.println("距离矩阵为:");
for (int i = 0; i < cityNum - 1; i++) {
distance[i][i] = 0; // 对角线为0
for (int j = 0; j < cityNum; j++) {
distance[i][j] = Math
.sqrt(((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j])
* (y[i] - y[j])));
}
}
distance[cityNum - 1][cityNum - 1] = 0;
Ghh = new int[cityNum];
bestGh = new int[cityNum];
bestEvaluation = Integer.MAX_VALUE;
LocalGhh = new int[cityNum];
localEvaluation = Integer.MAX_VALUE;
tempGhh = new int[cityNum];
tempEvaluation = Integer.MAX_VALUE;
jinji = new int[ll][cityNum];
bestT = 0;
t = 0;
random = new Random(System.currentTimeMillis());
}
// 初始化编码Ghh
void initGroup() {
int i, j;
Ghh[0] = random.nextInt(65535) % cityNum;
for (i = 1; i < cityNum;)// 编码长度
{
Ghh[i] = random.nextInt(65535) % cityNum;
for (j = 0; j < i; j++) {
if (Ghh[i] == Ghh[j]) {
break;
}
}
if (j == i) {
i++;
}
}
}
// 复制编码体,复制编码Gha到Ghb
public void copyGh(int[] Gha, int[] Ghb) {
for (int i = 0; i < cityNum; i++) {
Ghb[i] = Gha[i];
}
}
public double evaluate(int[] chr) {
double len = 0;
// 编码,起始城市,城市1,城市2...城市n
for (int i = 1; i < cityNum; i++) {
len += distance[chr[i - 1]][chr[i]];
}
// // 城市n,起始城市
len += distance[chr[cityNum - 1]][chr[0]];
return len;
}
// 邻域交换,得到邻居
public void Linju(int[] Gh, int[] tempGh) {
int i, temp;
int ran1, ran2;
for (i = 0; i < cityNum; i++) {
tempGh[i] = Gh[i];
}
ran1 = random.nextInt(65535) % cityNum;
ran2 = random.nextInt(65535) % cityNum;
while (ran1 == ran2) {
ran2 = random.nextInt(65535) % cityNum;
}
temp = tempGh[ran1];
tempGh[ran1] = tempGh[ran2];
tempGh[ran2] = temp;
}
// 判断编码是否在禁忌表中
public int panduan(int[] tempGh) {
int i, j;
int flag = 0;
for (i = 0; i < ll; i++) {
flag = 0;
for (j = 0; j < cityNum; j++) {
if (tempGh[j] != jinji[i][j]) {
flag = 1;// 不相同
break;
}
}
if (flag == 0)// 相同,返回存在相同
{
// return 1;
break;
}
}
if (i == ll)// 不等
{
return 0;// 不存在
} else {
return 1;// 存在
}
}
// 解禁忌与加入禁忌
public void jiejinji(int[] tempGh) {
synchronized (obj2) {
int i, j, k;
// 删除禁忌表第一个编码,后面编码往前挪动
for (i = 0; i < ll - 1; i++) {
for (j = 0; j < cityNum; j++) {
jinji[i][j] = jinji[i + 1][j];
}
}
// 新的编码加入禁忌表
for (k = 0; k < cityNum; k++) {
jinji[ll - 1][k] = tempGh[k];
}
}
}
public void solve() {
try {
init();
} catch (IOException e) {
e.printStackTrace();
}
int nn;
// 初始化编码Ghh
initGroup();
copyGh(Ghh, bestGh);// 复制当前编码Ghh到最好编码bestGh
bestEvaluation = evaluate(Ghh);
while (t < MAX_GEN) {
nn = 0;
localEvaluation = Integer.MAX_VALUE;
while (nn < N) {
Linju(Ghh, tempGhh);// 得到当前编码Ghh的邻域编码tempGhh
if (panduan(tempGhh) == 0)// 判断编码是否在禁忌表中
{
// 不在
tempEvaluation = evaluate(tempGhh);
if (tempEvaluation < localEvaluation) {
copyGh(tempGhh, LocalGhh);
localEvaluation = tempEvaluation;
}
nn++;
}
}
if (localEvaluation < bestEvaluation) {
bestT = t;
copyGh(LocalGhh, bestGh);
bestEvaluation = localEvaluation;
}
copyGh(LocalGhh, Ghh);
// 解禁忌表,LocalGhh加入禁忌表
jiejinji(LocalGhh);
synchronized (obj1) {
t++;
}
}
}
public static void main(String[] args) {
Tabu2.MAX_GEN = 200000;
final Tabu2 tabu1 = new Tabu2(30, 200, 20);
final Tabu2 tabu2 = new Tabu2(30, 200, 20);
final Thread thread1 = new Thread("thread-1") {
@Override
public void run() {
System.out.println("thread-1 start...");
tabu1.solve();
}
};
final Thread thread2 = new Thread("thread-2") {
@Override
public void run() {
System.out.println("thread-2 start...");
tabu2.solve();
}
};
final long t1 = System.currentTimeMillis();
Thread thread3 = new Thread("thread-2") {
@Override
public void run() {
System.out.println("thread-3 start...");
while (true) {
if (!thread1.isAlive() && !thread2.isAlive()) {
Tabu2 bestTabu = tabu1;
if (tabu1.localEvaluation <= tabu2.localEvaluation) {
bestTabu = tabu2;
}
System.out.print("最佳长度出现代数:");
System.out.println(bestTabu.bestT);
System.out.print("最佳长度:");
System.out.println(bestTabu.bestEvaluation);
System.out.print("最佳路径:");
for (int i = 0; i < 30; i++) {
System.out.print(bestTabu.bestGh[i] + "->");
}
System.out.println();
break;
}
}
System.out.println("计算用时:" + (System.currentTimeMillis() - t1));
}
};
thread1.start();
thread2.start();
thread3.start();
}
}