给出带有
的有向图我们需要打印从根节点到所有叶子节点的所有路径。这是我遇到这个问题最接近的问题 Find all paths between two graph nodes
答案 0 :(得分:0)
如果您真的关心从最短路径到最长路径的路径排序,那么使用修改后的A *或Dijkstra算法会好得多。稍作修改,算法将按照您想要的最短路径顺序返回尽可能多的可能路径。因此,如果您真正想要的是从最短到最长的所有可能路径,那么这就是要走的路。如果您关心从最短到最长的排序,我上面建议的代码会比它需要的慢很多,更不用说会占用更多的空间然后你想要一次存储每个可能的路径。
如果您希望基于A *的实现能够返回从最短到最长的所有路径,则以下内容将实现此目的。它有几个优点。首先,它从最短到最长的排序是有效的。此外,它仅在需要时计算每个附加路径,因此如果您提前停止,因为您不需要每个路径,则可以节省一些处理时间。它还在每次计算下一个路径时重用后续路径的数据,因此它更有效。最后,如果你找到一些理想的路径,你可以提前中止,节省一些计算时间。总的来说,如果您关心按路径长度排序,这应该是最有效的算法。
import java.util.*;
public class AstarSearch {
private final Map<Integer, Set<Neighbor>> adjacency;
private final int destination;
private final NavigableSet<Step> pending = new TreeSet<>();
public AstarSearch(Map<Integer, Set<Neighbor>> adjacency, int source, int destination) {
this.adjacency = adjacency;
this.destination = destination;
this.pending.add(new Step(source, null, 0));
}
public List<Integer> nextShortestPath() {
Step current = this.pending.pollFirst();
while( current != null) {
if( current.getId() == this.destination )
return current.generatePath();
for (Neighbor neighbor : this.adjacency.get(current.id)) {
if(!current.seen(neighbor.getId())) {
final Step nextStep = new Step(neighbor.getId(), current, current.cost + neighbor.cost + predictCost(neighbor.id, this.destination));
this.pending.add(nextStep);
}
}
current = this.pending.pollFirst();
}
return null;
}
protected int predictCost(int source, int destination) {
return 0; //Behaves identical to Dijkstra's algorithm, override to make it A*
}
private static class Step implements Comparable<Step> {
final int id;
final Step parent;
final int cost;
public Step(int id, Step parent, int cost) {
this.id = id;
this.parent = parent;
this.cost = cost;
}
public int getId() {
return id;
}
public Step getParent() {
return parent;
}
public int getCost() {
return cost;
}
public boolean seen(int node) {
if(this.id == node)
return true;
else if(parent == null)
return false;
else
return this.parent.seen(node);
}
public List<Integer> generatePath() {
final List<Integer> path;
if(this.parent != null)
path = this.parent.generatePath();
else
path = new ArrayList<>();
path.add(this.id);
return path;
}
@Override
public int compareTo(Step step) {
if(step == null)
return 1;
if( this.cost != step.cost)
return Integer.compare(this.cost, step.cost);
if( this.id != step.id )
return Integer.compare(this.id, step.id);
if( this.parent != null )
this.parent.compareTo(step.parent);
if(step.parent == null)
return 0;
return -1;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Step step = (Step) o;
return id == step.id &&
cost == step.cost &&
Objects.equals(parent, step.parent);
}
@Override
public int hashCode() {
return Objects.hash(id, parent, cost);
}
}
/*******************************************************
* Everything below here just sets up your adjacency *
* It will just be helpful for you to be able to test *
* It isnt part of the actual A* search algorithm *
********************************************************/
private static class Neighbor {
final int id;
final int cost;
public Neighbor(int id, int cost) {
this.id = id;
this.cost = cost;
}
public int getId() {
return id;
}
public int getCost() {
return cost;
}
}
public static void main(String[] args) {
final Map<Integer, Set<Neighbor>> adjacency = createAdjacency();
final AstarSearch search = new AstarSearch(adjacency, 1, 4);
System.out.println("printing all paths from shortest to longest...");
List<Integer> path = search.nextShortestPath();
while(path != null) {
System.out.println(path);
path = search.nextShortestPath();
}
}
private static Map<Integer, Set<Neighbor>> createAdjacency() {
final Map<Integer, Set<Neighbor>> adjacency = new HashMap<>();
//This sets up the adjacencies. In this case all adjacencies have a cost of 1, but they dont need to.
addAdjacency(adjacency, 1,2,1,5,1); //{1 | 2,5}
addAdjacency(adjacency, 2,1,1,3,1,4,1,5,1); //{2 | 1,3,4,5}
addAdjacency(adjacency, 3,2,1,5,1); //{3 | 2,5}
addAdjacency(adjacency, 4,2,1); //{4 | 2}
addAdjacency(adjacency, 5,1,1,2,1,3,1); //{5 | 1,2,3}
return Collections.unmodifiableMap(adjacency);
}
private static void addAdjacency(Map<Integer, Set<Neighbor>> adjacency, int source, Integer... dests) {
if( dests.length % 2 != 0)
throw new IllegalArgumentException("dests must have an equal number of arguments, each pair is the id and cost for that traversal");
final Set<Neighbor> destinations = new HashSet<>();
for(int i = 0; i < dests.length; i+=2)
destinations.add(new Neighbor(dests[i], dests[i+1]));
adjacency.put(source, Collections.unmodifiableSet(destinations));
}
}
以上代码的输出如下:
[1, 2, 4]
[1, 5, 2, 4]
[1, 5, 3, 2, 4]
请注意,每次拨打nextShortestPath()
时,都会根据需要为您生成下一条最短路径。它只计算所需的额外步骤,并且不会两次遍历任何旧路径。此外,如果您决定不需要所有路径并尽早结束执行,那么您可以节省大量的计算时间。您只计算所需路径的数量,而不是更多。
最后应该注意的是A *和Dijkstra算法确实有一些小的限制,尽管我认为它不会对你产生影响。即它在具有负权重的图表上无法正常工作。
以下是JDoodle的链接,您可以在浏览器中自行运行代码并查看其是否正常工作。您还可以更改图表以显示其在其他图表上的作用:http://jdoodle.com/a/ukx