elhacker.net cabecera Bienvenido(a), Visitante. Por favor Ingresar o Registrarse
¿Perdiste tu email de activación?.

 

 


Tema destacado: Curso de javascript por TickTack


+  Foro de elhacker.net
|-+  Programación
| |-+  Programación C/C++ (Moderadores: Eternal Idol, Littlehorse, K-YreX)
| | |-+  Algoritmo de Dijkstra paso a paso
0 Usuarios y 1 Visitante están viendo este tema.
Páginas: [1] 2 Ir Abajo Respuesta Imprimir
Autor Tema: Algoritmo de Dijkstra paso a paso  (Leído 35,060 veces)
Yoel Alejandro

Desconectado Desconectado

Mensajes: 254



Ver Perfil WWW
Algoritmo de Dijkstra paso a paso
« en: 2 Enero 2015, 23:00 pm »

Recientemente vi una pregunta de un usuario del foro sobre grafos no dirigidos y distancia mínima entre nodos del mismo. Me puse a invetigar el tema, el cual me pareció realmente interesante y decidí escribir un post al respecto.

El algoritmo de Dijkstra permite encontrar la distancia mínima entre un vértice dado de un grafo no dirigido, y cualquiera otro de sus vértices. No funciona sin embargo si la ponderación entre dos elementos es negativa, es sólo para grafos donde el coste de unir dos nodos cualesquiera es siempre mayor o igual que cero.

http://es.wikipedia.org/wiki/Algoritmo_de_Dijkstra
http://es.wikipedia.org/wiki/Anexo:Ejemplo_de_Algoritmo_de_Dijkstra

Un video de youtube que nos explica este tema de una manera excelente es:



A modo de ejemplo, consideremos el grafo (me disculpan la presentación simplemente me puse a escribir sobre una hoja de papel y le tomé fotografías)


En esta imagen, los valores sobre las aristas representan los pesos o costes de transitar los caminos que unen un nodo con el otro. El algoritmo comienza eligiendo un nodo inicial, en este caso digamos que el nodo 0.

Etiquetado de los nodos

Junto con cada nodo asociaremos una etiqueta o label que se compone de un par de valores:

( peso_acumulado, antecesor )

esto es, como primer elemento el peso total acumulado del nodo actual (la suma de los pesos de las aristas recorridas para llegar desde el nodo inicial hasta el nodo actual), y como segundo elemento denotamos el nodo previo al nodo actual. Esto último es importante para reconstruir la ruta seguida para llegar al nodo.

El nodo inicial tiene un peso de 0 (la distancia que lo separa de sí mismo es lógicamente cero), y no tiene antecesor, por lo que se asigna el par compuesto de un "cero" seguido de una "rayita": (0, -)


Vale decir que los restantes nodos comienzan con un peso o coste "infinito", pues aún no hemos calculado su coste.

Cálculo del coste hasta los nodos sucesivos

El siguiente paso es observar todos los nodos que están directamente conectados con este nodo inicial. En nuestro caso, los nodos 1, 2 y 3. El coste de cada nodo será el coste acumulado de su nodo antecesor, sumado al coste del camino que une al antecesor con el nodo actual:

Código:
coste de nodo 1: (coste acumulado nodo 0) + (coste camino que une nodos 0 y 1) = 0 + 16 = 16
coste de nodo 2: (coste acumulado nodo 0) + (coste camino que une nodos 0 y 2) = 0 + 10 = 10
coste de nodo 3: (coste acumulado nodo 0) + (coste camino que une nodos 0 y 3) = 0 + 5 = 5

por lo tanto etiquetamos estos nodos sucesores del nodo 0 de la siguiente manera:

Código:
nodo 1 ---> ( 16, 0 )     [ pues, su coste acumulado es 16, y procede del nodo 0 ]
nodo 2 ---> ( 10, 0 )     [ ídem ]
nodo 2 ---> ( 5, 0 )     [ ídem ]

Seguidamente, "tachamos" o "marcamos" el nodo 0 como definitivo, y con ellos hemos terminado el primer ciclio de la ejecución de nuestro algoritmo. Queda resaltar que un nodo "tachado" es un nodo que no se vuelve a revisar. El algoritmo terminará cuando todos los nodos estén "tachados".
Hasta ahora vamos así:


Continuación del algoritmo

El algoritmo continúa inspeccionando entre todos los nodos que no han sido tachados o marcados como definiitivos, pero tienen un peso total ya calculado menor a infinito. Esto es, los nodos 1, 2 y 3. De ellos, seleccionamos el de menor coste, que sería el nodo 3 y repetimos el proceso considerándolo como nodo inicial.

Al nodo 3 le suceden, exceptuando el propio nodo 0 que ya fue tachado (repito, los nodos tachados no se vuelven a revisar), los nodos 2 y 4. Sus costes totales se calcularían como:

Código:
coste de nodo 2: (coste acumulado nodo 3) + (coste camino que une nodos 3 y 2) = 5 + 4 = 9
coste de nodo 4: (coste acumulado nodo 3) + (coste camino que une nodos 3 y 4) = 5 + 15 = 20

por lo tanto etiquetamos estos nodos sucesores del nodo 3 de la siguiente manera:

Código:
nodo 2 ---> ( 9,  3 )     [ costo 9, procediendo del nodo 3 ]
nodo 4 ---> ( 20, 3 )     [ costo 20, procediendo del nodo 3 ]

Mejoramiento del coste

Obsérvese en la última etapa que hasta ahora llevamos del algoritmo, que el coste del nodo 2 mejoró de un valor 10 a un valor 9, al cambiar su antecesor y por lo tanto la ruta seguida.

Para ser más claros, el coste del nodo 2 al tomar la ruta que procede directamente desde el nodo 0 fue de 10. Pero al considerar la ruta que alcanza el nodo 2 desde el nodo 3, o sea la ruta 0-3-2, el coste total acumulado fue 5+4=9. Cuando esto ocurre tachamos la anterior etiqueta del nodo 2 que era (10,0), y la cambiamos por la etiqueta (9,3). Y esto lo debemos hacer cada vez que alcancemos un nodo que habíamos computado, pero esta vez desde una nueva ruta y con un costo menor.

El grafo nos va quedando así, y nótese como se mejora el coste del nodo 2. También nótese que tachamos el nodo 3, es decir lo marcamos como definitivo por eso no lo volveremos a revisar.


Siguiente paso

Revisamos ahora todos los nodos que tienen un coste calculado menor a infinito, en este caso los nodos 1,2  y 4. El de menor coste es el nodo 2, por lo tanto es el nodo que vamos a tomar como inicial.

Los sucesores del nodo 2 son los nodos 1, 4 y 5. Calculamos sus costes:

Código:
nodo 1, a partir de 2: 9 + 2 = 11   (mejora el coste)
nodo 5, a partir de 2: 9 + 12 = 21
nodo 4, a partir de 2: 9 + 10 = 19 (mejora el coste)

Marcamos el nodo 2 como definitivo, y continuamos.


Siguiente paso

De los nodos no definitivos que quedan (1, 4 y 5) el de menor coste es el 1. Inspeccionamos sus sucesores, los nodos 5 y 6, al calcular los costes de estos resulta:

Código:
nodo 5, a partir de 1: 11 + 4 = 15   (mejora el coste)
nodo 6, a partir de 1: 11 + 6 = 17

Tachamos el nodo 1 y continuamos.


b] Siguiente paso [/b]

De los nodos no definitivos que quedan (4, 5 y 6) el de menor coste es el 5. Inspeccionamos sus sucesores, los nodos 6, 7 y 4, al calcular los costes de estos resulta:

Código:
nodo 6, a partir de 5: 15 + 8 = 23   (empeora el coste)
nodo 7, a partir de 5: 15 + 16 = 31
nodo 7, a partir de 5: 15 + 3 = 18

Nótese que al recalcular el nodo 6 desde el nodo 5 se obtiene más bien un costo peor, por lo cual se mantiene su costo anterior que era 17, desde el nodo 1.

Tachamos el nodo 5 y continuamos, nos debe quedar así:


Pasos finales

El algoritmo se continúa repitiendo los pasos anteriores, hasta que no queden nodos sin tachar, es decir, hasta que todos queden como definitivos.



Reconstrucción de la ruta más económica

Una vez terminado, podemos calcular la ruta del coste mínimo desde el nodo inicial 0, hasta cualquiera otro de nuestros nodos. Por ejemplo, desde el nodo 0 hasta el nodo 7, vemos que el coste total es 23 y la ruta se forma regresivamente de la siguiente manera

Código:
el nodo 7, procede del nodo 4
el nodo 4, procede del nodo 5
el nodo 5, procede del nodo 1
el nodo 1, procede del nodo 2

etcétera, hasta llegar al nodo inicial. La ruta por lo tanto sería:

0 - 3 - 2 - 1 - 5 - 4 - 7

===================================================================
   PROGRAMA EN C++ PARA EL CALCULO DE LA RUTA MINIMA


Este es un pequeño programa que hice en C++ para computar el grafo anteriormente explicado usando el algoritmo de Dijkstra. Al cambiar la matriz de adyacencia por otra, se puede modificar el programa para trabajar con un grafo diferente. Claro, está un poco rudimentario, lo ideal sería poder pedir al usuario la matriz de adyacencia del grafo de forma interactica a través del teclado, pero hasta ahí no llegué por hoy jeje.

Codifiqué algunos "cout" para que el programa fuese imprimiendo los resultados de las distintas etapas del análisis, de modo que sea más explicativo o pedagógico. Por supuesto, el programa está dado a mejorar ya que esta es una versión muy básica pero es completamente funcional, sólo compilar y probar.

Para nuestro caso la salida final es (luego de imprimir información etapas previas del análisis, jeje)
Código:
================================================================

Ruta mas economica entre nodo 0 y nodo 7:

0 - 3 - 2 - 1 - 5 - 4 - 7

Costo total: 23

Por otra parte, creo que el programa no está mal, no es demasiado extenso porque la parte que calcula el Dijkstra sólo llevó 113 líneas de código, el resto del código es inicializando la matriz, declarando los structs, etc.

