2009-06-02 7 views
11

J'ai récemment joint la 3ème version de l'algorithme de Dijkstra pour le plus court chemin de source unique dans mon projet.Quelle est l'implémentation de Dijkstra la plus rapide que vous connaissez (en C++)?

Je me rends compte qu'il existe de nombreuses implémentations différentes qui varient fortement dans les performances et qui varient également dans la qualité du résultat dans les grands graphiques. Avec mon jeu de données (> 100.000 sommets), le temps d'exécution varie de 20 minutes à quelques secondes. Les chemins les plus courts varient également de 1-2%.

Quelle est la meilleure implémentation que vous connaissez?

EDIT: Mes données est un réseau hydraulique, avec 1 à 5 sommets par nœud. C'est comparable à une carte de rue. J'ai fait quelques modifications à un algorithme déjà accéléré (en utilisant une liste triée pour tous les nœuds restants) et maintenant trouver les mêmes résultats dans une fraction de temps. J'ai cherché une telle chose pendant un bon moment. Je me demande si une telle mise en œuvre existe déjà.

Je ne peux pas expliquer les légères différences dans les résultats. Je sais que Dijkstra n'est pas heuristique, mais toutes les implémentations semblent correctes. Les solutions les plus rapides ont les résultats avec des chemins plus courts. J'utilise exclusivement les mathématiques à double précision. J'ai découvert que les différences dans le chemin trouvé sont en fait de ma faute. J'avais inséré une manipulation spéciale pour certains sommets (valable uniquement dans une direction) et oublié dans l'autre implémentation.

MAIS im encore plus surpris que Dijkstra peut être considérablement accéléré par le changement suivant: En général, un algorithme de Dijkstra contient une boucle comme:

MyListType toDoList; // List sorted by smallest distance 
InsertAllNodes(toDoList); 
while(! toDoList.empty()) 
{ 
    MyNodeType *node = *toDoList.first(); 
    toDoList.erase(toDoList.first()); 
    ... 
} 

Si vous changez un peu, il fonctionne de la même, mais fonctionne mieux:

MyListType toDoList; // List sorted by smallest distance 
toDoList.insert(startNode); 
while(! toDoList.empty()) 
{ 
    MyNodeType *node = *toDoList.first(); 
    toDoList.erase(toDoList.first()); 
    for(MyNeigborType *x = node.Neigbors; x != NULL; x++) 
    { 
     ... 
     toDoList.insert(x->Node); 
    } 
} 

Il semble que cette modification réduit le temps d'exécution d'un ordre de grandeur non, mais un ordre d'exposant. Il a réduit ma forme d'exécution 30 secondes à moins de 2. Je ne trouve pas cette modification dans la littérature. Il est également très clair que la raison réside dans la liste triée. insérer/effacer effectue bien pire avec 100.000 éléments qu'avec une main pleine de.

RÉPONSE:

Après beaucoup de googler je l'ai trouvé moi-même. La réponse est clairement: boost graph lib. Incroyable - je n'avais pas trouvé ça depuis longtemps. Si vous pensez qu'il n'y a pas de variation de performance entre les implémentations de Dijkstra, voyez wikipedia.

+26

Un de mes amis peut implémenter Dijkstra en C++ en environ 3 minutes. Je n'ai pas entendu parler d'implémentations plus rapides. – jbasko

+5

Je suggérerais que vous souhaitiez vérifier ces implémentations ... si elles sont correctes, elles devraient toutes retourner le même chemin le plus court ... L'algorithme de Dijkstra n'est pas heuristique ... – jerryjvl

+2

Les différences dans les chemins les plus courts peuvent être le résultat de en utilisant des maths à double précision, à cause des erreurs d'arrondi lors de la sommation de longues séquences de doubles. Un ordre de sommation différent peut produire différentes erreurs. Pouvez-vous tester vos implémentations sur des entiers? –

Répondre

11

Les meilleures implémentations connues pour les réseaux routiers (> 1 million de nœuds) ont des temps de requête exprimés en micro secondes. Voir pour plus de détails le 9ème Défi de la mise en œuvre de DIMACS (2006). Notez que ce ne sont pas simplement Dijkstra, bien sûr, car le but était d'obtenir des résultats plus rapidement.

