没有一种算法可以有效地查询两个顶点之间的所有非简单路径。路径数量可能呈指数级增长。假设有一个包含以下边的图:(s,u),(u,v),(v,u),(u,t),其中所有边的长度都为1。现在从 s 到 t 查找所有权重限制为 N 的非简单路径,你会得到以下路径:
- s,u,t
- s,u,v,u,t
- s,u,v,u,v,u,t
- s,u,v,u,v,u,v,u,t
- ...
你可以不断循环[u,v,u],直到达到权重限制。
如果这确实是你想要的,我建议你使用一个简单的标记算法。标记编码了部分路径。标签保留对其前面的标签的引用、标签与之关联的节点的引用以及等于表示的部分路径的总成本的成本。通过创建一个带有成本0的源节点s的标签并将其添加到开放标签队列中来启动该算法。在每次迭代中,从打开队列中轮询一个标签,直到该队列用尽。对于与节点i相关联且具有成本c的已轮询标签L,扩展该标签:对于节点i的每个邻居j,创建一个新标签L',该标签指向标签L,并将其成本设置为c加上边缘权重d_ij。如果新标签L'的成本超过了可用的预算,则丢弃该标签。否则,如果j是目标节点,我们找到了一条新路径,因此将该标签存储起来,以便稍后我们可以恢复该路径。否则,将L'添加到开放标签队列中。以下是该算法的简单实现。
注意:
- 上述标记算法仅在图相对较小、N较低或边权重较高时才能有效工作,因为从s到t的可能路径数量可能会快速增长。
- 通过包括可接受的启发式方法计算完成从给定节点到终端路径所需的最少预算,可以略微提高上述算法的性能。这将允许你修剪一些标签。
- 所有边缘权重都必须大于0。
import org.jgrapht.*;
import org.jgrapht.graph.*;
import java.util.*;
public class NonSimplePaths<V,E> {
public List<GraphPath<V, E>> computeNoneSimplePaths(Graph<V,E> graph, V source, V target, double budget){
GraphTests.requireDirected(graph);
if(source.equals(target))
return Collections.emptyList();
Label start = new Label(null, source, 0);
Queue<Label> openQueue = new LinkedList<>();
List<Label> targetLabels = new LinkedList<>();
openQueue.add(start);
while(!openQueue.isEmpty()){
Label openLabel = openQueue.poll();
for(E e : graph.outgoingEdgesOf(openLabel.node)){
double weight = graph.getEdgeWeight(e);
V neighbor = Graphs.getOppositeVertex(graph, e, openLabel.node);
if(openLabel.cost + weight <= budget){
Label label = new Label(openLabel, neighbor, openLabel.cost + weight);
if(neighbor.equals(target))
targetLabels.add(label);
else
openQueue.add(label);
}
}
}
List<GraphPath<V,E>> paths = new ArrayList<>(targetLabels.size());
for(Label label : targetLabels){
List<V> path = new LinkedList<>();
double pathWeight = label.cost;
do{
path.add(label.node);
label=label.pred;
}while(label != null);
Collections.reverse(path);
paths.add(new GraphWalk<>(graph, path, pathWeight));
}
return paths;
}
private final class Label{
private final Label pred;
private final V node;
private final double cost;
private Label(Label pred, V node, double cost) {
this.pred = pred;
this.node = node;
this.cost = cost;
}
}
public static void main(String[] args){
Graph<String,DefaultWeightedEdge> graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class);
Graphs.addAllVertices(graph, Arrays.asList("s","u","v","t"));
graph.addEdge("s","u");
graph.addEdge("u","t");
graph.addEdge("u","v");
graph.addEdge("v","u");
graph.edgeSet().forEach(e -> graph.setEdgeWeight(e,1.0));
NonSimplePaths<String,DefaultWeightedEdge> nonSimplePaths = new NonSimplePaths<>();
List<GraphPath<String,DefaultWeightedEdge>> paths = nonSimplePaths.computeNoneSimplePaths(graph, "s", "t", 10);
for(GraphPath<String,DefaultWeightedEdge> path : paths)
System.out.println(path+" cost: "+path.getWeight());
}
}
以上示例代码的输出:
[s, u, t] cost: 2.0
[s, u, v, u, t] cost: 4.0
[s, u, v, u, v, u, t] cost: 6.0
[s, u, v, u, v, u, v, u, t] cost: 8.0
[s, u, v, u, v, u, v, u, v, u, t] cost: 10.0
优化上述实现的性能(例如添加可接受的启发式算法),我留给原帖作者自行练习。