Código
  1. #include <iostream>
  2. #include <iomanip>
  3. #include <list>
  4.  
  5. /* Definiendo la estructura etiqueta de nodo. Sus campos incluyen
  6.  * el número de nodo, el coste total del nodo, y su precedente. */
  7. struct label {
  8.   int nro; /* numero del nodo */
  9.   int prev; /* nodo precedente (-1 para el nodo inicial )*/
  10.   int peso; /* peso o coste total de la trayectoria que
  11. * conduce al nodo, i.e., el coste total desde
  12. * el nodo inicial hasta el actual. Un valor
  13. * de -1 denota el infinito */
  14.   int marca; /* si el nodo ha sido marcado o no */
  15. };
  16. typedef struct label label_t;
  17.  
  18. using namespace std;
  19.  
  20. void dijkstra( int, int **, int, int );
  21.  
  22. int main () {
  23.  
  24.   /* cantidad total de nodos */
  25.   int numNodos = 8;
  26.  
  27.   /* Definiendo la matriz de adyacencia */
  28.   int i, j, **A;
  29.  
  30.   if ( ( A = new int*[numNodos] ) == NULL ) return 1;
  31.   for ( i = 0; i < numNodos; i++ )
  32.      if ( ( A[i] = new int[numNodos] ) == NULL ) return 1;
  33.  
  34.   for ( i = 0; i < 8; i++ )
  35.      for ( j = 0; j < 8; j++ )
  36.         A[i][j] = 0;
  37.  
  38.   /* por simplicidad, completamos solo los pares de nodos conectados */
  39.   A[0][1] = 16;
  40.   A[0][2] = 10;
  41.   A[0][3] = 5;
  42.  
  43.   A[1][0] = 16;
  44.   A[1][2] = 2;
  45.   A[1][5] = 4;
  46.   A[1][6] = 6;
  47.  
  48.   A[2][0] = 10;
  49.   A[2][1] = 2;
  50.   A[2][3] = 4;
  51.   A[2][4] = 10;
  52.   A[2][5] = 12;
  53.  
  54.   A[3][0] = 5;
  55.   A[3][2] = 4;
  56.   A[3][4] = 15;
  57.  
  58.   A[4][2] = 10;
  59.   A[4][3] = 15;
  60.   A[4][5] = 3;
  61.   A[4][7] = 5;
  62.  
  63.   A[5][1] = 4;
  64.   A[5][2] = 12;
  65.   A[5][4] = 3;
  66.   A[5][6] = 8;
  67.   A[5][7] = 16;
  68.  
  69.   A[6][1] = 6;
  70.   A[6][5] = 8;
  71.   A[6][7] = 7;
  72.  
  73.   A[7][4] = 5;
  74.   A[7][5] = 16;
  75.   A[7][6] = 7;
  76.  
  77.   /* Imprimir la matriz de adyacencia */
  78.   cout << "Matriz de adyacencia:" << endl << endl;
  79.   for ( i = 0; i < 8; i++ ) {
  80.      for ( j = 0; j < 8; j++ )
  81.         cout << setw(2) << A[i][j] << "  ";
  82.      cout << endl;
  83.   }
  84.   cout << endl;
  85.  
  86.   /* calcular dijkstra a partir del nodo 0, a partir del nofo 7 */
  87.   dijkstra( numNodos, A, 0, 7 );
  88.  
  89.   /* liberar memoria */
  90.   delete [] A;
  91.   for ( i = 0; i < numNodos; i++ )
  92.      delete A[i];
  93.  
  94.   return 0;
  95. }
  96.  
  97. /* Calcula el coste minimo de alcanzar un nodo final 'b'
  98.  * grafo no dirigido con N nodos, a partir del nodo inicial 'a',
  99.  * dada su matriz de adyacencia A */
  100. void dijkstra( int N, int **A, int a, int b ) {
  101.  
  102.   label_t *Labels;
  103.   int i, i0, j, peso;
  104.   int *ruta; /* array de nodos de la ruta minima */
  105.  
  106.   /* Crea din'amicamente el arreglo de etiquetas de nodo */
  107.   if ( ( Labels = new label_t[N] ) == NULL ) return;
  108.  
  109.   /* nodo inicial 'a' entre 0 y N - 1 */
  110.   if ( a < 0 || a > N - 1 ) return;
  111.   /* nodo final 'a' entre 0 y N - 1 */
  112.   if ( b < 0 || b > N - 1 ) return;
  113.  
  114.   /* inicializar las etiquetas de nodo */
  115.   for ( i = 0; i < N; i++ ) {
  116.      Labels[i].nro = i;
  117.      if ( i != a ) {
  118.         Labels[i].prev = -1; /* a'un no se ha definido predecesor */
  119.         Labels[i].peso = -1; /* infinito */
  120.         Labels[i].marca = 0;
  121.      }
  122.      else {
  123.         Labels[i].prev = -1; /* a'un no se ha definido predecesor */
  124.         Labels[i].peso = 0; /* coste del nodo inicial a s'i mismo es cero */
  125.         Labels[i].marca = 0;
  126.      }
  127.   }
  128.  
  129.   /* continuamos este ciclo mientras existan nodos no marcados */
  130.   while ( 1 ) {
  131.      /* busca entre todos los nodos no marcados el de menor peso, descartando los
  132.        * de peso infinito (-1) */
  133.      peso = -1;
  134.      i0 = -1;
  135.      for ( i = 0; i < N; i++ ) {
  136.         if ( Labels[i].marca == 0 && Labels[i].peso >= 0 )
  137.            if ( peso == -1 ) {
  138.               peso = Labels[i].peso;
  139.               i0 = i;
  140.            }
  141.            else if ( Labels[i].peso <= peso ) {
  142.               peso = Labels[i].peso;
  143.               i0 = i;
  144.            }
  145.      }
  146.      if ( i0 == -1 ) { /* termina si no encuentra */
  147.         cout << "Ya no quedan nodos por analizar." << endl;
  148.         break;
  149.      }
  150.  
  151.      cout << "*** Analizando nodo " << i0 << " ***" << endl;
  152.  
  153.      /* actualiza el peso de todos los sucesores (si los hay) del nodo
  154.        * encontrado y luego se~nala dicho nodo como marcado */
  155.      for ( i = 0; i < N; i++ ) {
  156.         if ( A[i0][i] > 0 ) {
  157.            /* si el coste acumulado sumado al coste del enlace del nodo i0 al nodo i
  158.              * es menor al coste del nodo i (o si el coste del nodo i es infinito),
  159.              * debemos actualizar */
  160.            if ( Labels[i].peso == -1 || Labels[i0].peso + A[i0][i] < Labels[i].peso ) {
  161.               if ( Labels[i0].peso + A[i0][i] < Labels[i].peso )
  162.                  cout << "   [ mejorando coste de nodo " << i << " ]" << endl;
  163.               Labels[i].peso = Labels[i0].peso + A[i0][i];
  164.               Labels[i].prev = i0;
  165.               cout << "   coste de nodo " << i << " desde nodo " << i0 << ": " << Labels[i].peso << endl;
  166.            }
  167.         }
  168.      }
  169.      Labels[i0].marca = 1;
  170.      cout << "   [ nodo " << i0 << " marcado ]" << endl;
  171.  
  172.      /* para verificar, imprime los costes calculados hasta el momento */
  173.      for ( i = 0; i < N; i++ ) {
  174.         cout << i << ": [";
  175.         if ( Labels[i].peso == -1 ) cout << "Inf";
  176.         else cout << Labels[i].peso;
  177.         cout << ", " << Labels[i].prev ;
  178.         if ( Labels[i].marca == 1 ) cout <<  ", x]" << endl;
  179.         else cout << "]" << endl;
  180.      }
  181.      cout << endl;
  182.  
  183.      /* pausa (opcional) */
  184.      cout << "presione ENTER para continuar ...";
  185.      cin.get();
  186.   }
  187.  
  188.   /* Ruta desde el nodo 'a' hasta el nodo 'b' */
  189.   int longitud = 2;
  190.   i = b;
  191.   while ( ( i = Labels[i].prev ) != a ) longitud++; /* primero estimamos la longitud de la ruta */
  192.   if ( ( ruta = new int[longitud] ) == NULL ) return;
  193.  
  194.   ruta[longitud - 1] = b; /* luego rellenamos */
  195.   i = b;
  196.   j = 0;
  197.   for ( j = 1; j < longitud; j++ ) {
  198.      i = Labels[i].prev;
  199.      ruta[longitud - j - 1] = i;
  200.   }
  201.  
  202.   cout << "================================================================" << endl;
  203.   cout << endl << "Ruta mas economica entre nodo " << a << " y nodo " << b << ":" << endl << endl;
  204.   for ( i = 0; i < longitud; i++ ) {
  205.      cout << ruta[i];
  206.      if ( i < longitud - 1 ) cout << " - ";
  207.   }
  208.   cout << endl << endl << "Costo total: " << Labels[b].peso << endl << endl;
  209.  
  210.   delete ruta;
  211.   delete [] Labels;
  212. }