+2

J'ai trouvé le défi que vous avez mentionné sous http://www.dis.uniroma1.it/~challenge9/papers.shtml très impressionnant. Merci. –

+1

Voir également http://algo2.iti.uni-karlsruhe.de/english/1087.php pour la méthode Contraction Hierarchies. –

0

Ça va dépendre de beaucoup de choses. Que savez-vous de vos données d'entrée? Est-il dense ou peu dense? Cela va changer quelles versions de l'algorithme sont les plus rapides.

Si c'est dense, utilisez une matrice. Si elle est éparse, vous pourriez vouloir examiner des structures de données plus efficaces pour trouver le prochain sommet le plus proche. Si vous avez plus d'informations sur votre ensemble de données que sur la connectivité du graphe, voyez si un algorithme différent fonctionnerait mieux comme A *.

Le problème est qu'il n'existe pas de version "la plus rapide" de l'algorithme.

1

La dernière fois que j'ai vérifié, l'algorithme de Dijkstra renvoie une solution optimale. Toutes les "vraies" implémentations de Dijkstra doivent retourner le même résultat à chaque fois.

De même, l'analyse asymptotique nous montre que des optimisations mineures à des implémentations particulières ne vont pas affecter de manière significative les performances à mesure que la taille d'entrée augmente.

+0

Absulously. La seule différence peut être quand il y a plusieurs "chemins les plus courts" avec la même longueur (dans une erreur d'arrondi). Si une implémentation ne parvient pas à trouver le chemin le plus court, elle est boguée. À moins que votre rapport des distances de nœud soit extrêmement élevé (comme 1e20 ou plus), vous ne devriez certainement pas voir 1-2% de différence de pourcentage, même avec des flotteurs, ne parlant pas de doubles. – Suma

3

Peut-être que je ne réponds pas à votre question. Mon point est pourquoi utiliser Dijkstra quand il y a des algorithmes beaucoup plus efficaces pour votre problème. Si votre graphe remplit la propriété triangulaire (c'est un graphe euclidien)

| ab | + | bc | > | ac | (La distance du nœud a au nœud b plus la distance du nœud b au nœud c est plus grande que la distance du nœud a au nœud c), vous pouvez alors appliquer l'algorithme A *. Cet algorithme est assez efficace. Sinon, pensez à utiliser des heuristiques. La mise en œuvre n'est pas le problème majeur. L'algorithme à utiliser est important.

+0

Je ne pense pas que vous ayez besoin de la propriété triangulaire. Tout ce dont vous avez besoin est une bonne fonction heuristique. Avec une heuristique médiocre mais valide, A * se dégrade en Dijkstra. – MSalters

+0

True. Tu as raison. Ce que je disais était une possible heuristique admissible. – Luixv

2

Deux points que je voudrais faire: 1) Dijkstra vs A * L'algorithme de Dijkstra est un algorithme de programmation dynamique, pas une heuristique. A * est une heuristique car elle utilise aussi une fonction heuristique (disons h (x)) pour "estimer" la proximité d'un point x au point final. Cette information est exploitée dans les décisions suivantes des nœuds à explorer ensuite.

Pour des cas tels qu'un graphe euclidien, alors A * fonctionne bien car la fonction heuristique est facile à définir (on peut simplement utiliser la distance euclidienne, par exemple). Cependant, pour les graphes non-euclidiens, il peut être plus difficile de définir la fonction heuristique, et une mauvaise définition peut conduire à un chemin non-optimal. Par conséquent, dijkstra a l'avantage sur A * qui est que cela fonctionne pour n'importe quel graphe général (à l'exception de A * étant plus rapide dans certains cas). Il se pourrait bien que certaines implémentations utilisent ces algorithmes de façon interchangeable, ce qui donne des résultats différents.

2) L'algorithme de dijkstra (et d'autres tels que A *) utilise une file d'attente prioritaire pour obtenir le prochain nœud à explorer. Une bonne mise en œuvre peut utiliser un tas au lieu d'une file d'attente, et un encore meilleur peut utiliser un tas de fibonacci. Cela pourrait expliquer les différents temps d'exécution.

Questions connexes