CAMINOS ÓPTIMOS
1. Antecedentes.- El origen de la teoría de grafos, desarrollo de diversos algoritmos.
2. Definición.- Definición de lo que es camino optimo
3. Problema del camino más corto.- Problema de los caminos más cortos entre dos nodos.
El problema de los caminos más cortos desde un origen.
El problema de los caminos más cortos con un destino
El problema de los caminos más cortos entre todos los pares de vértices
4. Conceptos fundamentales.
Camino mínimo
Separación de un vértice
Caminos ha miltonianos
5. Algoritmos. Algoritmos más importante para resolver problemas
Algoritmo de Bellman – Ford
Algoritmo de Dijkstra
Algoritmo de Johnson
Algoritmo de Viterbi
Algoritmo de Foulkes
6. Conclusiones
CAMINOS OPTIMOS
1. ANTECEDENTES.-
Es una ciencia matemática que estudia los Grafos. Estas son estructuras matemáticas para
modelar relaciones por pares entre objetos. Un grafo se compone de vértices, nodos o puntos
que están conectados por aristas, arcos, bordes o líneas. Existen varios tipos de grafos y
muchos problemas propuestos para esta ciencia.
El origen de la teoría de grafos se remonta al siglo XVIII con el problema de los puentes de
Königsberg, el cual consistía en encontrar un camino que recorriera los siete puentes del río
Pregel en la ciudad de Königsberg
Primer grafo de la historia
El trabajo de Leonhard Euler sobre el problema titulado: La solución de un problema relativo a
la geometría de la posición.
Para entender este tipo de algoritmo primero debemos saber que está basado en la teoría de
los grafos, también llamada teoría de gráficas
Aquí vemos 3 tipos de grafos para objeto de distinto estudio.
Arco Pez Dodecaedro
2. DEFINICIÓN.
Este método, es un proceso administrativo de planeación, programación, ejecución y control de
todas y cada una de las actividades componentes de un proyecto que debe desarrollarse
dentro de un tiempo óptimo y al costo óptimo.
Para obtener los mejores resultados debe aplicarse a los proyectos que posean las siguientes
características:
Que el proyecto sea único, no repetitivo, en algunas partes o en su totalidad.
Que se deba ejecutar todo el proyecto o parte de el, en un tiempo mínimo, sin variaciones,
es decir, en tiempo crítico.
Que se desee el costo de operación más bajo posible dentro de un tiempo disponible.
El saber qué ruta es la más corta, nos puede ayudar a determinar un mejor itinerario, una
planeación de tiempo más conveniente. Desde luego, que esta aplicación en la importancia de
ahorro de recursos se nota de manera más clara ante problemas de mayor complejidad.
3. PROBLEMA DEL CAMINO MÁS CORTO
Los problemas conocidos como problemas del camino mínimo o camino más corto, tratan
como su nombre indica de hallar la ruta mínima o más corta entre dos puntos. Este mínimo
puede ser la distancia entre los puntos origen y destino o bien el tiempo transcurrido para
trasladarse desde un punto a otro. Se aplica mucho para problemas de redes de
comunicaciones.
Grafos no dirigidos y dirigidos
En los grafos no dirigidos las aristas son doble mano (se puede ir en ambos sentidos). En los
dirigidos en cambio, las aristas se recorren en un único sentido: desde el origen al destino de
la flecha. Si queremos representar que la calle que une las esquinas a y b es doble mano
deberemos poner dos aristas: una que vaya de a a b y otra que vaya de b a a.
Grafo NO dirigido Grafo dirigido
El problema es también conocido como
el problema de los caminos más cortos entre dos nodos, para diferenciarlo de las siguientes
generalizaciones:
El problema de los caminos más cortos desde un origen en el cual tenemos que
encontrar los caminos más cortos de un vértice origen v a todos los demás vértices del
grafo.
El problema de los caminos más cortos con un destino en el cual tenemos que encontrar
los caminos más cortos desde todos los vértices del grafo a un único vértice destino,
esto puede ser reducido al problema anterior invirtiendo el orden.
El problema de los caminos más cortos entre todos los pares de vértices, el cual
tenemos que encontrar los caminos más cortos entre cada par de vértices (v, v') en el
grafo.
4. CONCEPTOS FUNDAMENTALES.
Camino mínimo
Este problema consiste en hallar la mejor forma de ir desde un punto a otro (o a varios
otros) minimizando la distancia recorrida, el tiempo invertido, entre varias posibilidades.
Separación de un vértice
En la teoría de grafos , un subconjunto vértice es un separador de vértice (o corte
vértice, conjunto de separación) para vértices no adyacentes y si la retirada de de los separa
del gráfico y en distintos componentes conectados .
Caminos hamiltonianos
Un ciclo es una sucesión de aristas adyacentes, donde no se recorre dos veces la misma
arista, y donde se regresa al punto inicial. Un ciclo hamiltoniano tiene además que recorrer
todos los vértices exactamente una vez (excepto el vértice del que parte y al cual llega).
Por ejemplo, en un museo grande, lo idóneo sería recorrer todas las salas una sola vez, esto
es buscar un ciclo hamiltoniano en el grafo que representa el museo (los vértices son las salas,
y las aristas los corredores o puertas entre ellas).
Se habla también de Camino hamiltoniano si no se impone regresar al punto de partida, como
en un museo con una única puerta de entrada. Por ejemplo, un caballo puede recorrer todas
las casillas de un tablero de ajedrez sin pasar dos veces por la misma: es un camino
hamiltoniano. Un ejemplo de ciclo hamiltoniano es el grafo del dodecaedro.
Hoy en día, no se conocen métodos generales para hallar un ciclo hamiltoniano en tiempo
polinómico, siendo la búsqueda por fuerza bruta de todos los posibles caminos u otros
métodos excesivamente costosos. Existen, sin embargo, métodos para descartar la existencia
de ciclos o caminos hamiltonianos en grafos pequeños.
Teorema de Koening: Si una red es completa existe al menos un camino hamiltoniano
Teorema de Dirac.- Si una red es simétrica, fuertemente conexa y sin bucles y para
todo nodo se cumple que el número de sus descendientes es mayor o igual que la mitad
del número de nodos tiene al menos un circuito hamiltoniano.
La multiplicación latina.-
Llamado de multiplicación latina, debido a Kaufmann y Malgrange.
Sirve para grafos y digrafos.
Fácilmente adaptable para buscar el circuito hamiltoniano más corto.
Bastante ineficiente.
Idea básica: Si A es la matriz de adyacencia, el elemento (i,j) de Ak es el número
de caminos de longitud k que van de i a j
k=n-1 : caminos hamiltonianos
k=n : circuitos hamiltonianos
5. ALGORITMOS.
Algoritmo de Bellman – Ford
Descripción
El algoritmo de Bellman-Ford determina la ruta más corta desde un nodo origen hacia los
demás nodos para ello es requerido como entrada un grafo cuyas aristas posean pesos. La
diferencia de este algoritmo con los demás es que los pesos pueden tener valores negativos
ya que Bellman-Ford me permite detectar la existencia de un ciclo negativo.
Como trabaja
El algoritmo parte de un vértice origen que será ingresado, a diferencia de Dijkstra que utiliza
una técnica voraz para seleccionar vértices de menor peso y actualizar sus distancias
mediante el paso de relajación, Bellman-Ford simplemente relaja todas las aristas y lo hace |V|
– 1 veces, siendo |V| el número de vértices del grafo.
Para la detección de ciclos negativos realizamos el paso de relajación una vez más y si se
obtuvieron mejores resultados es porque existe un ciclo negativo, para verificar porque
tenemos un ciclo podemos seguir relajando las veces que queramos y seguiremos obteniendo
mejores resultados.
Algoritmo en pseudocódigo
Considerar distancia[ i ] como la distancia más corta del vértice origen ingresado al vértice i y |
V| el número de vértices del grafo.
1 método BellmanFord(Grafo,origen):
2 inicializamos las distancias con un valor grande
3 distancia[ origen ] = 0
4 para i = 1 hasta |V| - 1:
5 para cada arista E del Grafo:
6 sea ( u , v ) vértices que unen la arista E
7 sea w el peso entre vértices ( u , v )
8 Relajacion( u , v , w )
9 para cada arista E del Grafo:
10 sea ( u , v ) vértices que unen la arista E
11 sea w el peso entre vértices ( u , v )
12 si Relajacion( u , v , w )
13 Imprimir “Existe ciclo negativo”
15 Terminar Algoritmo
1 Relajacion( actual , adyacente , peso ):
2 si distancia[ actual ] + peso < distancia[ adyacente ]
3 distancia[ adyacente ] = distancia[ actual ] + peso
Ejemplo y Código paso a paso
Tengamos el siguiente grafo, cuyos ID están en color negro encima de cada vértice, los pesos
esta en color azul y la distancia inicial en cada vértice es infinito
Algunas consideraciones para entender el código que se explicara junto con el funcionamiento
del algoritmo.
1 #define MAX 105 //maximo numero de vértices
2 #define Node pair< int , int > //definimos el nodo como un par( first , second ) donde first es
el vertice adyacente y second el peso de la arista
3 #define INF 1<<30 //definimos un valor grande que represente la distancia infinita inicial,
basta conque sea superior al maximo valor del peso en alguna de las aristas
Inicializamos los valores de nuestros arreglos:
Dónde:
Vértices: ID de los vértices.
Distancia[ u ]: Distancia más corta desde vértice inicial a vértice con ID = u.
Previo[ u ]: Almacenara el ID del vértice anterior al vértice con ID = u, me servirá para
impresión del camino más corto.
En cuanto al código los declaramos de la siguiente manera:
1 vector< Node > ady[ MAX ]; //lista de adyacencia
2 int distancia[ MAX ]; //distancia[ u ] distancia de vértice inicial a vértice con ID = u
3 int previo[ MAX ]; //para la impresion de caminos
4 int V; //numero de vertices
Y la función de inicialización será simplemente lo siguiente:
1 //función de inicialización
2 void init(){
3 for( int i = 0 ; i <= V ; ++i ){
4 distancia[ i ] = INF; //inicializamos todas las distancias con valor infinito
5 previo[ i ] = -1; //inicializamos el previo del vértice i con -1
6 }
7 }
De acuerdo al vértice inicial que elijamos cambiara la distancia inicial, por ejemplo la ruta más
corta partiendo del vértice 1 a todos los demás vértices.
Inicialmente la distancia de vértice 1 -> vértice 1 es 0 por estar en el mismo lugar.
1 distancia[ inicial ] = 0; //Este paso es importante, inicializamos la distancia del inicial como 0
Hasta este momento la tabla quedaría de la siguiente manera:
Ahora según Bellman-Ford debemos realizar el paso de relajación |V| – 1 = 5 – 1 = 4 veces
para cada arista:
1 for( int i = 1 ; i <= V - 1 ; ++i ){ //Iteramos |V| - 1 veces
2 …
3 }
Primera Iteración
Empezamos con las aristas que parten del vértice 1:
Como podemos observar tenemos 2 aristas, la que une vértice 1 con vértice 2 y vértice 1 con
vértice 4. Ello en código:
1 for( int actual = 1 ; actual <= V ; ++actual ){ //Estos dos for = O(E)
2 for( int j = 0 ; j < ady[ actual ].size() ; ++j ){ //reviso sus adyacentes del vertice actual
3 int adyacente = ady[ actual ][ j ].v; //id del vertice adyacente
4 int peso = ady[ actual ][ j ].w; //peso de la arista que une actual con adyacente
( actual , adyacente )
5 …
6 }
7 }
Las aristas de acuerdo al código serian de la forma e = (actual , adyacente ,
peso) donde actual es el vértice de donde partimos (en este caso sería 1) adyacente son los
vértices que unen la arista e (en este caso serían los vértices 2 y 4) y peso son los pesos de
cada arista (en este caso tendríamos 7 y 2).
Evaluamos primero para vértice 2:
Vemos que la distancia actual desde el vértice inicial a 2 es ∞, verifiquemos el paso de
relajación:
distancia[ 1 ] + 7 < distancia[ 2 ] -> 0 + 7 < ∞ -> 7 < ∞
El paso de relajación es posible realizarlo entonces actualizamos la distancia en el vértice 2
quedando:
El paso de relajación se daría en la siguiente parte:
1 for( int actual = 1 ; actual <= V ; ++actual ){ //Estos dos for = O(E)
2 for( int j = 0 ; j < ady[ actual ].size() ; ++j ){ //reviso sus adyacentes del vertice actual
3 int adyacente = ady[ actual ][ j ].v; //id del vertice adyacente
4 int peso = ady[ actual ][ j ].w; //peso de la arista que une actual con adyacente
( actual , adyacente )
5 //Realizamos paso de relajacion para la arista actual
6 relajacion( actual , adyacente , peso );
7 }
8 }
Donde la función de relajación seria
1 //Paso de relajacion
2 void relajacion( int actual , int adyacente , int peso ){
3 //Si la distancia del origen al vertice actual + peso de su arista es menor a la distancia
del origen al vertice adyacente
4 if( distancia[ actual ] + peso < distancia[ adyacente ] ){
5 distancia[ adyacente ] = distancia[ actual ] + peso; //relajamos el vertice actualizando
la distancia
6 previo[ adyacente ] = actual; //a su vez actualizamos el vertice previo
7 Q.push( Node( adyacente , distancia[ adyacente ] ) ); //agregamos adyacente a la cola
de prioridad
8 }
9 }
Ahora evaluamos al siguiente adyacente que es el vértice 4:
De manera similar al anterior vemos que la distancia actual desde el vértice inicial a 4 es ∞,
verifiquemos el paso de relajación:
distancia[ 1 ] + 2 < distancia[ 4 ] -> 0 + 2 < ∞ -> 2 < ∞
El paso de relajación es posible realizarlo entonces actualizamos la distancia en el vértice 4
quedando:
Hemos terminado las aristas que parten del vértice 1, continuamos con las aristas que parten
del vértice 2:
Comenzamos por el vértice 3 y realizamos paso de relajación:
Distancia[ 2 ] + 1 < distancia[ 3 ] -> 7 + 1 < ∞ -> 8 < ∞
En esta oportunidad hemos encontrado una ruta más corta partiendo desde el vértice inicial al
vértice 3, actualizamos la distancia en el vértice 3 y actualizamos el vértice previo al actual
quedando:
Ahora continuamos con la arista que une al vértice 2 con el vértice 4:
En este caso vemos que no se lleva acabo el paso de relajación:
distancia[ 2 ] + 2 < distancia[ 4 ] -> 7 + 2 < 2 -> 9 < 2
Por lo tanto no actualizamos valores en la tabla. Ahora el siguiente vértice a evaluar es el
vértice 3 que posee una sola arista:
Como el peso de su vértice adyacente es infinito actualizamos la distancia:
Ahora el siguiente vértice a evaluar es el vértice 4 que posee
cuatro aristas:
Realizamos paso de relajación para cada vértice adyacente:
Con vértice 2: distancia[ 4 ] + 3 < distancia[ 2 ] -> 2 + 3 < 7 -> 5 < 7
Con vértice 3: distancia[ 4 ] + 8 < distancia[ 3 ] -> 2 + 8 < 8 -> 10 < 8
Con vértice 5: distancia[ 4 ] + 5 < distancia[ 5 ] -> 2 + 5 < 13 -> 7 < 13
Actualizamos distancias para los vértices 2 y 5:
Ahora continuamos con vértice 5:
En este caso no actualizamos las distancias. Hemos terminado la primera iteración,
continuemos:
Segunda Iteración:
Luego de la segunda iteración obtendremos lo siguiente:
En esta iteración solamente se realizó el paso de relajación en la arista que une vértices 2 y 3.
Para el grafo dado en la segunda iteración ya habremos obtenido la ruta más corta partiendo
del vértice 1 a todos los demás vértices. Sin embargo no siempre obtendremos el óptimo en la
2da iteración, todo dependerá del grafo ingresado
Algoritmo de Dijkstra
Para el problema de la ruta corta tenemos varios algoritmos, en esta oportunidad se explicará el
algoritmo de dijkstra el cual usa una técnica voraz (greedy). Al final del articulo se encuentran
adjuntas las implementaciones en C++ y JAVA.
Descripción
El algoritmo de dijkstra determina la ruta más corta desde un nodo origen hacia los demás
nodos para ello es requerido como entrada un grafo cuyas aristas posean pesos. Algunas
consideraciones:
Si los pesos de mis aristas son de valor 1, entonces bastará con usar el algoritmo de BFS.
Si los pesos de mis aristas son negativos no puedo usar el algoritmo de dijsktra, para pesos
negativos tenemos otro algoritmo llamado Algoritmo de Bellmand-Ford.
Como trabaja
Primero marcamos todos los vértices como no utilizados. El algoritmo parte de un vértice origen
que será ingresado, a partir de ese vértices evaluaremos sus adyacentes, como dijkstra usa
una técnica greedy – La técnica greedy utiliza el principio de que para que un camino sea
óptimo, todos los caminos que contiene también deben ser óptimos- entre todos los vértices
adyacentes, buscamos el que esté más cerca de nuestro punto origen, lo tomamos como punto
intermedio y vemos si podemos llegar más rápido a través de este vértice a los demás.
Después escogemos al siguiente más cercano (con las distancias ya actualizadas) y repetimos
el proceso. Esto lo hacemos hasta que el vértice no utilizado más cercano sea nuestro destino.
Al proceso de actualizar las distancias tomando como punto intermedio al nuevo vértice se le
conoce como relajación (relaxation).
Dijkstra es muy similar a BFS, si recordamos BFS usaba una Cola para el recorrido para el
caso de Dijkstra usaremos una Cola de Prioridad o Heap, este Heap debe tener la propiedad de
Min-Heap es decir cada vez que extraiga un elemento del Heap me debe devolver el de menor
valor, en nuestro caso dicho valor será el peso acumulado en los nodos.
Tanto java como C++ cuentan con una cola de prioridad ambos implementan un Binary Heap
aunque con un Fibonacci Heap la complejidad de dijkstra se reduce haciéndolo mas eficiente,
pero en un concurso mas vale usar la librería que intentar programar una nueva estructura
como un Fibonacci Heap, claro que particularmente uno puede investigar y programarlo para
saber como funciona internamente.
Algoritmo en pseudocódigo
Considerar distancia[ i ] como la distancia mas corta del vértice origen ingresado al vértice i.
1 método Dijkstra(Grafo,origen):
2 creamos una cola de prioridad Q
3 agregamos origen a la cola de prioridad Q
4 mientras Q no este vacío:
5 sacamos un elemento de la cola Q llamado u
6 si u ya fue visitado continuo sacando elementos de Q
7 marcamos como visitado u
8 para cada vértice v adyacente a u en el Grafo:
9 sea w el peso entre vértices ( u , v )
10 si v no ah sido visitado:
11 Relajacion( u , v , w )
1 método Relajacion( actual , adyacente , peso ):
2 si distancia[ actual ] + peso < distancia[ adyacente ]
3 distancia[ adyacente ] = distancia[ actual ] + peso
4 agregamos adyacente a la cola de prioridad Q
Ejemplo y Código paso a paso
Tengamos el siguiente grafo, cuyos ID están en color negro encima de cada vértice, los pesos
esta en color azul y la distancia inicial en cada vértice es infinito
Algunas consideraciones para entender el código que se explicara junto con el funcionamiento
del algoritmo.
1 #define MAX 10005 //maximo numero de vértices
2 #define Node pair< int , int > //definimos el nodo como un par( first , second ) donde first es
el vertice adyacente y second el peso de la arista
3 #define INF 1<<30 //definimos un valor grande que represente la distancia infinita inicial,
basta conque sea superior al maximo valor del peso en alguna de las aristas
Inicializamos los valores de nuestros arreglos
Donde:
Vértices: ID de los vértices.
Distancia[ u ]: Distancia mas corta desde vértice inicial a vértice con ID = u.
Visitado[ u ]: 0 si el vértice con ID = u no fue visitado y 1 si ya fue visitado.
Previo[ u ]: Almacenara el ID del vértice anterior al vértice con ID = u, me servirá para
impresión del camino mas corto.
En cuanto al código los declaramos de la siguiente manera:
1 vector< Node > ady[ MAX ]; //lista de adyacencia
2 int distancia[ MAX ]; //distancia[ u ] distancia de vértice inicial a vértice con ID = u
3 bool visitado[ MAX ]; //para vértices visitados
4 int previo[ MAX ]; //para la impresion de caminos
priority_queue< Node , vector<Node> , cmp > Q; //priority queue propia del c++, usamos
5
el comparador definido para que el de menor valor este en el tope
int V; //numero de vértices
6
Y la función de inicialización será simplemente lo siguiente
1 //función de inicialización
2 void init(){
3 for( int i = 0 ; i <= V ; ++i ){
4 distancia[ i ] = INF; //inicializamos todas las distancias con valor infinito
5 visitado[ i ] = false; //inicializamos todos los vértices como no visitado
6 previo[ i ] = -1; //inicializamos el previo del vértice i con -1
7 }
8 }
De acuerdo al vértice inicial que elijamos cambiara la distancia inicial, por ejemplo la ruta más
corta partiendo del vértice 1 a todos los demás vértices:
El vértice 1 es visitado, la distancia de vértice 1 -> vértice 1 es 0 por estar en el mismo lugar.
1 Q.push( Node( inicial , 0 ) ); //Insertamos el vértice inicial en la Cola de Prioridad
2 distancia[ inicial ] = 0; //Este paso es importante, inicializamos la distancia del inicial como 0
Extraemos el tope de la cola de prioridad
1 while( !Q.empty() ){ //Mientras cola no este vacia
2 actual = Q.top().first; //Obtengo de la cola el nodo con menor peso, en un
comienzo será el inicial
3 Q.pop(); //Sacamos el elemento de la cola
Si el tope ya fue visitado entonces no tengo necesidad de evaluarlo, por ello continuaría
extrayendo elementos dela cola:
1 if( visitado[ actual ] ) continue; //Si el vértice actual ya fue visitado entonces sigo sacando
elementos de la cola
En este caso al ser el tope el inicial no esta visitado por lo tanto marcamos como visitado.
1 visitado[ actual ] = true; //Marco como visitado el vértice actual
Hasta este momento la tabla quedaría de la siguiente manera
Ahora vemos sus adyacentes que no hayan sido visitados. Tendríamos 2 y 4.
1 for( int i = 0 ; i < ady[ actual ].size() ; ++i ){ //reviso sus adyacentes del vertice actual
2 adyacente = ady[ actual ][ i ].first; //id del vertice adyacente
3 peso = ady[ actual ][ i ].second; //peso de la arista que une actual con adyacente
( actual , adyacente )
4 if( !visitado[ adyacente ] ){ //si el vertice adyacente no fue visitado
Evaluamos primero para vértice 2
Vemos que la distancia actual desde el vértice inicial a 2 es ∞, verifiquemos el paso de
relajación:
distancia[ 1 ] + 7 < distancia[ 2 ] -> 0 + 7 < ∞ -> 7 < ∞
El paso de relajación es posible realizarlo entonces actualizamos la distancia en el vértice 2 y
agregando el vértice en la cola de prioridad con nueva distancia quedando:
El paso de relajación se daría en la siguiente parte:
1 for( int i = 0 ; i < ady[ actual ].size() ; ++i ){ //reviso sus adyacentes del vertice actual
2 adyacente = ady[ actual ][ i ].first; //id del vertice adyacente
3 peso = ady[ actual ][ i ].second; //peso de la arista que une actual con adyacente
( actual , adyacente )
4 if( !visitado[ adyacente ] ){ //si el vertice adyacente no fue visitado
5 relajacion( actual , adyacente , peso ); //realizamos el paso de relajacion
6 }
7 }
Donde la función de relajación seria
1 //Paso de relajacion
2 void relajacion( int actual , int adyacente , int peso ){
3 //Si la distancia del origen al vertice actual + peso de su arista es menor a la distancia
del origen al vertice adyacente
4 if( distancia[ actual ] + peso < distancia[ adyacente ] ){
5 distancia[ adyacente ] = distancia[ actual ] + peso; //relajamos el vertice actualizando la
distancia
6 previo[ adyacente ] = actual; //a su vez actualizamos el vertice previo
7 Q.push( Node( adyacente , distancia[ adyacente ] ) ); //agregamos adyacente a la cola
de prioridad
8 }
9 }
Ahora evaluamos al siguiente adyacente que es el vértice 4:
De manera similar al anterior vemos que la distancia actual desde el vértice inicial a 4 es ∞,
verifiquemos el paso de relajación:
distancia[ 1 ] + 2 < distancia[ 4 ] -> 0 + 2 < ∞ -> 2 < ∞
El paso de relajación es posible realizarlo entonces actualizamos la distancia en el vértice 4
quedando:
En cuanto a la cola de prioridad como tenemos un vértice con menor peso este nuevo vértice
ira en el tope de la cola:
Revisamos sus adyacentes no visitados que serian vértices 2, 3 y 5.
Empecemos por el vértice 2:
Ahora vemos que la distancia actual del vértice inicial al vértice 2 es 7, verifiquemos el paso de
relajación:
distancia[ 4 ] + 3 < distancia[ 2 ] -> 2 + 3 < 7 -> 5 < 7
En esta oportunidad hemos encontrado una ruta mas corta partiendo desde el vértice inicial al
vértice 2, actualizamos la distancia en el vértice 2 y actualizamos el vértice previo al actual
quedando:
En cuanto a la cola de prioridad como tenemos un vértice con menor peso este nuevo vértice
ira en el tope de la cola, podemos ver que tenemos 2 veces el mismo vértice pero como
usamos una técnica greedy siempre usaremos el valor óptimo:
Continuamos con los Vértices 3 y 5 como tienen valor ∞ si será posible relajarlos por lo que
sería similar a los pasos iniciales solo que en los pasos iniciales distancia[ 1 ] era 0 en este
caso distancia[ 4 ] es 2, quedando:
Hemos terminado de evaluar al vértice 4, continuamos con el tope de la cola que es vértice 2, el
cual marcamos como visitado.
Los adyacentes no visitados del vértice 2 son los vértices 3 y 5. Comencemos con el vértice 3
Ahora vemos que la distancia actual del vértice inicial al vértice 3 es 10, verifiquemos el paso de
relajación:
distancia[ 2 ] + 1 < distancia[ 3 ] -> 5 + 1 < 10 -> 6 < 10
En esta oportunidad hemos encontrado una ruta mas corta partiendo desde el vértice inicial al
vértice 3, dicha ruta sería 1 -> 4 -> 2 -> 3 cuyo peso es 6 que es mucho menor que la ruta 1 – >
4 -> 3 cuyo peso es 10, actualizamos la distancia en el vértice 3 quedando:
El siguiente vértice de la cola de prioridad es el vértice 3 y su único adyacente no visitado es el
vértice 5.
Vemos que la distancia actual del vértice inicial al vértice 5 es 7, verifiquemos el paso de
relajación:
distancia[ 3 ] + 5 < distancia[ 5 ] -> 6 + 5 < 7 -> 11 < 7
En esta oportunidad se no cumple por lo que no relajamos el vértice 5, por lo que la tabla en
cuanto a distancias no sufre modificaciones y no agregamos vértices a la cola:
Ahora tocaría el vértice 2 pero como ya fue visitado seguimos extrayendo elementos de la cola,
el siguiente vértice será el 5.
Al ser el ultimo vértice a evaluar no posee adyacentes sin visitar por lo tanto hemos terminado
el algoritmo. En el grafico anterior observamos que 2 aristas no fueron usadas para la
relajación, las demás si fueron usadas. La tabla final quedaría de la siguiente manera:
De la tabla si deseo saber la distancia mas corta del vértice 1 al vértice 5, solo tengo que
acceder al valor del arreglo en su índice respectivo (distancia[ 5 ]).
Shortest Path Tree
En el proceso anterior usábamos el arreglo previo[ u ] para almacenar el ID del vértice previo al
vértice con ID = u, ello me sirve para formar el árbol de la ruta mas corta y además me sirve
para imprimir caminos de la ruta mas corta.
Impresión del camino encontrado.
Para imprimir el camino mas corto deseado usamos el arreglo previo[ u ], donde u tendrá el ID
del vértice destino, o sea si quiero imprimir el camino mas corto desde vértice 1 -> vértice 3
partiré desde previo[ 3 ] hasta el previo[ 1 ]. De manera similar a lo que se explico en el
algoritmo BFS, en este caso se realizara de manera recursiva:
1 //Impresion del camino mas corto desde el vertice inicial y final ingresados
2 void print( int destino ){
3 if( previo[ destino ] != -1 ) //si aun poseo un vertice previo
4 print( previo[ destino ] ); //recursivamente sigo explorando
5 printf("%d " , destino ); //terminada la recursion imprimo los vertices recorridos
6 }
Veamos gráficamente el funcionamiento, desde el grafo comenzamos en 3
El previo de 3 es el vértice 2, por lo tanto ahora evaluó 2:
Ahora el previo de 2 es el vértice 4
El previo de 4 es el vértice inicial 1
Como el previo de 1 es -1 terminamos el recorrido, ahora en el retorno de las llamadas
recursivas imprimo el camino: 1 4 2
Algoritmo de Johnson
El algoritmo de Johnson se utiliza para programar actividades que siguen una secuencia fija en
dos centros de trabajo, es decir, primero pasan por una máquina I y luego por una máquina II;
en este caso Centro de Trabajo I y luego Centro de Trabajo II; claro está que las actividades no
pueden tener ninguna relación de dependencia entre sí.
Es utilizado con el objetivo claro de optimizar el tiempo de terminación del proyecto cuando este
se encuentra bajos las características señaladas.
PASOS PARA EL ALGORITMO DE JOHNSON
Básicamente para le regla de JOHNSON se puede seguir los siguientes criterios generales:
Escoger el tiempo de menor duración entre todas las actividades y centros de trabajo, si
este tiempo se halla en el centro de trabajo I, la actividad se programa al inicio. De lo contrario,
si se encuentra en el centro de trabajo II se programa al final.
Se repiten los pasos cada vez eliminando la actividad ya programada hasta que todas las
actividades se encuentren programadas.
Algoritmo de Viterbi
El algoritmo de Viterbi es una programación dinámica algoritmo para encontrar el
más probable secuencia de estados ocultos-llama el camino de Viterbi -que da lugar a una
secuencia de eventos observados, especialmente en el contexto de las fuentes de información
de Markov y modelos ocultos de Markov .
El algoritmo ha encontrado una aplicación universal en la descodificación de los códigos
convolucionales utilizados tanto en CDMA y GSM celular digital, dial-up módems, las
comunicaciones por satélite, en el espacio profundo, y 802.11 LAN inalámbricas. Ahora también
se usa comúnmente en el reconocimiento de voz , síntesis de voz , diarización , la localización
de palabras clave , la lingüística computacional y bioinformática . Por ejemplo, en voz a
texto (reconocimiento de voz), la señal acústica se trata como la secuencia observada de los
acontecimientos, y una cadena de texto se considera que es la "causa oculta" de la señal
acústica. El algoritmo de Viterbi encuentra el más probable cadena de texto dada la señal
acústica.
Algoritmo de Foulkes
Es uno de los métodos heurísticos más sencillos y eficientes para resolver el problema el
viajante de comercio.
Supongamos una res simétrica (razón por la que hemos considerado solo aristas) y tal que
cada arista tiene asignado dos costes según en el sentido en el que se recorra.
6. CONCLUSIONES
Los caminos cortos reducen costos en tiempo para desplazarse de un lugar a otro.
Los viajes dejan de ser tediosos y largos por lugares que necesariamente antes se tenían
que pasar obligatoriamente.
Ahorran en el uso de combustible de los motorizados
Haciendo uso de estos métodos se disminuye considerablemente el tiempo de respuesta
en la búsqueda de caminos mínimos por lo que se garantiza la eficiencia.