« Última modificación: 2 Enero 2015, 23:41 pm por yoel_alejandro » En línea

Saludos, Yoel.
P.D..-   Para mayores dudas, puedes enviarme un mensaje personal (M.P.)
vk496

Desconectado Desconectado

Mensajes: 205


Ver Perfil
Re: Algoritmo de Dijkstra paso a paso
« Respuesta #1 en: 2 Enero 2015, 23:59 pm »

Muy bueno... Y muy útil este algoritmo, tiene muchas aplicaciones en la vida real (no sabía hasta que punto si no lo hubiese descubierto)

Salu2


En línea

marrison

Desconectado Desconectado

Mensajes: 179



Ver Perfil
Re: Algoritmo de Dijkstra paso a paso
« Respuesta #2 en: 5 Enero 2015, 18:10 pm »

Muchas gracias amigo por la explicacion, de verdad que me ha ayudado mucho, aunque yo lo tengo que hacer para c..
« Última modificación: 5 Enero 2015, 18:16 pm por marrison » En línea

"Es genial trabajar con ordenadores. No discuten, lo recuerdan todo y no se beben tu cerveza" (Paul Leary)

"Controlar la complejidad es la esencia de la programación" (Brian Kernigan)

"Primero resuelve el problema. Entonces, escribe el código" (John Johnson)

