gpt4 book ai didi

algorithm - 优化的 TSP 算法

转载 作者:塔克拉玛干 更新时间:2023-11-03 02:19:11 24 4
gpt4 key购买 nike

我对改进或提出能够解决 Travelling salesman problem 问题的算法感兴趣。对于大约 n = 100 到 200 个城市。

我提供的维基百科链接列出了各种优化,但它是在相当高的水平上进行的,我不知道如何在代码中实际实现它们。

那里有工业强度的求解器,例如 Concorde ,但这些对于我想要的来说太复杂了,并且充斥着 TSP 搜索的经典解决方案都提供了随机算法或经典回溯或动态规划算法,这些算法仅适用于大约 20 个城市。

那么,有没有人知道如何实现一个简单的(我所说的简单是指一个实现不超过 100-200 行代码)TSP 求解器,它可以在合理的时间(几秒钟)内工作至少 100城市?我只对精确解感兴趣。

您可能会假设输入是随机生成的,所以我不关心专门用于破坏特定算法的输入。

最佳答案

200 行且没有库是一个严格的限制。高级求解器使用带有 Held–Karp 松弛的分支定界法,我不确定即使是最基本的版本是否适合 200 条法线。不过,这里有一个大纲。

举行卡普

将 TSP 编写为整数程序的一种方法如下(Dantzig、Fulkerson、Johnson)。对于所有边 e,常量 we 表示边 e 的长度,变量 xe 为 1,如果边 e 在巡回中,否则为 0。对于顶点的所有子集S,∂(S)表示连接S中的顶点和不在S中的顶点的边。

最小化边e we xe
受制于
1. 对于所有顶点 v,求和 edges e in ∂({v}) xe = 2
2. 对于顶点的所有非空真子集S,求和edges e in ∂(S) xe ≥ 2
3. 对于 E 中的所有边 e,{0, 1}

中的 x e

条件 1 确保边集是游览的集合。条件 2 确保只有一个。 (否则,令 S 为其中一个游览访问的顶点集。)通过进行此更改获得 Held–Karp 松弛。

<罢工>3.对于 E 中的所有边 e,{0, 1}
中的 xe3. 对于E中的所有边e,0≤xe≤1

Held–Karp 是一个线性程序,但它具有指数数量的约束。解决它的一种方法是引入拉格朗日乘数,然后进行次梯度优化。这归结为一个计算最小生成树然后更新一些向量的循环,但细节有点复杂。除了“Held–Karp”和“subgradient (descent|optimization)”,“1-tree”是另一个有用的搜索词。

(一个较慢的替代方案是编写一个 LP 求解器并引入 subtour 约束,因为它们被先前的最优解所违反。这意味着编写一个 LP 求解器和一个最小切割程序,这也是更多的代码,但它可能会更好地扩展到更奇特的 TSP 约束。)

分支定界

“部分解决方案”是指将变量部分分配给 0 或 1,其中分配为 1 的边肯定在游览中,而分配为 0 的边肯定在游览中。使用这些边约束评估 Held–Karp 给出了尊重已经做出的决定(扩展)的最佳旅行的下限。

分支定界法维护一组部分解,其中至少有一个扩展到最优解。一种变体的伪代码,具有最佳优先回溯的深度优先搜索如下。

let h be an empty minheap of partial solutions, ordered by Held–Karp value
let bestsolsofar = null
let cursol be the partial solution with no variables assigned
loop
while cursol is not a complete solution and cursol's H–K value is at least as good as the value of bestsolsofar
choose a branching variable v
let sol0 be cursol union {v -> 0}
let sol1 be cursol union {v -> 1}
evaluate sol0 and sol1
let cursol be the better of the two; put the other in h
end while
if cursol is better than bestsolsofar then
let bestsolsofar = cursol
delete all heap nodes worse than cursol
end if
if h is empty then stop; we've found the optimal solution
pop the minimum element of h and store it in cursol
end loop

