web-dev-qa-db-fra.com

Trouver tous les chemins entre deux nœuds de graphe

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

59
Paul

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.

49
amit

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();
        }
    }
}
10
Omer Hassan

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 .

9
araruna

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]
4
yanis

find_paths [s, t, d, k]

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ù:

  • s est le noeud de départ
  • t est le noeud cible
  • d est la profondeur maximale de recherche
  • k est le nombre de chemins à trouver

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]

Fonction d'assistance

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()

Fonction principale

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:

  • le pseudo-code ci-dessus est un mélange de langages - mais qui ressemble le plus à python (puisque je ne faisais que le coder)). Un copier-coller strict ne fonctionnera pas.
  • [] _ est une liste non initialisée, remplacez-la par l'équivalent pour le langage de programmation de votre choix
  • paths_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.
  • ici 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.
  • cela suppose que vous ayez pré-traité pour supprimer les "boucles", les cycles et les arêtes multiples
2
SumNeuron

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

1
richmb

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.

1
jpalecek

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.

0
Zruty

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/

0
Attila Karoly