"640K deberían ser suficientes para todo el mundo" (Bill Gates, 1981)
Yoel Alejandro

Desconectado Desconectado

Mensajes: 254



Ver Perfil WWW
Re: Algoritmo de Dijkstra paso a paso
« Respuesta #3 en: 6 Enero 2015, 01:48 am »

Bueno marrison, si es para C la verdad es que no tienes mucho que cambiar. Tuve cuidado al elaborar el código de modo que fuera en su mayor parte compatible con C.

Lo único que tendrías que adaptar en la gestión dinámica de memoria para los arreglos que se crean en tiempo de ejecución. O sea, el uso de malloc(), realloc() y free(), en lugar de la combinación new y delete de C++.

De todos modos voy a ver si en estos días tengo un tiempo y versiono el código completamente a C.

Y un detalle, en el algoritmo que propuse falta implementar una "cola de prioridad", que puede optimizar la ejecución. En esta cola se van guardando los nodos en función de su peso, colocando de primero los de menor coste. De esta manera no hay que calcular el mínimo con cada ciclo, puesto que dicho nodo más económico siempre de está de primero en la cola y sólo hay que cogerlo de allí.

Un saludo ...
En línea

Saludos, Yoel.
P.D..-   Para mayores dudas, puedes enviarme un mensaje personal (M.P.)
marrison

Desconectado Desconectado

Mensajes: 179



Ver Perfil
Re: Algoritmo de Dijkstra paso a paso
« Respuesta #4 en: 7 Enero 2015, 17:56 pm »

La verdad es que me harias un gran favor, gracias a tu aporte entiendo el algoritmo, pero no tengo ni idea de como implementarlo...  :huh:
En línea

"Es genial trabajar con ordenadores. No discuten, lo recuerdan todo y no se beben tu cerveza" (Paul Leary)

"Controlar la complejidad es la esencia de la programación" (Brian Kernigan)

"Primero resuelve el problema. Entonces, escribe el código" (John Johnson)

"640K deberían ser suficientes para todo el mundo" (Bill Gates, 1981)
Yoel Alejandro

Desconectado Desconectado

Mensajes: 254



Ver Perfil WWW
Re: Algoritmo de Dijkstra paso a paso
« Respuesta #5 en: 8 Enero 2015, 17:14 pm »

Voy a trabajar pronto en una versión completamente en C, que implemente cola de prioridad
En línea

Saludos, Yoel.
P.D..-   Para mayores dudas, puedes enviarme un mensaje personal (M.P.)
sabeeee

Desconectado Desconectado

Mensajes: 155


Ver Perfil
Re: Algoritmo de Dijkstra paso a paso
« Respuesta #6 en: 8 Enero 2015, 17:41 pm »

Jajaja que genialidad.  :o
En línea

"Vengándose, uno iguala a su enemigo; perdonando, uno se muestra superior a él."
Francis Bacon
daryo


Desconectado Desconectado

Mensajes: 1.070



Ver Perfil WWW
Re: Algoritmo de Dijkstra paso a paso
« Respuesta #7 en: 8 Enero 2015, 18:08 pm »

chincheta!
En línea

buenas
Yoel Alejandro

Desconectado Desconectado

Mensajes: 254



Ver Perfil WWW
Re: Algoritmo de Dijkstra paso a paso
« Respuesta #8 en: 10 Enero 2015, 16:22 pm »

Ahora sí que sí, ya lo pude terminar jajaja  ;D

El inicio de estre post me referí a un programa que emplea el algoritmo de Dijkstra para calcular la ruta más económica entre dos puntos de un grafo no dirigido. La versión inicialmente publicada fue en C++. Ahora voy con una vesión completamente en C, que además implementa una cola de prioridad para seleccionar más eficientemente el nodo objetivo a analizar en cada ciclo del algoritmo.

El principio es sencillo. Inicialmente la cola contiene únicamente al nodo inicial del grafo. Seguidamente se analizan todos los sucesores (no marcados) del nodo objetivo, y se evalúa o re-evalua su coste, y se coloca ordenadamente como corresponda en la cola de prioridad. La cola siempre permanece ordenada según el coste del nodo, de menor a mayor, puesto que cuando se inserta un nuevo elemento se lo hace en el lugar que corresponde según su valor.

En el caso que un nodo al ser re-evaluado disminuye o mejora su coste, entonces debe ser reubicado en la cola. Para ello, lo que hacemos es retirarlo de la cola y luego reinsertarlo en la posición adecuada.

Luego de ésto, el nodo objetivo es "marcado" y por lo tanto sacado de la cola puesto que no volverá a ser analizado en ejecuciones posteriores del algoritmo.

La versión a C se logró simplemente cambiando algunos "cout" a "printf()", y reemplazando el típico new/delete de C++ por el malloc()/free() de C. Lo que fue un poquito más complicado fue tener que implementar "manualmente" una cola de prioridad en C, porque no podemos usar la clase stack de la STL de C++.

A continuación el programa como quedó, y advierto que ahora si creció un poco superando las 300 líneas por eso lo voy a dividir por partes. Recomiendo leer con atención la función insert() que se ocupa de agregar un elemento a la cola según el coste del número de nodo indicado.

