其实精确算法也好,启发式算法也好,都是独立的算法,可以不依赖求解器进行代码实现的,只要过程符合算法框架即可。只不过平常看到的大部分是精确算法在各种整数规划模型上的应用,为此难免脱离不了cplex等求解器。下面我们用分支定界算法求解TSP问题的代码实现,完全脱离求解器。
package tspsolver;
public class Timer
{
private long startTime;
private long stopTime;
private boolean running;
private final double nano = 1000000000.0;
public Timer()
{
super();
}
public void reset()
{
this.startTime = 0;
this.running = false;
}
public void start()
{
this.startTime = System.nanoTime();
this.running = true;
}
public void stop()
{
if (running)
{
this.stopTime = System.nanoTime();
this.running = false;
}
}
public double getTime()
{
double elapsed;
if (running)
{
elapsed = ((System.nanoTime() - startTime) / nano);
}
else
{
elapsed = ((stopTime - startTime) / nano);
}
return elapsed;
}
}
TSPInstanceReader类:读取实例
package tspsolver;
import java.awt.GraphicsEnvironment;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
public class TSPInstanceReader {
ArrayList cities;
double[][] distanceMatrix;
public TSPInstanceReader(String instanceFile) {
File file;
if(instanceFile != null) {
file = new File(instanceFile);
}
else {
JFileChooser chooser = new JFileChooser();
int response = chooser.showOpenDialog(null);
if(response != JFileChooser.APPROVE_OPTION)
return;
file = chooser.getSelectedFile();
}
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(file));
}
catch(IOException e) {
alert("Error loading file " + e);
System.exit(1);
}
int dimension = 0;
try {
String line;
while(!(line = reader.readLine()).equals("NODE_COORD_SECTION")) {
String[] entry = line.split(":");
//System.out.println(entry[0].trim());
switch(entry[0].trim()) {
case "TYPE":
if(!entry[1].trim().equals("TSP"))
throw new Exception("File not in TSP format");
break;
case "DIMENSION":
dimension = Integer.parseInt(entry[1].trim());
break;
}
}
}
catch(Exception e) {
alert("Error parsing header " + e);
System.exit(1);
}
System.out.println("DIMENSION = "+dimension);
this.cities = new ArrayList(dimension);
try {
String line;
// while((line = reader.readLine()) != null && !line.trim().equals("EOF")) {
// String[] entry = line.trim().split(" ");
// System.out.println(entry[0] +" "+entry[1]+" "+entry[2]);
// cities.add(new City(entry[0], Double.parseDouble(entry[1]), Double.parseDouble(entry[2])));
// }
for(int i = 0; i < dimension; i++) {
line = reader.readLine();
String[] entry = line.trim().split(" ");
//System.out.println(entry[0] +" "+entry[1]+" "+entry[2]);
cities.add(new City(entry[0], Double.parseDouble(entry[1].trim()), Double.parseDouble(entry[2].trim())));
}
reader.close();
}
catch(Exception e) {
alert("Error parsing data " + e);
System.exit(1);
}
this.distanceMatrix = new double[cities.size()][cities.size()];
for(int i = 0; i < cities.size(); i++) {
for(int ii = 0; ii < cities.size(); ii++)
distanceMatrix[i][ii] = cities.get(i).distance(cities.get(ii));
}
}
public double[][] getDistances() {
return this.distanceMatrix;
}
public ArrayList getCities(){
return this.cities;
}
private static void alert(String message) {
if(GraphicsEnvironment.isHeadless())
System.out.println(message);
else
JOptionPane.showMessageDialog(null, message);
}
}
PriorityQueue类:优先队列
package tspsolver;
public class PriorityQueue {
private Node[] queue;
private int capacity,size;
public PriorityQueue(int capacity){
this.capacity = capacity + 1;
queue = new Node[this.capacity];
size = 0;
}
public boolean isEmpty(){
return size == 0;
}
public int size(){
return size;
}
public void add(Node u){
Node v = new Node( u.getPath(), u.getLevel(), u.getBound());
queue[++size] = v;
int tempSize = size;
while(tempSize!=1 && v.getBound() < queue[tempSize/2].getBound()){
queue[tempSize] = queue[tempSize/2];
tempSize /= 2;
}
queue[tempSize] = v;
}
public Node remove(){
int parent=1,child=2;
Node item,tempNode;
if(isEmpty()){
return null;
}
item = queue[1];
tempNode = queue[size--];
while(child <= size){
if(child < size && queue[child].getBound() > queue[child+1].getBound())
child++;
if(tempNode.getBound() <= queue[child].getBound())
break;
queue[parent] = queue[child];
parent = child;
child *= 2;
}
queue[parent] = tempNode;
return item;
}
public void clear(){
queue = new Node[this.capacity];
}
}
Node类:搜索树的节点
package tspsolver;
import java.util.ArrayList;
public class Node {
public Node(ArrayList path, int city, double bound, int level) {
super();
this.path = path;
this.path.add(city);
this.bound = bound;
this.level = level;
}
public Node() {
// TODO Auto-generated constructor stub
this.path = new ArrayList();
this.bound = 0;
this.level = 0;
}
public Node(ArrayList path2, int level2, double bound2) {
// TODO Auto-generated constructor stub
this.path = path2;
this.level = level2;
this.bound = bound2;
}
public ArrayList getPath() {
return path;
}
public void setPath(ArrayList path) {
this.path = new ArrayList<>(path);
}
public double getBound() {
return bound;
}
public void setBound(double bound) {
this.bound = bound;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
private ArrayList path;//保存该节点目前已经走过的城市序列
private double bound;//记录该节点目前所能达到的最低distance
private int level;//记录节点处于搜索树的第几层
//记录当前城市序列的distance
public double computeLength(double[][] distanceMatrix) {
// TODO Auto-generated method stub
double distance = 0;
for(int i=0;i
可能大家还没理解节点是如何分支的,看一张图大家就懂了。我们知道TSP问题的一个solution是能用一个序列表示城市的先后访问顺序,比如现在有4座城市(1,2,3,4):
图中每个节点的数字序列就是path保存的。大家都看到了吧,其实分支就是一个穷枚举的过程。
相对于穷举,分支定界算法的优越之处就在于其加入了定界过程,在分支的过程中就砍掉了某些不可能的支,减少了枚举的次数,大大提高了算法的效率。如下:
City类:保存城市的坐标,名字等
package tspsolver;
import java.awt.geom.Point2D;
/**
* City class that represents a city by a point and a name
*/
public class City extends Point2D.Double {
private String name;
/**
* Constructs a city by point data and name
*
* @param name The city name
* @param x The x-coordinate
* @param y The y-coordinate
*/
public City(String name, double x, double y) {
super(x, y);
this.name = name;
}
/**
* Gets the city's name
*
* @return The city's name
*/
public String getName() {
return name;
}
}
BranchBound_TSP类:BB算法主程序
package tspsolver;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.List;
public class BranchBound_TSP {
/*
* shortestPath: shortest path to visit all cities exactly once.
* shortestDistance: variable will store distance to traverse shortestPath.
* longestDistance: variable will contain longest distance to visit all
* cities.
*
*/
public static double shortestDistance;
public static int queueCount;
public static ArrayList shortestPath;
public static PriorityQueue queue = new PriorityQueue(100000000);
/*
* Reads input file; solves the traveling salesman problem using brute force
* search; writes the output file
*
* @param pathName path of input file.
*/
public static void startTSP(String pathName) {
double[][] distanceMatrix = null;
TSPInstanceReader tspIns = new TSPInstanceReader(pathName);
distanceMatrix = tspIns.getDistances();
ArrayList cities = tspIns.getCities();
shortestDistance = Integer.MAX_VALUE;
queueCount = 0;
shortestPath = null;
queue.clear();
solveTSP(distanceMatrix);
System.out.println("distance = "+shortestDistance);
for (int city : shortestPath) {
System.out.print(cities.get(city).getName() +" -> ");
}
}
private static void solveTSP(double[][] distanceMatrix) {
int totalCities = distanceMatrix.length;
ArrayList cities = new ArrayList();
for (int i = 0; i < totalCities; i++) {
cities.add(i);
}
ArrayList path;
double initB = initbound(totalCities, distanceMatrix);
Node v = new Node(new ArrayList<>(), 0, initB, 0);
queue.add(v);
queueCount++;
while (!queue.isEmpty()) {
v = queue.remove();
if (v.getBound() < shortestDistance) {
Node u = new Node();
u.setLevel(v.getLevel() + 1);
for (int i = 1; i < totalCities; i++) {
path = v.getPath();
if (!path.contains(i)) {
u.setPath(v.getPath());
path = u.getPath();
path.add(i);
u.setPath(path);
if (u.getLevel() == totalCities - 2) {
// put index of only vertex not in u.path at the end
// of u.path
for (int j = 1; j < cities.size(); j++) {
if (!u.getPath().contains(j)) {
ArrayList temp = new ArrayList<>();
temp = u.getPath();
temp.add(j);
u.setPath(temp);
}
}
path = u.getPath();
path.add(0);
u.setPath(path);
if (u.computeLength(distanceMatrix) < shortestDistance) {
shortestDistance = u.computeLength(distanceMatrix);// implement
shortestPath = u.getPath();
}
} else {
u.setBound(computeBound(u, distanceMatrix, cities));
//u.getBound()获得的是不完整的解,如果一个不完整的解bound都大于当前最优解,那么完整的解肯定会更大,那就没法玩了。
//所以这里只要u.getBound() < shortestDistance的分支
if (u.getBound() < shortestDistance) {
queue.add(u);
queueCount++;
}
else {
System.out.println("currentBest = "+shortestDistance+" cut bound >>> "+u.getBound());
}
}
}
}
}
}
}
private static double computeBound(Node u, double[][] distanceMatrix, ArrayList cities) {
double bound = 0;
ArrayList path = u.getPath();
for (int i = 0; i < path.size() - 1; i++) {
bound = bound + distanceMatrix[path.get(i)][path.get(i + 1)];
}
int last = path.get(path.size() - 1);
List subPath1 = path.subList(1, path.size());
double min;
//回来的
for (int i = 0; i < cities.size(); i++) {
min = Integer.MAX_VALUE;
if (!path.contains(cities.get(i))) {
for (int j = 0; j < cities.size(); j++) {
if (i != j && !subPath1.contains(cities.get(j))) {
if (min > distanceMatrix[i][j]) {
min = distanceMatrix[i][j];
}
}
}
}
if (min != Integer.MAX_VALUE)
bound = bound + min;
}
//出去的
min = Integer.MAX_VALUE;
for (int i = 0; i < cities.size(); i++) {
if (/*cities.get(i) != last && */!path.contains(i) && min > distanceMatrix[last][i]) {
min = distanceMatrix[last][i];
}
}
bound = bound + min;
//System.out.println("bound = "+bound);
return bound;
}
//贪心初始解
private static double initbound(int totalCities, double[][] distanceMatrix) {
double min;
double bound = 0;
for (int i = 0; i < totalCities; i++) {
min = Integer.MAX_VALUE;
for (int j = 0; j < totalCities; j++) {
if (distanceMatrix[i][j] != 0) {
if (min > distanceMatrix[i][j])
min = distanceMatrix[i][j];
}
}
bound = bound + min;
}
return bound;
}
public static void main(String[] args) {
String instancePath = System.getProperty("user.dir")+"\\input\\";
String instanceName = "cities.tsp.txt";
Timer watch = new Timer();
String pathName = instancePath+instanceName;
watch.start();
System.out.println("pathName = "+pathName);
watch.stop();
startTSP(pathName);
System.out.println('\n'+"time = "+watch.getTime()+" s");
}
}
运行结果:
注:本文装载自
https://mp.weixin.qq.com/s?__biz=MzI3NTkyODIzNg==&mid=2247484854&idx=1&sn=e8b15038a5ffc389c31ca1db00fe72b1&chksm=eb7c0075dc0b8963051d305155d3fc7e89a6442e01b4ffb1f8446c2e504f1c68e0dc7513436a&mpshare=1&scene=1&srcid=&sharer_sharetime=1567610412106&sharer_shareid=054592193644de509623829748e83807&key=39e8dcac625792606b32b4899caa220a6fe4fe6ea1122c2fc8e3b2a61071fabc76cc48ee21216432eb1e6ec413431577d5a033d5df31b948e3bcb0504e9e5453e6407c97b06ece7fa8853e6bc090d238&ascene=1&uin=MjYzMDA1MzAyMQ%3D%3D&devicetype=Windows+10&version=62060834&lang=zh_CN&pass_ticket=x2YOENqmw1WX%2BbY2oMQM2%2FvgZw9bCyJdvL36g%2Fad0MnNoTVOTcP2gVVONuueS7VV