分支定界法的思想是存在一个部分解的搜索树。求解Held-Karp的要点在于LP的值至多为最优游的长度OPT,但也推测至少为3/4 OPT(实际中通常更接近OPT)。

伪代码中我遗漏的一个细节是如何选择分支变量。目标通常是首先做出“艰难”的决定,因此固定一个值已经接近 0 或 1 的变量可能并不明智。一种选择是选择最接近 0.5 的值,但还有很多很多其他选项。

编辑

Java 实现。 198 行非空白、非注释行。我忘记了 1 树不能将变量分配给 1,所以我通过找到其 1 树的度 > 2 的顶点并依次删除每条边来进行分支。此程序接受 EUC_2D 格式的 TSPLIB 实例,例如 eil51.tspeil76.tspeil101.tsp和来自 http://www2.iwr.uni-heidelberg.de/groups/comopt/software/TSPLIB95/tsp/lin105.tsp .

// simple exact TSP solver based on branch-and-bound/Held--Karp
import java.io.*;
import java.util.*;
import java.util.regex.*;

public class TSP {
// number of cities
private int n;
// city locations
private double[] x;
private double[] y;
// cost matrix
private double[][] cost;
// matrix of adjusted costs
private double[][] costWithPi;
Node bestNode = new Node();

public static void main(String[] args) throws IOException {
// read the input in TSPLIB format
// assume TYPE: TSP, EDGE_WEIGHT_TYPE: EUC_2D
// no error checking
TSP tsp = new TSP();
tsp.readInput(new InputStreamReader(System.in));
tsp.solve();
}

public void readInput(Reader r) throws IOException {
BufferedReader in = new BufferedReader(r);
Pattern specification = Pattern.compile("\\s*([A-Z_]+)\\s*(:\\s*([0-9]+))?\\s*");
Pattern data = Pattern.compile("\\s*([0-9]+)\\s+([-+.0-9Ee]+)\\s+([-+.0-9Ee]+)\\s*");
String line;
while ((line = in.readLine()) != null) {
Matcher m = specification.matcher(line);
if (!m.matches()) continue;
String keyword = m.group(1);
if (keyword.equals("DIMENSION")) {
n = Integer.parseInt(m.group(3));
cost = new double[n][n];
} else if (keyword.equals("NODE_COORD_SECTION")) {
x = new double[n];
y = new double[n];
for (int k = 0; k < n; k++) {
line = in.readLine();
m = data.matcher(line);
m.matches();
int i = Integer.parseInt(m.group(1)) - 1;
x[i] = Double.parseDouble(m.group(2));
y[i] = Double.parseDouble(m.group(3));
}
// TSPLIB distances are rounded to the nearest integer to avoid the sum of square roots problem
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
double dx = x[i] - x[j];
double dy = y[i] - y[j];
cost[i][j] = Math.rint(Math.sqrt(dx * dx + dy * dy));
}
}
}
}
}

public void solve() {
bestNode.lowerBound = Double.MAX_VALUE;
Node currentNode = new Node();
currentNode.excluded = new boolean[n][n];
costWithPi = new double[n][n];
computeHeldKarp(currentNode);
PriorityQueue<Node> pq = new PriorityQueue<Node>(11, new NodeComparator());
do {
do {
boolean isTour = true;
int i = -1;
for (int j = 0; j < n; j++) {
if (currentNode.degree[j] > 2 && (i < 0 || currentNode.degree[j] < currentNode.degree[i])) i = j;
}
if (i < 0) {
if (currentNode.lowerBound < bestNode.lowerBound) {
bestNode = currentNode;
System.err.printf("%.0f", bestNode.lowerBound);
}
break;
}
System.err.printf(".");
PriorityQueue<Node> children = new PriorityQueue<Node>(11, new NodeComparator());
children.add(exclude(currentNode, i, currentNode.parent[i]));
for (int j = 0; j < n; j++) {
if (currentNode.parent[j] == i) children.add(exclude(currentNode, i, j));
}
currentNode = children.poll();
pq.addAll(children);
} while (currentNode.lowerBound < bestNode.lowerBound);
System.err.printf("%n");
currentNode = pq.poll();
} while (currentNode != null && currentNode.lowerBound < bestNode.lowerBound);
// output suitable for gnuplot
// set style data vector
System.out.printf("# %.0f%n", bestNode.lowerBound);
int j = 0;
do {
int i = bestNode.parent[j];
System.out.printf("%f\t%f\t%f\t%f%n", x[j], y[j], x[i] - x[j], y[i] - y[j]);
j = i;
} while (j != 0);
}