Espero con esto haber disipado las dudas de algunos de los lectores de mi tema, y cualquier sugerencia o recomendación no duden en escribir :)

Bibliotecas y prototipos

Código
  1. /* Programa que utiliza el algoritmo de Dijsktra para encontrar el
  2.  * camino más corto o económico entre dos puntos de un grafo
  3.  * no dirigido.
  4.  */
  5.  
  6. #include <stdlib.h>
  7. #include <stdio.h>
  8.  
  9. /* Definiendo la estructura etiqueta de nodo. Sus campos incluyen
  10.  * el n'umero de nodo, el coste total del nodo, y su precedente. */
  11. struct label {
  12. int nro; /* numero del nodo */
  13. int prev; /* nodo precedente (-1 para el nodo inicial )*/
  14. int peso; /* peso o coste total de la trayectoria que
  15. * conduce al nodo, i.e., el coste total desde
  16. * el nodo inicial hasta el actual. Un valor
  17. * de -1 denota el infinito */
  18. int marca; /* si el nodo ha sido marcado o no */
  19. };
  20. typedef struct label label_t;
  21.  
  22. /* Cola de Prioridad de n'umeros de nodo */
  23. struct node {
  24. int data;
  25. struct node * nextPtr; /* apuntador al siguiente nodo de la cola */
  26. };
  27. typedef struct node node_t;
  28.  
  29. int isEmpty( node_t * ); /* decide si cola es vac'ia */
  30. int insert( node_t **, int, label_t * ); /* agrega un nodo ordenadamente a la cola */
  31. void quit( node_t **, int ); /* retira un nodo espec'ifico de la cola */
  32. int pop( node_t ** ); /* retira el primer nodo de la cola */
  33. void printQueue( node_t * ); /* imprimir los valores almacenados en la cola */
  34.  
  35. /* Funci'on que calcula la distancia m'inima de cualquiera de los
  36.  * nodos al primero */
  37. void dijkstra( int, int **, int, int );

Función MAIN , simplemente crea la matriz de adyacencia y llama al Dijkstra

Código
  1. int main () {
  2.  
  3. int i, j;
  4.  
  5. /* cantidad total de nodos */
  6. int numNodos = 8;
  7.  
  8. /* Definiendo la matriz de adyacencia */
  9. int **A;
  10. if ( ( A = (int **) malloc( numNodos * sizeof(int *) ) ) == NULL ) return 1;
  11. for ( i = 0; i < numNodos; i++ )
  12. if ( ( A[i] = (int *) malloc( numNodos * sizeof(int) ) ) == NULL ) return 1;
  13.  
  14. for ( i = 0; i < 8; i++ )
  15. for ( j = 0; j < 8; j++ )
  16. A[i][j] = 0;
  17.  
  18. /* por simplicidad, completamos solo los pares de nodos conectados */
  19. A[0][1] = 16;
  20. A[0][2] = 10;
  21. A[0][3] = 5;
  22.  
  23. A[1][0] = 16;
  24. A[1][2] = 2;
  25. A[1][5] = 4;
  26. A[1][6] = 6;
  27.  
  28. A[2][0] = 10;
  29. A[2][1] = 2;
  30. A[2][3] = 4;
  31. A[2][4] = 10;
  32. A[2][5] = 12;
  33.  
  34. A[3][0] = 5;
  35. A[3][2] = 4;
  36. A[3][4] = 15;
  37.  
  38. A[4][2] = 10;
  39. A[4][3] = 15;
  40. A[4][5] = 3;
  41. A[4][7] = 5;
  42.  
  43. A[5][1] = 4;
  44. A[5][2] = 12;
  45. A[5][4] = 3;
  46. A[5][6] = 8;
  47. A[5][7] = 16;
  48.  
  49. A[6][1] = 6;
  50. A[6][5] = 8;
  51. A[6][7] = 7;
  52.  
  53. A[7][4] = 5;
  54. A[7][5] = 16;
  55. A[7][6] = 7;
  56.  
  57. /* Imprimir la matriz de adyacencia */
  58. printf( "Matriz de adyacencia:\n\n" );
  59. for ( i = 0; i < 8; i++ ) {
  60. for ( j = 0; j < 8; j++ )
  61. printf( "%2d  ", A[i][j] );
  62. printf( "\n" );
  63. }
  64. printf( "\n" );
  65.  
  66. /* calcular dijkstra a partir del nodo 0, a partir del nofo 7 */
  67. dijkstra( numNodos, A, 0, 7 );
  68.  
  69. /* liberar memoria */
  70. for ( i = 0; i < numNodos; i++ )
  71. free( A[i] );
  72. free( A );
  73.  
  74. return 0;
  75. }

Algoritmo de Dijsktra

