2009-06-02 23 views
11

Recientemente adjunté la 3ra versión del algoritmo de Dijkstra para la ruta más corta de una sola fuente en mi proyecto.¿Cuál es la implementación más rápida de Dijkstra que conoces (en C++)?

Me doy cuenta de que hay muchas implementaciones diferentes que varían mucho en rendimiento y también varían en la calidad del resultado en gráficos grandes. Con mi conjunto de datos (> 100.000 vértices) el tiempo de ejecución varía de 20 minutos a unos pocos segundos. Los caminos más cortos también varían en un 1-2%.

¿Cuál es la mejor implementación que conoces?

EDIT: My Data es una red hidráulica, con 1 a 5 vértices por nodo. Es comparable a un mapa de calles. Hice algunas modificaciones a un algoritmo ya acelerado (utilizando una lista ordenada para todos los nodos restantes) y ahora encuentro los mismos resultados en una fracción de tiempo. He buscado tal cosa bastante tiempo. Me pregunto si tal implementación ya existe.

No puedo explicar las pequeñas diferencias en los resultados. Sé que Dijkstra no es heurístico, pero todas las implementaciones parecen ser correctas. Las soluciones más rápidas tienen los resultados con trayectorias más cortas. Utilizo matemática de precisión doble exclusivamente.

EDIT 2: Descubrí que las diferencias en la ruta encontrada son de hecho mi culpa. Inserté un manejo especial para algunos vértices (solo válido en una dirección) y me olvidé de eso en la otra implementación.

PERO mejorar aún más sorprendido de que Dijkstra se puede acelerar de manera espectacular por el siguiente cambio: En general un algoritmo de Dijkstra contiene un bucle como:

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

Si cambia esto un poco, se funciona de la misma, pero tiene un mejor rendimiento:

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); 
    } 
} 

parece que esta modificación reduce el tiempo de ejecución de una orden de magnitud no, pero una orden de exponente. Reduje mi tiempo de ejecución de 30 Segundos a menos de 2. No puedo encontrar esta modificación en ninguna literatura. También está muy claro que la razón radica en la lista ordenada. insertar/borrar se comporta mucho peor con 100.000 elementos que con una mano llena de.

RESPUESTA:

Después de mucho googlear lo encontré a mí mismo. La respuesta es clara: boost graph lib. Increíble, no había encontrado esto por bastante tiempo. Si cree que no existe una variación en el rendimiento entre las implementaciones de Dijkstra, consulte wikipedia.

+26

Un amigo mío puede implementar Dijkstra en C++ en aproximadamente 3 minutos. No he oído hablar de implementaciones más rápidas. – jbasko

+5

Sugeriría que tal vez quiera verificar esas implementaciones ... si son correctas, todas deberían devolver el mismo camino más corto ... El algoritmo de Dijkstra no es heurístico ... – jerryjvl

+2

Las diferencias en las rutas más cortas pueden ser el resultado de usando matemática de precisión doble, debido a errores de redondeo al sumar largas secuencias de dobles. Un orden diferente de suma puede producir diferentes errores. ¿Puedes probar tus implementaciones en enteros? –

Respuesta

11

Las mejores implementaciones conocidas para redes de carreteras (> 1 millón de nodos) tienen tiempos de consulta expresados ​​en micro segundos. Vea para más detalles el noveno Desafío de Implementación de DIMACS (2006). Tenga en cuenta que estos no son simplemente Dijkstra, por supuesto, ya que el objetivo era obtener resultados más rápidos.

+2

Encontré el desafío que mencionas en http://www.dis.uniroma1.it/~challenge9/papers.shtml muy impresionante. Gracias. –

+1

También vea http://algo2.iti.uni-karlsruhe.de/english/1087.php para el método de jerarquías de contracción. –

0

Va a depender de muchas cosas. ¿Cuánto sabes sobre tus datos de entrada? ¿Es denso o escaso? Eso cambiará qué versiones del algoritmo son las más rápidas.

Si es denso, solo use una matriz. Si es escaso, es posible que desee buscar estructuras de datos más eficientes para encontrar el siguiente vértice más cercano. Si tiene más información sobre su conjunto de datos que solo la conectividad del gráfico, entonces vea si un algoritmo diferente funcionaría mejor como A *.

El problema es que no existe la versión "más rápida" del algoritmo.

1

La última vez que verifiqué, el Algoritmo de Dijkstra devuelve una solución óptima. Todas las implementaciones "verdaderas" de Dijkstra deben devolver el mismo resultado cada vez.

Del mismo modo, el análisis asintótico nos muestra que las optimizaciones menores para implementaciones particulares no van a afectar significativamente el rendimiento a medida que aumenta el tamaño de la entrada.

+0

Absolutamente. La única diferencia puede ser cuando hay varias "rutas más cortas" con la misma longitud (en un error de redondeo) (costo). Si alguna implementación no logra encontrar la ruta más corta, tiene errores. A menos que su relación de distancias de nodo sea extremadamente alta (como 1e20 o más), definitivamente no debería ver una diferencia de 1-2% por ciento, incluso con flotadores, sin hablar de dobles. – Suma

3

Puede que no responda su pregunta. Mi punto es por qué usar Dijkstra cuando hay algoritmos bastante más eficientes para su problema. Si su gráfico cumple con la propiedad triangular (es un gráfico euclidiano)

| ab | + | bc | > | ac |

(la distancia desde el nodo a al nodo b más la distancia desde el nodo b al nodo c es mayor que la distancia desde el nodo a al nodo c) luego puede aplicar el algoritmo A *. Este algoritmo es bastante eficiente. De lo contrario, considere usar heurística. La implementación no es el problema principal. El algoritmo que se utilizará importa.

+0

No creo que incluso * necesite * la propiedad triangular. Todo lo que necesitas es una buena función heurística. Con una heurística pobre pero válida, A * se degrada a Dijkstra. – MSalters

+0

Es cierto. Tienes razón. Lo que estaba diciendo era una posible heurística admisible. – Luixv

2

Dos puntos que me gustaría hacer: 1) Dijkstra vs A * El algoritmo de Dijkstra es un algoritmo de programación dinámica, no una heurística. A * es una heurística porque también usa una función heurística (digamos h (x)) para "estimar" qué tan cerca está llegando un punto x al punto final. Esta información se explota en decisiones posteriores sobre qué nodos explorar a continuación.

Para casos como un gráfico euclidiano, entonces A * funciona bien porque la función heurística es fácil de definir (se puede usar simplemente la distancia euclidiana, por ejemplo). Sin embargo, para los gráficos no euclidianos puede ser más difícil definir la función heurística, y una definición incorrecta puede conducir a un camino no óptimo.

Por lo tanto, dijkstra tiene la ventaja sobre A *, que es que funciona para cualquier gráfico general (con la excepción de que A * es más rápido en algunos casos). Podría ser que ciertas implementaciones usen estos algoritmos de forma intercambiable, lo que da como resultado diferentes resultados.

2) El algoritmo dijkstra (y otros como A *) usan una cola de prioridad para obtener el siguiente nodo para explorar. Una buena implementación puede usar un montón en lugar de una cola, y una aún mejor puede usar un montón de fibonacci. Esto podría explicar los diferentes tiempos de ejecución.

Cuestiones relacionadas