web-dev-qa-db-fra.com

Recherche de tous les chemins les plus courts entre deux nœuds dans un graphe non dirigé non pondéré

J'ai besoin d'aide pour trouver tous les chemins les plus courts entre deux nœuds dans un graphe non pondéré non dirigé

Je suis capable de trouver l’un des chemins les plus courts en utilisant BFS, mais je n’ai pas encore trouvé comment l’avoir trouvé et imprimé. 

Toute idée de l'algorithme/pseudocode que je pourrais utiliser?

30
user1946334

Comme mise en garde, rappelez-vous qu'il peut exister exponentiellement de nombreux chemins les plus courts entre deux nœuds d'un graphe. Tout algorithme pour cela prendra potentiellement un temps exponentiel.

Cela dit, il existe une modification relativement simple de BFS que vous pouvez utiliser comme étape de prétraitement pour accélérer la génération de tous les chemins possibles. Rappelez-vous que lorsque BFS s'exécute, il procède vers l'extérieur en "couches", en obtenant un seul chemin le plus court vers tous les nœuds situés à la distance 0, puis à la distance 1, puis à la distance 2, etc. du nœud de départ doit être connecté par un Edge à un nœud situé à une distance k du nœud de départ. BFS découvre ce nœud à la distance k + 1 en trouvant un chemin de longueur k vers un nœud à la distance k, puis en l'étendant d'un bord.

Si votre objectif est de trouver tous chemins les plus courts, vous pouvez modifier BFS en étendant every chemin vers un nœud situé à une distance k de tous les nœuds situés à la distance k + 1 auxquels ils se connectent, plutôt que de choisir un seul bord. Pour ce faire, modifiez BFS de la manière suivante: chaque fois que vous traitez un Edge en ajoutant son noeud final dans la file d'attente de traitement, ne marquez pas immédiatement ce nœud comme étant en cours d'exécution. Au lieu de cela, insérez ce nœud dans la file d'attente annotée avec laquelle vous avez suivi Edge pour y accéder. Cela vous permettra éventuellement d'insérer le même nœud dans la file plusieurs fois s'il existe plusieurs nœuds qui y sont liés. Lorsque vous supprimez un nœud de la file d'attente, vous le marquez comme étant en cours d'exécution et ne l'insérez plus jamais dans la file d'attente. De même, plutôt que de stocker un seul pointeur parent, vous allez stocker plusieurs pointeurs parents, un pour chaque nœud lié à ce nœud.

Si vous faites ce BFS modifié, vous obtiendrez un DAG dans lequel chaque nœud sera soit le nœud de départ et n'aura pas de bord sortant, soit sera à une distance k + 1 du nœud de départ et aura un pointeur sur chaque nœud de distance k à laquelle il est connecté. À partir de là, vous pouvez reconstruire tous les chemins les plus courts d'un noeud au noeud de départ en listant tous les chemins possibles de votre noeud de choix jusqu'au noeud de départ dans le DAG. Cela peut être fait de manière récursive:

  • Il n'y a qu'un seul chemin du nœud de départ à lui-même, à savoir le chemin vide.
  • Pour tout autre nœud, les chemins peuvent être trouvés en suivant chaque Edge sortant, puis en étendant récursivement ces chemins pour renvoyer un chemin au nœud de départ.

J'espère que cela t'aides!

29
templatetypedef

@templatetypedef est correct, mais il a oublié de mentionner que le contrôle de distance doit être effectué avant l'ajout de liens parent au noeud. Cela signifie qu’ils gardent la distance de la source dans chacun des nœuds et incrémentent de un la distance pour les enfants. Nous devons ignorer cette augmentation et l’ajout des parents si l’enfant a déjà été visité et a une distance inférieure.

public void addParent(Node n) {
    // forbidding the parent it its level is equal to ours
    if (n.level == level) {
        return;
    }
    parents.add(n);

    level = n.level + 1;
}

L'implémentation complète de Java peut être trouvée par le lien suivant.

http://ideone.com/UluCBb

4
bitec

J'ai rencontré le même problème en résolvant ceci https://oj.leetcode.com/problems/Word-ladder-ii/

La façon dont j'ai essayé de traiter est d'abord de trouver la distance la plus courte en utilisant BFS, disons que la distance la plus courte est d. Maintenant, appliquez DFS et dans l'appel DFS récursif, n'allez pas au-delà du niveau récursif d. 

Cependant, cela pourrait finir par explorer tous les chemins mentionnés par @templatetypedef.

2
rgaut

Tout d'abord, recherchez la distance jusqu'au début de tous les nœuds à l'aide de la recherche en largeur d'abord.

(s'il y a beaucoup de nœuds, vous pouvez utiliser A * et vous arrêter lorsque le haut de la file d'attente contient distance-to-start > distance-to-start(end-node). Cela vous donnera tous les nœuds appartenant au chemin le plus court)

Ensuite, il suffit de revenir en arrière du noeud final. Chaque fois qu'un nœud est connecté à deux nœuds (ou plus) avec une distance de démarrage inférieure, vous vous branchez dans deux chemins (ou plus).

Étape 1: Parcourez le graphique à partir de la source à l'aide de BFS et attribuez à chaque nœud la distance minimale par rapport à la source.

Étape 2: la distance attribuée au nœud cible est la plus courte des longueurs.

Étape 3: À partir de la source, effectuez une recherche DFS sur tous les chemins où la distance minimale est augmentée une par une jusqu'à ce que le noeud cible soit atteint ou que la longueur la plus courte soit atteinte. Imprimez le chemin chaque fois que le nœud cible est atteint.

0
Joe C

templatetypedef votre réponse était très bonne, merci beaucoup pour celle-là (!!), mais elle a manqué un point:

Si vous avez un graphique comme celui-ci:

 A-B-C-E-F 
 | | 
 RÉ------

Imaginons maintenant que je veux ce chemin: 

A -> E.

Il va s'étendre comme ceci:

  A-> B -> D-> C -> F -> E.

Le problème ici est que Vous aurez F comme parent de E, mais

  A-> B-> D-> F-E 
est plus long que

  A-> B-> C-> E.
Vous devrez suivre les distances des parents que vous ajoutez si heureusement.

0
Firespy