Código
  1. /* Calcula el coste m'inimo de alcanzar un nodo final 'b'
  2.  * grafo no dirigido con N nodos, a partir del nodo inicial 'a',
  3.  * dada su matriz de adyacencia A */
  4. void dijkstra( int N, int **A, int a, int b ) {
  5.  
  6. label_t *Labels;
  7. int i, i0, j, peso;
  8. int *ruta; /* array de nodos de la ruta minima */
  9.  
  10. node_t *Cola = NULL; /* cola de prioridad */
  11.  
  12. /* Crea din'amicamente el arreglo de etiquetas de nodo */
  13. if ( ( Labels = malloc( N * sizeof( label_t ) ) ) == NULL ) return;
  14.  
  15. /* nodo inicial 'a' entre 0 y N - 1 */
  16. if ( a < 0 || a > N - 1 ) return;
  17. /* nodo final 'a' entre 0 y N - 1 */
  18. if ( b < 0 || b > N - 1 ) return;
  19.  
  20. /* inicializar las etiquetas de nodo */
  21. for ( i = 0; i < N; i++ ) {
  22. Labels[i].nro = i;
  23. if ( i != a ) {
  24. Labels[i].prev = -1; /* a'un no se ha definido predecesor */
  25. Labels[i].peso = -1; /* infinito */
  26. Labels[i].marca = 0;
  27. }
  28. else {
  29. Labels[i].prev = -1; /* a'un no se ha definido predecesor */
  30. Labels[i].peso = 0; /* coste del nodo inicial a s'i mismo es cero */
  31. Labels[i].marca = 0;
  32. }
  33. }
  34.  
  35. /* agregamos el nodo inicial como primer elemento de la cola */
  36. insert( &Cola, a, Labels );
  37.  
  38. /* continuamos este ciclo mientras existan nodos en la cola */
  39. while ( !isEmpty( Cola ) ) {
  40.  
  41. /* toma como objetivo el primer elemento de la cola de prioridad */
  42. i0 = pop( &Cola );
  43.  
  44. printf( "*** Analizando nodo %d ***\n", i0 );
  45.  
  46. /* actualiza el peso de todos los sucesores no marcados (si los hay) del nodo
  47. * encontrado y luego se~nala dicho nodo como marcado */
  48. for ( i = 0; i < N; i++ ) {
  49. if ( A[i0][i] > 0 && Labels[i].marca == 0 ) {
  50. /* si el coste es infinito, actualizar y añadir a la cola */
  51. if ( Labels[i].peso == -1 ) {
  52. Labels[i].peso = Labels[i0].peso + A[i0][i];
  53. insert( &Cola, i, Labels );
  54. }
  55. /* si el coste acumulado sumado al coste del enlace del nodo i0 al nodo i
  56. * es menor al coste del nodo i, actualizar al valor del costo menor y
  57. * reubicar en la cola */
  58. else if ( Labels[i0].peso + A[i0][i] < Labels[i].peso ) {
  59. printf( "   * advertencia: mejorando coste de nodo %d\n", i );
  60. Labels[i].peso = Labels[i0].peso + A[i0][i];
  61. quit( &Cola, i ); /* para reubicar, quitamos y luego a~nadimos el nodo */
  62. insert( &Cola, i, Labels );
  63. }
  64. Labels[i].prev = i0;
  65. printf( "   coste de nodo %d desde nodo %d: %d\n", i, i0, Labels[i].peso );
  66. }
  67. }
  68. Labels[i0].marca = 1;
  69. printf( "   * nodo %d marcado\n", i0 );
  70.  
  71. /* para verificar, imprime los costes calculados hasta el momento */
  72. for ( i = 0; i < N; i++ ) {
  73. printf( "%d: [", i);
  74. if ( Labels[i].peso == -1 ) printf( "Inf" ); else printf( "%d", Labels[i].peso);
  75. printf( ", %d", Labels[i].prev );
  76. if ( Labels[i].marca == 1 ) printf( ", x]\n" ); else printf( "]\n" );
  77. }
  78.  
  79. /* imprimir cola prioridad de nodos */
  80. printf( "cola de nodos en orden de prioridad es:\n" );
  81. printQueue( Cola );
  82.  
  83. /* pausa (opcional) */
  84. printf( "\npresione ENTER para continuar ..." );
  85. getchar();
  86. }
  87. printf( "Ya no quedan nodos por analizar.\n" ); /* mensaje final */
  88.  
  89. /* Ruta desde el nodo 'a' hasta el nodo 'b' */
  90. int longitud = 2;
  91. i = b;
  92. while ( ( i = Labels[i].prev ) != a ) longitud++; /* primero estimamos la longitud de la ruta */
  93. if ( ( ruta = malloc( longitud * sizeof(int) ) ) == NULL ) return;
  94.  
  95. ruta[longitud - 1] = b; /* luego rellenamos */
  96. i = b;
  97. j = 0;
  98. for ( j = 1; j < longitud; j++ ) {
  99. i = Labels[i].prev;
  100. ruta[longitud - j - 1] = i;
  101. }
  102.  
  103. printf( "================================================================\n" );
  104. printf( "\nRuta mas economica entre nodo %d y nodo %d:\n\n", a, b );
  105. for ( i = 0; i < longitud; i++ ) {
  106. printf( "%d", ruta[i]);
  107. if ( i < longitud - 1 ) printf( " - " );
  108. }
  109. printf( "\n\nCosto total: %d\n\n", Labels[b].peso );
  110.  
  111. free( ruta );
  112. free( Labels );
  113. }

