分支定界算法求解TSP

其实精确算法也好,启发式算法也好,都是独立的算法,可以不依赖求解器进行代码实现的,只要过程符合算法框架即可。只不过平常看到的大部分是精确算法在各种整数规划模型上的应用,为此难免脱离不了cplex等求解器。下面我们用分支定界算法求解TSP问题的代码实现,完全脱离求解器。

代码框架如下
分支定界算法求解TSP_第1张图片
Timer类:计时用

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):
分支定界算法求解TSP_第2张图片
图中每个节点的数字序列就是path保存的。大家都看到了吧,其实分支就是一个穷枚举的过程
相对于穷举,分支定界算法的优越之处就在于其加入了定界过程,在分支的过程中就砍掉了某些不可能的支,减少了枚举的次数,大大提高了算法的效率。如下:
分支定界算法求解TSP_第3张图片
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");
		
	}

}

运行结果:
分支定界算法求解TSP_第4张图片
:本文装载自
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

你可能感兴趣的:(算法基础)