Je travaille sur une implémentation de l'algorithme de Dijkstras afin de récupérer le chemin le plus court entre des nœuds interconnectés sur un réseau de routes. J'ai l'implémentation de travail. Il renvoie tous les chemins les plus courts vers tous les nœuds lorsque je passe le nœud de départ dans l'algorithme.
Ma question: comment récupérer tous les chemins possibles de Node A dire Node G ou même tous les chemins possibles de Node A et retour à Node A
Trouver tous les chemins possibles est un problème difficile, car il existe un nombre exponentiel de chemins simples. Même trouver le kème chemin le plus court [ou le plus long chemin] est NP-Hard .
Une solution possible pour rechercher tous les chemins [ou tous les chemins d’une certaine longueur] de s
à t
est BFS , sans conserver un ensemble visited
, ou pour la version pondérée - vous voudrez peut-être utiliser recherche de coût uniforme
Notez que dans chaque graphe comportant des cycles [ce n’est pas un DAG ], il peut y avoir un nombre infini de chemins entre s
à t
.
J'ai implémenté une version dans laquelle tous les chemins possibles d'un nœud à un autre sont trouvés, mais elle ne compte pas les "cycles" possibles (le graphe que j'utilise est cyclique). Donc, fondamentalement, aucun noeud n'apparaîtra deux fois dans le même chemin. Et si le graphique était acyclique, alors je suppose que vous pourriez dire qu'il semble trouver tous les chemins possibles entre les deux nœuds. Il semble fonctionner parfaitement, et pour une taille de graphique d'environ 150, il tourne presque instantanément sur ma machine, bien que je sois sûr que le temps d'exécution doit être quelque chose comme exponentiel. le graphique s'agrandit.
Voici du code Java) qui illustre ce que j'avais implémenté. Je suis sûr qu'il doit exister des moyens plus efficaces ou plus élégants de le faire.
Stack connectionPath = new Stack();
List<Stack> connectionPaths = new ArrayList<>();
// Push to connectionsPath the object that would be passed as the parameter 'node' into the method below
void findAllPaths(Object node, Object targetNode) {
for (Object nextNode : nextNodes(node)) {
if (nextNode.equals(targetNode)) {
Stack temp = new Stack();
for (Object node1 : connectionPath)
temp.add(node1);
connectionPaths.add(temp);
} else if (!connectionPath.contains(nextNode)) {
connectionPath.Push(nextNode);
findAllPaths(nextNode, targetNode);
connectionPath.pop();
}
}
}
Je vais vous donner une version (un peu petite) (bien que compréhensible, je pense) d'une preuve scientifique que vous ne pouvez pas faire cela dans un laps de temps réalisable.
Ce que je vais prouver, c'est que la complexité temporelle pour énumérer tous les chemins simples entre deux nœuds sélectionnés et distincts (par exemple, s
et t
) dans un graphe arbitraire G
est pas polynomial. Notez que, étant donné que nous ne nous soucions que de la quantité de chemins entre ces nœuds, les coûts Edge ne sont pas importants.
Bien sûr, si le graphique a des propriétés bien sélectionnées, cela peut être facile. Je considère cependant le cas général.
Supposons que nous ayons un algorithme polynomial qui répertorie tous les chemins simples entre s
et t
.
Si G
est connecté, la liste n'est pas vide. Si G
n'est pas et que s
et t
appartiennent à des composants différents, il est très facile de lister tous les chemins entre eux, car il n'y en a aucun! S'ils se trouvent dans le même composant, nous pouvons prétendre que tout le graphique ne comprend que ce composant. Supposons donc que G
soit effectivement connecté.
Le nombre de chemins répertoriés doit alors être polynomial, sinon l'algorithme ne pourrait pas me les renvoyer tous. S'il les énumère tous, il me faut le plus long, donc c'est là. Ayant la liste des chemins, une procédure simple peut être appliquée pour me désigner, qui est ce chemin le plus long.
Nous pouvons montrer (bien que je ne puisse pas penser à un moyen cohésif de le dire) que ce plus long chemin doit traverser tous les sommets de G
. Ainsi, nous venons de trouver un chemin hamiltonien avec une procédure polynomiale! Mais c'est un problème NP-difficile bien connu.
Nous pouvons alors conclure que cet algorithme polynomial que nous pensions avoir est très peu probable d'exister, à moins que P = NP .
Here est un algorithme recherchant et imprimant tous les chemins de s à t en utilisant la modification de DFS. La programmation dynamique peut également être utilisée pour trouver le nombre de tous les chemins possibles. Le pseudo-code ressemblera à ceci:
AllPaths(G(V,E),s,t)
C[1...n] //array of integers for storing path count from 's' to i
TopologicallySort(G(V,E)) //here suppose 's' is at i0 and 't' is at i1 index
for i<-0 to n
if i<i0
C[i]<-0 //there is no path from vertex ordered on the left from 's' after the topological sort
if i==i0
C[i]<-1
for j<-0 to Adj(i)
C[i]<- C[i]+C[j]
return C[i1]
Cette question est maintenant un peu vieille ... mais je vais lancer mon chapeau dans le ring.
Je trouve personnellement un algorithme de la forme find_paths[s, t, d, k]
utile, où:
Utiliser la forme d'infini de votre langage de programmation pour d
et k
vous donnera tous les chemins§.
§ évidemment si vous utilisez un graphe orienté et que vous voulez tous les chemins non dirigés entre s
et t
, vous aurez pour exécuter cela dans les deux sens:
find_paths[s, t, d, k] <join> find_paths[t, s, d, k]
Personnellement, j'aime bien la récursion, bien que cela puisse parfois être difficile, commençons par définir notre fonction d'assistance:
def find_paths_recursion(graph, current, goal, current_depth, max_depth, num_paths, current_path, paths_found)
current_path.append(current)
if current_depth > max_depth:
return
if current == goal:
if len(paths_found) <= number_of_paths_to_find:
paths_found.append(copy(current_path))
current_path.pop()
return
else:
for successor in graph[current]:
self.find_paths_recursion(graph, successor, goal, current_depth + 1, max_depth, num_paths, current_path, paths_found)
current_path.pop()
Avec cela, la fonction principale est triviale:
def find_paths[s, t, d, k]:
paths_found = [] # PASSING THIS BY REFERENCE
find_paths_recursion(s, t, 0, d, k, [], paths_found)
Tout d’abord, remarquons quelques points:
[]
_ est une liste non initialisée, remplacez-la par l'équivalent pour le langage de programmation de votre choixpaths_found
est passé par référence. Il est clair que la fonction de récursivité ne renvoie rien. Manipulez-le de manière appropriée.graph
assume une forme de structure hashed
. Il existe une multitude de façons d'implémenter un graphique. D'une manière ou d'une autre, graph[vertex]
vous obtient une liste de sommets adjacents dans un graphe dirigé - ajustez en conséquence.Je pense que ce que vous voulez, c'est une forme de l'algorithme Ford – Fulkerson basé sur BFS. Il permet de calculer le débit maximal d’un réseau en recherchant tous les chemins d’augmentation entre deux nœuds.
http://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm
Si vous souhaitez réellement commander vos chemins du chemin le plus court au chemin le plus long, il serait de loin préférable d'utiliser un algorithme A * ou de Dijkstra modifié . Avec une légère modification, l'algorithme renverra en premier autant de chemins possibles que vous le souhaitez, dans l'ordre du chemin le plus court. Donc, si vous voulez vraiment que tous les chemins possibles soient ordonnés du plus court au plus long, alors c'est le chemin à parcourir.
Si vous souhaitez une implémentation basée sur A * capable de renvoyer tous les chemins ordonnés du plus court au plus long, procédez comme suit. Il a plusieurs avantages. Tout d'abord, il est efficace pour trier du plus court au plus long. En outre, chaque chemin supplémentaire est calculé uniquement en cas de besoin. Ainsi, si vous arrêtez tôt car vous n’avez pas besoin de chaque chemin, vous épargnerez du temps de traitement. Il réutilise également les données pour les chemins suivants à chaque fois qu'il calcule le chemin suivant pour en améliorer l'efficacité. Enfin, si vous trouvez le chemin souhaité, vous pouvez abandonner rapidement en économisant du temps de calcul. Globalement, cela devrait être l'algorithme le plus efficace si vous vous souciez du tri par longueur de chemin.
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));
}
}
La sortie du code ci-dessus est la suivante:
[1, 2, 4]
[1, 5, 2, 4]
[1, 5, 3, 2, 4]
Notez que chaque fois que vous appelez nextShortestPath()
, il génère le chemin le plus court suivant pour vous à la demande. Il ne calcule que les étapes supplémentaires nécessaires et ne traverse pas deux fois les anciens chemins. De plus, si vous décidez que vous n'avez pas besoin de tous les chemins et que vous terminez l'exécution plus tôt, vous avez gagné un temps de calcul considérable. Vous ne faites que calculer le nombre de chemins dont vous avez besoin et pas plus.
Enfin, il convient de noter que les algorithmes A * et Dijkstra ont quelques limitations mineures, bien que je ne pense pas que cela vous affecterait. Cela signifie que cela ne fonctionnera pas correctement sur un graphique ayant des pondérations négatives.
Voici un lien vers JDoodle où vous pouvez exécuter le code vous-même dans le navigateur et le voir fonctionner. Vous pouvez également modifier le graphique pour indiquer qu'il fonctionne également sur d'autres graphiques: http://jdoodle.com/a/ukx
Vous ne voulez généralement pas, car il existe un nombre exponentiel d'entre eux dans les graphes non triviaux; si vous voulez vraiment obtenir tous les chemins (simples) ou tous les cycles (simples), il vous suffit d'en trouver un (en parcourant le graphique), puis de revenir en arrière.
Je suppose que vous voulez trouver des chemins "simples" (un chemin est simple si aucun nœud n'y apparaît plus d'une fois, à l'exception peut-être du premier et du dernier).
Comme le problème est NP-difficile, vous pouvez effectuer une variante de la recherche en profondeur d'abord.
Fondamentalement, générez tous les chemins possibles à partir de A et vérifiez s’ils aboutissent à G.
Il y a un article intéressant qui peut répondre à votre question/seulement il affiche les chemins au lieu de les collecter /. Veuillez noter que vous pouvez expérimenter avec les exemples C++/Python dans l'EDI en ligne.
http://www.geeksforgeeks.org/find-paths-given-source-destination/