我试图在给定的一对顶点之间找到最短的一对边缘不相交的路径,我跟着这个algorithm,我猜这通常是Suurballe's algorithm
。
以下是算法:
在该维基百科中,第一步是找到源节点和目标节点之间的最短路径,我能够正确地执行它,如下面的代码所示,使用Dijkstra Algorithm
-
public class DijkstraAlgorithm {
private static final Graph.Edge[] GRAPH = {
new Graph.Edge("A", "G", 8),
new Graph.Edge("A", "B", 1),
new Graph.Edge("A", "E", 1),
new Graph.Edge("B", "C", 1),
new Graph.Edge("B", "E", 1),
new Graph.Edge("B", "F", 2),
new Graph.Edge("C", "G", 1),
new Graph.Edge("C", "D", 1),
new Graph.Edge("D", "F", 1),
new Graph.Edge("D", "Z", 1),
new Graph.Edge("E", "F", 4),
new Graph.Edge("F", "Z", 4),
new Graph.Edge("G", "Z", 2),
};
private static final String START = "A";
private static final String END = "Z";
public static void main(String[] args) {
Graph g = new Graph(GRAPH);
g.dijkstra(START);
// print the shortest path using Dijkstra algorithm
g.printPath(END);
// g.printAllPaths();
}
}
class Graph {
private final Map<String, Vertex> graph; // mapping of vertex names to Vertex objects, built from a set of Edges
/** One edge of the graph (only used by Graph constructor) */
public static class Edge {
public final String v1, v2;
public final int dist;
public Edge(String v1, String v2, int dist) {
this.v1 = v1;
this.v2 = v2;
this.dist = dist;
}
}
/** One vertex of the graph, complete with mappings to neighbouring vertices */
public static class Vertex implements Comparable<Vertex> {
public final String name;
public int dist = Integer.MAX_VALUE; // MAX_VALUE assumed to be infinity
public Vertex previous = null;
public final Map<Vertex, Integer> neighbours = new HashMap<Vertex, Integer>();
public Vertex(String name) {
this.name = name;
}
private void printPath() {
if (this == this.previous) {
System.out.printf("%s", this.name);
} else if (this.previous == null) {
System.out.printf("%s(unreached)", this.name);
} else {
this.previous.printPath();
System.out.printf(" -> %s(%d)", this.name, this.dist);
}
}
public int compareTo(Vertex other) {
if (dist==other.dist)
return name.compareTo(other.name);
return Integer.compare(dist, other.dist);
}
}
/** Builds a graph from a set of edges */
public Graph(Edge[] edges) {
graph = new HashMap<String, Vertex>(edges.length);
//one pass to find all vertices
for (Edge e : edges) {
if (!graph.containsKey(e.v1))
graph.put(e.v1, new Vertex(e.v1));
if (!graph.containsKey(e.v2))
graph.put(e.v2, new Vertex(e.v2));
}
//another pass to set neighbouring vertices
for (Edge e : edges) {
graph.get(e.v1).neighbours.put(graph.get(e.v2), e.dist);
graph.get(e.v2).neighbours.put(graph.get(e.v1), e.dist); // also for an undirected graph
}
}
/** Runs dijkstra using a specified source vertex */
public void dijkstra(String startName) {
if (!graph.containsKey(startName)) {
System.err.printf("Graph doesn't contain start vertex \"%s\"\n", startName);
return;
}
final Vertex source = graph.get(startName);
NavigableSet<Vertex> q = new TreeSet<Vertex>();
// set-up vertices
for (Vertex v : graph.values()) {
v.previous = v == source ? source : null;
v.dist = v == source ? 0 : Integer.MAX_VALUE;
q.add(v);
}
dijkstra(q);
}
/** Implementation of dijkstra's algorithm using a binary heap. */
private void dijkstra(final NavigableSet<Vertex> q) {
Vertex u, v;
while (!q.isEmpty()) {
u = q.pollFirst(); // vertex with shortest distance (first iteration will return source)
if (u.dist == Integer.MAX_VALUE)
break; // we can ignore u (and any other remaining vertices) since they are unreachable
//look at distances to each neighbour
for (Map.Entry<Vertex, Integer> a : u.neighbours.entrySet()) {
v = a.getKey(); //the neighbour in this iteration
final int alternateDist = u.dist + a.getValue();
if (alternateDist < v.dist) { // shorter path to neighbour found
q.remove(v);
v.dist = alternateDist;
v.previous = u;
q.add(v);
}
}
}
}
/** Prints a path from the source to the specified vertex */
public void printPath(String endName) {
if (!graph.containsKey(endName)) {
System.err.printf("Graph doesn't contain end vertex \"%s\"\n", endName);
return;
}
graph.get(endName).printPath();
System.out.println();
}
/** Prints the path from the source to every vertex (output order is not guaranteed) */
public void printAllPaths() {
for (Vertex v : graph.values()) {
v.printPath();
System.out.println();
}
}
}
现在我不得不执行该算法中的剩余步骤,这样我就可以在给定的顶点对之间获得最短的一对边缘不相交的路径。
从节点A到节点Z的最短路径为ABCDZ
,而最短路径为ABCGZ
和AEBFDZ
。
答案 0 :(得分:2)
我不会编写代码,但我可以向您解释如何解决问题,并提供基本知识,为什么它可以工作。我将对您正在搜索其间路径的两个节点使用术语source
和sink
。
首先,为什么它有效。正如您在示例中所注意到的那样,最短的路径对不一定包含其中的单个最短路径。此外,如果您找到最短路径并将其删除,您可能会发现自己处于某种情况,当存在从源到接收器的无路径时,该路径与您刚找到的最短路径不相交。所以我们需要找到一种方法来改变我们在构建第二条路径时找到的第一条路径。事实上,这正是我们通过添加负权重而实现的目标。
让我们用你的例子来考虑它。你运行了第一个dijkstra,它为你找到了路径ABCDZ
。现在,当我们运行第二个dijkstra时,我们希望找到另一个路径AEBFDZ
,同时将ABCDZ
修改为ABCGZ
。以下是它的发生方式:在第一个dijstra之后,你反转该路径中的所有边缘并否定它们的权重。例如,权重为1的边A->B
变为边B->A
,权重为-1。它应该很容易 - 你已经有了恢复路径的逻辑。在恢复路径时,删除它所包含的边缘,然后将其反向添加,并使用否定权重。
对于您的特定示例,我们只关心权重为1的边C->D
。在我们运行第一个dijkstra之后,它被反转,并成为权重为-1的边D->C
。现在,当我们尝试找到第二条最短路径时,它会找到路径AEFDCGZ
。请注意,它包含我们刚刚添加的边D->C
。在第二条路径中使用负重的边缘是什么意思?好吧,试着在纸上画出来,你会发现第一条路径就像A-B-C ----- D-Z
,第二条路径就像A-E-F-D ---- C-G-Z
。如果你绘制它,你会注意到你可以从两个路径中删除该边缘(C-D
),并交换它们的尾部。当你这样做时,路径权重的总和不会改变(因为第一条路径的边缘具有正权重,第二条路径具有负权重),导致两条路径A-B-C-G-Z
和{{1你正在寻找的两条路径。
现在让我们来看看如何明智地解决这个问题。您将需要三个独立的阶段。我可以为你编写代码,但我相信你会通过自己攻击来学习更多的东西。
阶段1.您需要实现反转边缘的逻辑。正如我所说,这非常简单。您只需在第一个dijkstra之后恢复路径(您已经有了这样做的逻辑),并且对于该路径中的每个边缘,您将其从图形中删除,然后将其反转并反转。
阶段2.您需要一个能够在负权重的图形中找到最短路径的函数。非常重要的是要注意dijkstra 不在负权重的图上工作。所以这里我们有两种方法:方法(a)是使用bellman-ford算法。它比dijkstra慢,但确实如此:在负权重的图中找到最短路径。方法(b)鲜为人知,但更快,并且利用了我们引入这些负权重的方式。它的工作方式如下:
首先,当您运行第一个dijkstra时,不要在到达接收器时停止,继续遍历图形并为节点分配距离。在第一个dijskstra的末尾,每个节点将与分配给它的源有一定距离。将这些值复制到一个名为A-E-F-D-Z
的新数组中。我们将这些价值观称为潜力&#39;
删除属于第一条路径的边缘并添加其反转和否定副本(执行第1阶段)
之后,将图表中每条边的权重pt
更改为w[i, j]
,其中w'[i, j] = w[i, j] + pt[i] - pt[j]
是顶点pt[i]
的潜力。有趣的是,权重为i
的新图形将具有两个属性:它将不具有任何负权重,并且原始图形中源和接收器之间的最短路径将是新图形中源和接收器之间的最短路径(反之亦然)。现在你可以在这个新图形中运行dijkstra(因为它没有负权重),并确保你在其中找到的最短路径对应于原始图形中的最短路径。
现在,您应该可以为示例图获取路径w'
和A-B-C-D-Z
。在进入第3阶段之前一定要进入这个阶段。
第3阶段:当您完成第2阶段时,最后一个阶段将是实现正确的路径恢复。给定第2阶段的两条路径,您需要找到第二条路径中使用的所有负权重,并重新连接路径之间的边。还有一个更简单的替代方案,即每个边缘跟踪它是否属于两条路径之一。如果将某个边添加到具有上边缘的两个路径之一,则将其标记为属于其中一个路径,并且当您使用负权重添加它时,将其标记为不属于。在您的示例中,当您找到第一条路径时,边A-E-F-D-C-G-Z
将首先被标记为属于其中一条路径,然后当您找到第二条路径并向其添加C-D
时,边D-C
将被标记为属于路径。当您有这样的标记时,您可以通过以任何顺序遍历从源到接收器的标记边缘来恢复两条路径。
dijkstra
方法也发生了变化。也就是说,它现在在计算alternativeDist
时使用势。我恢复路径的方式似乎有点过于复杂,可能更简单。我目前存储了属于答案的所有边的树集。如果我试图添加一个边缘,其反转已经在答案中,我将其从答案中移除(它是一个否定边缘的遍历)。然后我只根据该树集恢复答案。
import java.util.*;
public class DijkstraAlgorithm {
private static final Graph.Edge[] GRAPH = {
new Graph.Edge("A", "G", 8),
new Graph.Edge("A", "B", 1),
new Graph.Edge("A", "E", 1),
new Graph.Edge("B", "C", 1),
new Graph.Edge("B", "E", 1),
new Graph.Edge("B", "F", 2),
new Graph.Edge("C", "G", 1),
new Graph.Edge("C", "D", 1),
new Graph.Edge("D", "F", 1),
new Graph.Edge("D", "Z", 1),
new Graph.Edge("E", "F", 4),
new Graph.Edge("F", "Z", 4),
new Graph.Edge("G", "Z", 2),
};
private static final String START = "A";
private static final String END = "Z";
public static void main(String[] args) {
Graph g = new Graph(GRAPH);
g.dijkstra(START);
g.restorePath(END);
g.revertEdges(END);
g.assignPotentials();
g.dijkstra(START);
g.restorePath(END);
g.printPaths(START, END);
}
}
class Graph {
private final Map<String, Vertex> graph; // mapping of vertex names to Vertex objects, built from a set of Edges
/** One edge of the graph (only used by Graph constructor) */
public static class Edge implements Comparable<Edge> {
public final String v1, v2;
public final int dist;
public Edge(String v1, String v2, int dist) {
this.v1 = v1;
this.v2 = v2;
this.dist = dist;
}
public int compareTo(Edge other) {
if (v1.equals(other.v1))
return v2.compareTo(other.v2);
return v1.compareTo(other.v1);
}
}
private TreeSet<Edge> answer = new TreeSet<Edge>(); // stores all the edges in the answer
/** One vertex of the graph, complete with mappings to neighbouring vertices */
public static class Vertex implements Comparable<Vertex> {
public final String name;
public int potential = 0; // is assigned to dist before the second dijkstra
public int dist = Integer.MAX_VALUE; // MAX_VALUE assumed to be infinity
public Vertex previous = null;
public final Map<Vertex, Integer> neighbours = new HashMap<Vertex, Integer>();
public Vertex(String name) {
this.name = name;
}
public int compareTo(Vertex other) {
if (dist==other.dist)
return name.compareTo(other.name);
return Integer.compare(dist, other.dist);
}
}
/** Builds a graph from a set of edges */
public Graph(Edge[] edges) {
graph = new HashMap<String, Vertex>(edges.length);
//one pass to find all vertices
for (Edge e : edges) {
if (!graph.containsKey(e.v1))
graph.put(e.v1, new Vertex(e.v1));
if (!graph.containsKey(e.v2))
graph.put(e.v2, new Vertex(e.v2));
}
//another pass to set neighbouring vertices
for (Edge e : edges) {
graph.get(e.v1).neighbours.put(graph.get(e.v2), e.dist);
graph.get(e.v2).neighbours.put(graph.get(e.v1), e.dist); // also for an undirected graph
}
}
/** Runs dijkstra using a specified source vertex */
public void dijkstra(String startName) {
if (!graph.containsKey(startName)) {
System.err.printf("Graph doesn't contain start vertex \"%s\"\n", startName);
return;
}
final Vertex source = graph.get(startName);
NavigableSet<Vertex> q = new TreeSet<Vertex>();
// set-up vertices
for (Vertex v : graph.values()) {
v.previous = v == source ? source : null;
v.dist = v == source ? 0 : Integer.MAX_VALUE;
q.add(v);
}
dijkstra(q);
}
/** Implementation of dijkstra's algorithm using a binary heap. */
private void dijkstra(final NavigableSet<Vertex> q) {
Vertex u, v;
while (!q.isEmpty()) {
u = q.pollFirst(); // vertex with shortest distance (first iteration will return source)
if (u.dist == Integer.MAX_VALUE)
break; // we can ignore u (and any other remaining vertices) since they are unreachable
//look at distances to each neighbour
for (Map.Entry<Vertex, Integer> a : u.neighbours.entrySet()) {
v = a.getKey(); //the neighbour in this iteration
final int alternateDist = u.dist + a.getValue() + u.potential - v.potential;
if (alternateDist < v.dist) { // shorter path to neighbour found
q.remove(v);
v.dist = alternateDist;
v.previous = u;
q.add(v);
}
}
}
}
/** Prints a path from the source to the specified vertex */
public void revertEdges(String endName) {
Vertex v = graph.get(endName);
while (v.previous != null && v.previous != v) {
Vertex w = v.previous;
int weight = v.neighbours.get(w);
v.neighbours.remove(w);
w.neighbours.remove(v);
v.neighbours.put(w, - weight);
v = w;
}
}
public void assignPotentials() {
for (Vertex v : graph.values()) {
v.potential = v.dist;
}
}
/** Stores the path found by dijkstra into the answer */
public void restorePath(String endName) {
Vertex v = graph.get(endName);
while (v.previous != null && v.previous != v) {
String from = v.previous.name;
String to = v.name;
if (answer.contains(new Edge(to, from, 0))) {
answer.remove(new Edge(to, from, 0));
}
else {
answer.add(new Edge(from, to, 0));
}
v = v.previous;
}
}
/** Restores and prints one path based on `answer` dictionary, and removes the edges restored from the answer */
public void printOnePath(String startName, String endName) {
Vertex from = graph.get(startName);
Vertex to = graph.get(endName);
Vertex cur = from;
do {
System.out.printf("%s -> ", cur.name);
Edge e = answer.ceiling(new Edge(cur.name, "", 0));
answer.remove(e);
cur = graph.get(e.v2);
} while (cur != to);
System.out.println(to.name);
}
/** Restores and prints paths based on `answer` dicrionary */
public void printPaths(String startName, String endName) {
printOnePath(startName, endName);
printOnePath(startName, endName);
}
}