private Node exclude(Node node, int i, int j) {
Node child = new Node();
child.excluded = node.excluded.clone();
child.excluded[i] = node.excluded[i].clone();
child.excluded[j] = node.excluded[j].clone();
child.excluded[i][j] = true;
child.excluded[j][i] = true;
computeHeldKarp(child);
return child;
}

private void computeHeldKarp(Node node) {
node.pi = new double[n];
node.lowerBound = Double.MIN_VALUE;
node.degree = new int[n];
node.parent = new int[n];
double lambda = 0.1;
while (lambda > 1e-06) {
double previousLowerBound = node.lowerBound;
computeOneTree(node);
if (!(node.lowerBound < bestNode.lowerBound)) return;
if (!(node.lowerBound < previousLowerBound)) lambda *= 0.9;
int denom = 0;
for (int i = 1; i < n; i++) {
int d = node.degree[i] - 2;
denom += d * d;
}
if (denom == 0) return;
double t = lambda * node.lowerBound / denom;
for (int i = 1; i < n; i++) node.pi[i] += t * (node.degree[i] - 2);
}
}

private void computeOneTree(Node node) {
// compute adjusted costs
node.lowerBound = 0.0;
Arrays.fill(node.degree, 0);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) costWithPi[i][j] = node.excluded[i][j] ? Double.MAX_VALUE : cost[i][j] + node.pi[i] + node.pi[j];
}
int firstNeighbor;
int secondNeighbor;
// find the two cheapest edges from 0
if (costWithPi[0][2] < costWithPi[0][1]) {
firstNeighbor = 2;
secondNeighbor = 1;
} else {
firstNeighbor = 1;
secondNeighbor = 2;
}
for (int j = 3; j < n; j++) {
if (costWithPi[0][j] < costWithPi[0][secondNeighbor]) {
if (costWithPi[0][j] < costWithPi[0][firstNeighbor]) {
secondNeighbor = firstNeighbor;
firstNeighbor = j;
} else {
secondNeighbor = j;
}
}
}
addEdge(node, 0, firstNeighbor);
Arrays.fill(node.parent, firstNeighbor);
node.parent[firstNeighbor] = 0;
// compute the minimum spanning tree on nodes 1..n-1
double[] minCost = costWithPi[firstNeighbor].clone();
for (int k = 2; k < n; k++) {
int i;
for (i = 1; i < n; i++) {
if (node.degree[i] == 0) break;
}
for (int j = i + 1; j < n; j++) {
if (node.degree[j] == 0 && minCost[j] < minCost[i]) i = j;
}
addEdge(node, node.parent[i], i);
for (int j = 1; j < n; j++) {
if (node.degree[j] == 0 && costWithPi[i][j] < minCost[j]) {
minCost[j] = costWithPi[i][j];
node.parent[j] = i;
}
}
}
addEdge(node, 0, secondNeighbor);
node.parent[0] = secondNeighbor;
node.lowerBound = Math.rint(node.lowerBound);
}

private void addEdge(Node node, int i, int j) {
double q = node.lowerBound;
node.lowerBound += costWithPi[i][j];
node.degree[i]++;
node.degree[j]++;
}
}

class Node {
public boolean[][] excluded;
// Held--Karp solution
public double[] pi;
public double lowerBound;
public int[] degree;
public int[] parent;
}

class NodeComparator implements Comparator<Node> {
public int compare(Node a, Node b) {
return Double.compare(a.lowerBound, b.lowerBound);
}
}

关于algorithm - 优化的 TSP 算法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7159259/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com