Wenn Sie Ihre Pfade tatsächlich vom kürzesten zum längsten Pfad ordnen möchten, ist es weitaus besser, einen modifizierten A * - oder Dijkstra-Algorithmus zu verwenden . Mit einer geringfügigen Änderung gibt der Algorithmus so viele der möglichen Pfade zurück, wie Sie möchten, und zwar in der Reihenfolge des kürzesten Pfades zuerst. Wenn Sie also wirklich alle möglichen Pfade vom kürzesten zum längsten geordnet haben möchten, ist dies der richtige Weg.
Wenn Sie eine A * -basierte Implementierung wünschen, die alle vom kürzesten zum längsten geordneten Pfade zurückgeben kann, wird dies im Folgenden erreicht. Es hat mehrere Vorteile. Zunächst einmal ist es effizient beim Sortieren vom kürzesten zum längsten. Außerdem wird jeder zusätzliche Pfad nur bei Bedarf berechnet. Wenn Sie also vorzeitig anhalten, weil Sie nicht jeden einzelnen Pfad benötigen, sparen Sie Verarbeitungszeit. Außerdem werden Daten für nachfolgende Pfade jedes Mal wiederverwendet, wenn der nächste Pfad berechnet wird, um die Effizienz zu steigern. Wenn Sie einen gewünschten Pfad finden, können Sie ihn vorzeitig abbrechen, um Rechenzeit zu sparen. Insgesamt sollte dies der effizienteste Algorithmus sein, wenn Sie nach Pfadlänge sortieren möchten.
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;
}
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);
}
}
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<>();
addAdjacency(adjacency, 1,2,1,5,1);
addAdjacency(adjacency, 2,1,1,3,1,4,1,5,1);
addAdjacency(adjacency, 3,2,1,5,1);
addAdjacency(adjacency, 4,2,1);
addAdjacency(adjacency, 5,1,1,2,1,3,1);
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));
}
}
Die Ausgabe des obigen Codes lautet wie folgt:
[1, 2, 4]
[1, 5, 2, 4]
[1, 5, 3, 2, 4]
Beachten Sie, dass bei jedem Aufruf nextShortestPath()
bei Bedarf der nächst kürzere Pfad für Sie generiert wird. Es werden nur die zusätzlichen erforderlichen Schritte berechnet und keine alten Pfade zweimal durchlaufen. Wenn Sie entscheiden, dass Sie nicht alle Pfade benötigen und die Ausführung vorzeitig beenden, haben Sie sich außerdem erhebliche Rechenzeit gespart. Sie berechnen nur bis zur Anzahl der benötigten Pfade und nicht mehr.
Schließlich sollte angemerkt werden, dass die A * - und Dijkstra-Algorithmen einige geringfügige Einschränkungen aufweisen, obwohl ich nicht glaube, dass dies Auswirkungen auf Sie haben würde. Es funktioniert nämlich nicht richtig in einem Diagramm mit negativen Gewichten.
Hier ist ein Link zu JDoodle, über den Sie den Code selbst im Browser ausführen und sehen können, wie er funktioniert. Sie können das Diagramm auch ändern, um anzuzeigen, dass es auch in anderen Diagrammen funktioniert: http://jdoodle.com/a/ukx