您可以修改我的原始代码以按照您的要求将所有路径作为列表返回。只是不要提前返回代码。这不会按路径长度排序,但是,如果需要,则需要 A*。
public List<List<Integer>> searchHops(int from, int to, Set<Integer> seen) {
seen.add(from);
if (from == to) {
final List<List<Integer>> newList = new ArrayList<>();
newList.add(new ArrayList<>(Arrays.asList(from)));
return newList;
}
List<List<Integer>> allPaths = null;
for (int neighbor : getNeighbors(from)) {
if (!seen.contains(neighbor)) {
List<List<Integer>> results = searchHops(neighbor, to, new HashSet<>(seen));
if (results != null) {
for(List<Integer> result : results) {
result.add(0, from);
if( allPaths != null )
allPaths.add(result);
}
if( allPaths == null )
allPaths = results;
}
}
}
return allPaths;
}
如果您真的关心从最短路径到最长路径的路径排序,那么使用 A* 会好得多。 A* 将按照最短路径的顺序返回尽可能多的可能路径。因此,如果您真正想要的是从最短到最长排序的所有可能路径,那么您仍然需要 A* 算法。如果您关心从最短到最长的顺序,我上面建议的代码将比它需要的要慢得多,更不用说会占用比您想要的更多空间以便一次存储所有可能的路径。
既然您表示您首先关心最短路径,并且可能想要检索 N 最短路径,那么您绝对应该在这里使用 A*。
如果您想要一个基于 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. Otherwise
//They are exactly the same as the example you gave in your question
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() 时,它都会根据需要为您生成下一条最短路径。它只计算所需的额外步骤,不会遍历任何旧路径两次。此外,如果您决定不需要所有路径并提前结束执行,您就可以节省大量的计算时间。你只计算你需要的路径数量,而不是更多。
如果您有某种启发式方法可以帮助您估算路径成本,则覆盖 predictCost() 方法并将其放在那里。您提到您的节点也有与之关联的空间坐标。在这种情况下,一个好的启发式方法是两个节点之间的欧几里得距离(它们之间的直线距离)。然而,这完全是一种选择,只有在您在计算所有可能的路径之前退出时才有助于缩短计算时间。
最后应该指出的是,A* 和 Dijkstra 算法确实有一些小的限制,尽管我认为它不会影响你。也就是说,它不能在权重为负的图上正常工作。
这里是 JDoodle 的链接,您可以在该链接中自己在浏览器中运行代码并查看其运行情况。您还可以更改图表以显示它也适用于其他图表:http://jdoodle.com/a/ukx