Métodos asociados a la cola de prioridad

Código
  1. /* Devuelve 1 si la cola está vacía, 0 si no lo está. */
  2. int isEmpty( node_t * head ) {
  3.  
  4. return head == NULL;
  5. }
  6.  
  7. /* Insertar ordenadamente en la cola.
  8.  * Devuelve 0 si terminó con éxito, y 1 si ocurrió un error
  9.  * de asignación de memoria. */
  10. int insert( node_t ** frontPtr, int nodeNumber, label_t * Labels ) {
  11.  
  12. node_t *newPtr, *currentPtr, *prevPtr;
  13.  
  14. if ( ( newPtr = malloc( sizeof( node_t ) ) ) == NULL ) return 1;
  15.  
  16. /* busca el último nodo menor al que se quiere insertar */
  17. currentPtr = *frontPtr;
  18. prevPtr = NULL;
  19. while ( currentPtr != NULL && Labels[currentPtr->data].peso < Labels[nodeNumber].peso ) {
  20. prevPtr = currentPtr;
  21. currentPtr = currentPtr->nextPtr;
  22. }
  23.  
  24. /* inserta el nuevo nodo */
  25. newPtr->data = nodeNumber;
  26. if ( currentPtr != NULL )
  27. newPtr->nextPtr = currentPtr;
  28. else
  29. newPtr->nextPtr = NULL;
  30.  
  31. if ( prevPtr != NULL )
  32. prevPtr->nextPtr = newPtr;
  33. else
  34. *frontPtr = newPtr;
  35.  
  36. return 0;
  37. }
  38.  
  39. /* Remover de la cola el nodo con el valor específico, si lo encuentra */
  40. void quit( node_t ** frontPtr, int data ) {
  41.  
  42. node_t *prevPtr = NULL, *currPtr = *frontPtr;
  43.  
  44. /* busca el nodo con el valor espec'ifico */
  45. while ( currPtr != NULL && currPtr->data != data ) {
  46. prevPtr = currPtr;
  47. currPtr = currPtr->nextPtr;
  48. }
  49. if ( currPtr == NULL ) return; /* y termina si no encuentra */
  50.  
  51. /* si encuentra el nodo, lo remueve */
  52. if ( prevPtr != NULL )
  53. prevPtr->nextPtr = currPtr->nextPtr;
  54. else
  55. *frontPtr = currPtr->nextPtr;
  56.  
  57. free( currPtr );
  58. }
  59.  
  60. /* Retirar el primer elemento de la cola.
  61.  * Precaución: Devuelve cero si la cola est'a vacía.
  62.  */
  63. int pop( node_t **frontPtr ) {
  64.  
  65. node_t *auxPtr;
  66. int data;
  67.  
  68. if ( *frontPtr != NULL ) {
  69. auxPtr = *frontPtr;
  70. *frontPtr = (*frontPtr)->nextPtr;
  71. data = auxPtr->data;
  72. free( auxPtr );
  73. return data;
  74. }
  75. else
  76. return 0;
  77. }
  78.  
  79. void printQueue( node_t *queue ) {
  80.  
  81. node_t *currPtr = queue;
  82.  
  83. if ( currPtr == NULL ) {
  84. printf( "(vacio)\n" );
  85. return;
  86. }
  87.  
  88. while ( currPtr != NULL ) {
  89. printf( "%d", currPtr->data );
  90. if ( currPtr->nextPtr != NULL ) printf( " -> " );
  91. currPtr = currPtr->nextPtr;
  92. }
  93. printf( "\n" );
  94. }
En línea

Saludos, Yoel.
P.D..-   Para mayores dudas, puedes enviarme un mensaje personal (M.P.)
MeCraniDOS


Desconectado Desconectado

Mensajes: 337


Sr. Glass


Ver Perfil
Re: Algoritmo de Dijkstra paso a paso
« Respuesta #9 en: 10 Enero 2015, 18:59 pm »

Recientemente vi una pregunta de un usuario del foro sobre grafos no dirigidos y distancia mínima entre nodos del mismo. Me puse a invetigar el tema, el cual me pareció realmente interesante y decidí escribir un post al respecto.

Si te interesan los grafos y calculo de distancias minimas, tambien le puedes echar un vistazo al Algoritmo de Floyd, tienes que ir calculando matrices bidimensionales como vertices tiene el grafo  :laugh:

Con Dijkstra solo calculas la distancia minima desde un vertice inicial al resto, en cambio con Floyd puedes calcular la distancia minima entre cualquier vertice  :rolleyes:

Por poner un ejemplo, si quieres saber la distancia minima de B a C y F, aplicas Dijkstra, pero ahora quieres saber tambien de D a E y F, tienes que volver a aplicar el algoritmo.
En cambio aplicando una unica vez Floyd sacas todas las distancias minimas entre todos los vertices  ;D

Un saludo
En línea

"La física es el sistema operativo del Universo"
     -- Steven R Garman
Páginas: [1] 2 Ir Arriba Respuesta Imprimir 

Ir a:  

WAP2 - Aviso Legal - Powered by SMF 1.1.21 | SMF © 2006-2008, Simple Machines