Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Modulo1 Lectura2

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 11

Bloque 1Bloque 2Bloque 3Bloque 4Referencias

Caminos mínimos en Grafos

1. Problema de los caminos mínimos

Uno de los problemas más comunes en la teoría de grafos consiste en encontrar el


camino mínimo entre 2 vértices dados. Por ejemplo, si tenemos un grafo representando
aeropuertos y rutas de vuelo de una o varias compañías aéreas, podríamos usarlo para
consultar cuál es la ruta más económica para ir desde Buenos Aires hasta Hong Kong. O,
quizás, deseamos encontrar la ruta más corta, con menos aeropuertos intermedios, o con
menos horas de vuelo. En cualquier caso, debemos almacenar, para cada tramo, los
pesos correspondientes a cada criterio (costo del billete, distancia, tiempo de vuelo).
Nótese que cada tramo es una arista del grafo.

La resolución al problema de los caminos mínimos tiene diferentes algoritmos de


resolución y diferentes complejidades, dependiendo de las características del grafo. La
resolución es bastante simple, en el caso de los grafos no ponderados. Si las aristas
tienen pesos positivos, su resolución es algo más compleja. Para el caso más general,
cuando las aristas tienen pesos positivos o negativos, la resolución es todavía más
compleja. A continuación, exploramos cada uno de estos casos.

2. Problema del camino mínimo sin pesos

El problema del camino mínimo sin pesos con un único origen consiste en encontrar el
camino más corto, medido por el número de aristas, del vértice de origen a cualquier otro
vértice del grafo. Es importante resaltar que este problema se suele asociar a los grafos
no ponderados, ya que, en este caso, la longitud del camino entre dos vértices está dada
por el número de aristas que componen dicho camino.

Figura 1: Grafo no ponderado

Fuente: elaboración propia.


En el grafo de la figura 1, asumimos que el vértice de origen O es V2. El

algoritmo que resuelve el problema de los caminos mínimos sin pesos


comienza encontrando el camino de longitud 0 al nodo de origen. Por
definición, el camino más corto, desde O hasta V2, es un camino de longitud
0.

Ahora, miramos los vértices a distancia 1 de O (V0 y V5), a continuación,


buscamos los vértices a distancia 2 y así sucesivamente. Esta estrategia se
llama búsqueda en anchura o breadth first search (BFS). En la lectura 4,
veremos cómo funciona esta estrategia para encontrar los caminos mínimos
desde el vértice de origen hasta cada uno de los vértices del grafo.

Nótese que habrá algunos casos en los que existan vértices no alcanzables
desde el vértice de origen. En este caso, diremos que la distancia a dichos
vértices es infinita. Por ejemplo, en la figura 1, si el vértice de origen es V4,

entonces, el vértice V6 se encuentra a distancia 1, el vértice V5 se encuentra

a distancia 2 y el resto de vértices están a distancia infinita, ya que no es


posible construir un camino entre V4 y dichos vértices.

Figura 2: Principio de funcionamiento

Fuente: elaboración propia.

El principio de funcionamiento de dicho algoritmo está ejemplificado en la


figura 2. Nótese que, desde el vértice de origen O, vamos construyendo un
camino de distancia Dv, hasta un nodo v. Para todo nodo w adyacente a v,
hay un camino de distancia Dv+1 desde O a w.
El algoritmo de búsqueda empieza asumiendo que Dw=∞, para todo vértice

w diferente a O. Luego, el grafo se explora en anchura y, cada vez que se


llega a un vértice w cuya distancia es ∞, se reduce su Dw, que pasa a valer

Dv+1, donde Dv es el nodo que estamos explorando actualmente. Se debería


notar que el hecho de que el algoritmo finalice no garantiza que todos los
vértices del grafo finalizan con valores Dw diferentes a ∞; solo aquellos

vértices alcanzables desde el vértice de origen tendrán un valor de Dw


diferente a ∞. Recordemos que un vértice w es alcanzable, desde un vértice

v, si existe un camino que lleve desde v a w.


Analicemos, ahora, qué estructuras de datos necesitamos para implementar el algoritmo
descripto anteriormente. Existen dos acciones básicas a realizar:

necesitamos buscar repetidamente el vértice a procesar; y


necesitamos comprobar, rápidamente, los vértices adyacentes al vértice actual.

Omitamos, por un momento, el primer problema y enfoquemos nuestra atención en el


segundo problema, que es más simple de resolver. Esto es así, ya que hemos visto que
los grafos se representan, en general, usando una lista o una matriz de adyacencia. En
cualquier caso, los vértices adyacentes se encuentran iterando sobre dicha estructura. En
el caso de las listas de adyacencia, simplemente, accederemos a cada uno de los nodos
de la lista enlazada para dicho vértice. En el caso de las matrices de adyacencia,
tendremos que recorrer toda la fila correspondiente al vértice procesado, para detectar
aquellos vértices que son adyacentes al actual (es decir, si el vértice procesado es v,
quiero detectar todos los vértices w, tal que m[v][w]=1). Obviamente, para realizar esta
operación, es más eficiente emplear una lista de adyacencia para implementar el grafo, ya
que la matriz obliga a recorrer posiciones que no necesariamente pertenecen a la lista de
adyacencia del vértice que está siendo procesado. Notemos, además, que, si usamos una
lista de adyacencia, cada arista se procesará, a lo sumo, una vez, por lo que el costo total
de las iteraciones sobre las listas de adyacencia será de O(|E|).

Por ejemplo, si nos remitimos al grafo de la figura 1, observaremos que, si el


vértice de origen fuese V2, entonces, todas las aristas del grafo serían

procesadas exactamente una vez, ya que todos los vértices del grafo son
alcanzables desde dicho vértice de origen. Por otra parte, si el vértice de
origen fuese V4, solo las aristas que conectan a los vértices V5 y V6 serían

procesadas una vez, ya que el resto de vértices no son alcanzables desde


dicho vértice de origen.
Volvamos, ahora, al primer problema que intenta encontrar, rápidamente, cuál
es el siguiente vértice a procesar. Si empleamos un vector que contiene listas
de adyacencia para cada vértice del grafo, explorar el vector que la
implementa buscando un vértice en particular tiene un costo medio de O(|V|),
que es el tamaño de dicho vector. Pero esta operación se hace O(|V|) veces,
ya que, para cada vértice que está siendo procesado, debemos decidir cuál
es el siguiente vértice a procesar. Esta búsqueda secuencial sobre el vector
es demasiado costosa. Para resolver este problema, notamos que, en lugar
de considerar todos los vértices del grafo como potenciales candidatos a ser
el próximo vértice a procesar, podemos considerar, solamente, aquellos
vértices w que han visto rebajado su Dw desde ∞. Para poder procesar

exclusivamente dichos vértices y no otros, necesitamos almacenarlos en una


estructura auxiliar. Debido a que deseamos procesar los vértices en el orden
en que su distancia deja de ser ∞, parece apropiado usar una cola como

estructura auxiliar
Usando una cola, el algoritmo comienza tomando el vértice de origen y
poniéndolo en una cola vacía. A continuación, tomamos el primer vértice de
la cola, recorremos su lista de adyacencia y los ponemos en la cola. Todos
estos vértices estarán a distancia Dv+1 respecto del vértice actual v. El
algoritmo continúa hasta que la cola queda vacía. Nótese que el uso de una
cola posibilita procesar primero todos aquellos vértices que están a distancia
1 del vértice de origen, luego, aquellos vértices que están a distancia 2, a
continuación, los vértices que están a distancia 3, etcétera. Recordemos que,
cada vez que estemos procesando los vértices adyacentes al vértice actual,
solo introduciremos en la cola los vértices w que ven decrementada su Dw
desde ∞. El uso de la cola permite que el costo para elegir el siguiente

vértice a procesar sea constante (O(1)), por lo que el costo total de esta
operación sobre todos los vértices del grafo será de O(|V|).
Con este análisis ya podemos saber cuál es el costo total del algoritmo,
basándonos en las dos operaciones descriptas anteriormente. La elección del
siguiente vértice a procesar cuesta, en total, O(|V|), mientras que descubrir
los vértices adyacentes al vértice que está siendo procesado tiene un costo
total de O(|E|). En otras palabras, el costo total del algoritmo, desde que
comienza hasta que procesa todos los vértices, es de O(|V|+|E|).

3. Problema de los caminos mínimos con pesos


positivos

El problema de los caminos mínimos con pesos positivos y origen único consiste en
encontrar el camino más corto desde el vértice origen O hasta el resto de los vértices del
grafo, donde el peso o costo de cada arista del grafo es un valor no negativo. Debemos
recordar que, en el caso de los grafos ponderados, la longitud de un camino con pesos es
la suma del costo de las aristas del camino. Al restringir el problema a grafos con aristas
de peso positivo, garantizamos que cualquier camino del grafo tiene un costo positivo.
Esto permite usar un algoritmo sencillo y eficiente para encontrar todos los caminos
mínimos desde un vértice de origen. Este fue propuesto por el científico holandés Edsger
Dijkstra. Dijkstra es uno de los mayores contribuidores a las Ciencias de la Computación.

Entre sus contribuciones a las ciencias de la computación está la solución del


problema del camino más corto, también conocido como el algoritmo de
Dijkstra, la notación polaca inversa y el relacionado algoritmo shunting yard …
el algoritmo del banquero y la construcción del semáforo para coordinar
múltiples procesadores y programas. Otro concepto debido a Dijkstra, en el
campo de la computación distribuida, es el de la auto-estabilización, una vía
alternativa para garantizar la confiabilidad del sistema. (Matemáticos en
México, s.f., t.ly/4pHP).

Figura 3: Edsger Wybe Dijkstra


Fuente: captura de pantalla de Wikipedia (t.ly/LjOl).

El algoritmo de Dijkstra para la resolución del problema de los caminos


mínimos con pesos positivos es muy similar al algoritmo que resuelve el
problema de los caminos mínimos sin pesos. Este algoritmo debe resolver
dos problemas principales:

cómo ajustamos el valor de Dw de cada vértice w del grafo; y


cómo buscamos el siguiente vértice v a ser procesado.

En el primer problema, debemos notar que, a diferencia del problema del


camino mínimo sin pesos, cada vértice w puede ser visitado varias veces y su
Dw se puede actualizar múltiples veces.

Tomemos el ejemplo de la figura 4. Como vemos en este grafo, el vértice u ya


ha sido procesado, Du tiene un valor de 2 y, al procesar este vértice, hemos
decrementado Dw a 8, ya que existe una arista de peso 6 que conduce a w
desde u. En el algoritmo de los caminos mínimos sin pesos, el vértice w iría a
la cola y Dw no volvería a ser modificado. Sin embargo, cuando existen
pesos, podría ocurrir que, al procesar otro vértice como v, podamos encontrar
un camino de menor peso que conduzca hasta w. En el gráfico de la figura 4,
observamos que existe un camino de costo 3 que va desde el origen hasta v
y una arista de costo 2 que va desde v hasta w, por lo que podríamos
encontrar un camino de menor costo que vaya desde el vértice de origen
hasta w, pasando por v. En este ejemplo, ese camino tendría costo 5.
Basados en este ejemplo, podemos resolver el problema de saber cómo
ajustar el valor de Dw de cada vértice del grafo, haciendo

Dw=Dv+cv,w

Si Dv+cv,w es menor que el valor actual de Dw, para cada vértice w

adyacente al vértice v que está siendo procesado actualmente.


Figura 4: Actualización de Dw

Fuente: elaboración propia.

El segundo problema plantea la elección del siguiente vértice a ser procesado. En el caso
del algoritmo que resuelve el problema de los caminos mínimos sin pesos, usamos una
cola, como estructura auxiliar, para determinar el orden en que los vértices debían ser
procesados. En este caso, debemos prestar atención a un teorema que dice que debemos
posar nuestra atención en vértices no visitados que minimicen el valor Dv para cada
vértice v del grafo, con el propósito de garantizar que el algoritmo produzca correctamente
los caminos mínimos. En otras palabras, el siguiente vértice a procesar, en cada paso del
algoritmo, debiera ser aquel vértice v, todavía no procesado, cuyo Dv sea el menor entre
todos los vértices no procesados.

Dicho esto, podemos comprobar, rápidamente, que una cola no es una estructura
adecuada para este propósito. Para ver esto, supongamos, usando la figura 4, que luego
de procesar u ponemos a w en la cola existente. Actualmente, estamos procesando todos
los vértices de distancia 2 desde el origen. El orden en que los insertamos en la cola
podría estar relacionado con el costo de las aristas de la lista de adyacencia de u.
Supongamos que existe una arista de costo 5 que va desde u hasta un vértice z.
Entonces, z se insertaría antes que w en dicha cola. Sin embargo, al procesar v,
cambiaríamos el valor de Dw de 8 a 5. Esto significaría que, ahora, deberíamos procesar
w antes que z. Sin embargo, z fue insertado antes que w en la cola y, por lo tanto, será
procesado antes. Con esto, vemos que una cola no es apropiada, sin embargo, una cola
de prioridad, ordenada por Dw, sí sería de utilidad y cumpliría exactamente con el
propósito deseado. En el ejemplo que veníamos siguiendo, al procesar el vértice v,
insertaríamos en la cola de prioridad el par (w,5).

Nótese que, en la estructura, existiría ya una instancia (w,8). El hecho que existan dos
instancias simplemente indicaría que existen, al menos, 2 caminos que van desde el
vértice de origen hasta el vértice w, uno de costo 5 y el otro de costo 8. Eventualmente,
sacaríamos de la cola de prioridad el par (w,5), procederíamos a procesar el vértice w y
dejaríamos 5 como valor final de Dw.
Eventualmente, cuando sacásemos de la cola de prioridad el par (w,8), simplemente lo
descartaríamos, ya que el vértice w figuraría en nuestra lista de vértices procesados.
Notemos que, en la elección del nuevo vértice v a ser procesado, eliminamos
repetidamente el menor elemento de la cola de prioridad, hasta sacar un vértice no
visitado. Ya que la cola de prioridad contendrá a lo sumo tantos elementos como aristas
haya en el grafo, realizaremos |E| inserciones y eliminaciones, donde cada una de estas
operaciones cuesta O(log N), siendo N=O(|E|). Por lo tanto, el costo total de la elección del
nuevo vértice será de O(|E| log |E|) y ese es, a su vez, el costo total del algoritmo, ya que,
al igual que en el caso del algoritmo que encuentra todos los caminos mínimos sin pesos,
cuesta O(|E|) el determinar todos los vértices adyacentes a cada vértice que está siendo
procesado. Por lo tanto, el costo total estaría dado por O(|E| log |E| + |E|), que es lo mismo
que O(|E| log |E|), por propiedades de la notación O.

En la lectura 4, observamos un ejemplo de cómo el algoritmo de Dijkstra se comporta para


un grafo de ejemplo.

4. Problema de los caminos mínimos con pesos

El problema de los caminos mínimos con pesos y origen único consiste en encontrar el
camino más corto desde el vértice origen O hasta el resto de vértices del grafo, donde el
peso de cada arista del grafo puede ser un valor positivo, negativo o cero. Claramente,
este caso es el más general de todos los problemas que resuelve el problema de los
caminos mínimos. Si restringimos el problema a grafos que contienen aristas de peso
positivo, estamos frente al problema resuelto en la sección 4.3. Si restringimos el
problema aún más y consideramos, solamente, grafos que contengan aristas de peso 1,
entonces, estaremos frente al problema resuelto en la sección 4.2.

En este punto, deberíamos notar que el hecho de que existan aristas de peso
negativo hace que el algoritmo de Dijkstra no funcione correctamente, en
aquellos casos donde existan, en el grafo, ciclos cuyo costo sea negativo.
Esto es simple de ver, ya que, de existir tal ciclo, uno podría dar vueltas
indefinidamente para alcanzar un camino a otro vértice de menor costo. Para
ver esto más claramente, observemos el grafo de la figura 5, donde vemos
que el ciclo V1, V3, V4, V1 tiene un costo negativo de -5. En este caso,

supongamos que deseamos encontrar el camino mínimo que lleva desde V3

hasta V4. El camino directo entre ambos vértices tiene costo 2. Sin embargo,

observamos que existe un camino más corto V3, V4, V1, V3, V4 con costo -3.

Pero, es claro que podemos dar vueltas arbitrariamente el ciclo, es decir,


construir un camino V3, V4 , (V1 , V3 , V4)*, donde (V1, V3, V4)* implica que

se dan varias vueltas al ciclo V1 , V3, V4, V1 y hacer que el costo final del

camino sea menor, por lo que concluimos que el camino más corto, que lleva
del vértice V3 al vértice V4, está indefinido.

Este problema no se restringe a vértices en el ciclo. Por ejemplo, si


deseáramos encontrar el camino más corto que lleva desde el vértice V2
hasta el vértice V5, concluiríamos, de igual manera, que dicho camino es
indefinido, ya que existen maneras de entrar y salir del ciclo. Por ejemplo,
uno podría elegir el siguiente camino V2, V0, (V3, V4, V1)*, V6 , V5, donde (V3

, V4 , V1)* implica que se dan varias vueltas al ciclo V3 , V4, V1, V3.
Figura 5: Problemas con ciclos de costo negativo
Fuente: elaboración propia.

Por otra parte, es importante destacar que las aristas de costo negativo no
representan un problema en aquellos grafos en los que no existen ciclos
negativos. En dichos grafos, el algoritmo de Dijkstra funciona correctamente.

El algoritmo que resuelve el problema general de los caminos mínimos es


una combinación de los algoritmos vistos anteriormente para resolver los
caminos mínimos sin pesos y con pesos positivos. Este algoritmo sufre un
incremento drástico en su tiempo de ejecución, ya que cada vértice v puede
ser procesado varias veces durante la ejecución del algoritmo, haciendo que
su valor de Dv pueda ser modificado varias veces también. En este algoritmo,
utilizamos, nuevamente, una cola, en lugar de una cola de prioridad, pero
insertando Dv+cv,w, como medida de la distancia. Debemos tener en cuenta

que, cuando visitemos un vértice v por i-ésima vez, el valor de Dv es la


longitud del camino más corto formado por, a lo sumo, i aristas.

Lo interesante de este algoritmo es que permite detectar ciclos de costo


negativo (en cuyo caso, como ya dijimos, las distancias a aquellos vértices
que son alcanzables a través de un ciclo negativo estarán indefinidas). En
particular, si no hay ciclos de costo negativo, decimos que un vértice puede
salir de la cola, a lo sumo, |V| veces. Si sale más veces, podemos detener el
algoritmo y anunciar que hemos detectado un ciclo de costo negativo. El
hecho de tener que contemplar que un vértice pueda ser procesado hasta |V|
veces determina que el costo del algoritmo sea O(|E||V|), ya que tendremos
que realizar |V| veces una operación de costo O(|E|) (descubrir los vértices
adyacentes al vértice que está siendo procesado).

Referencias

Matemáticos en México (s.f.). 11 de mayo, natalicio de Edsger Dijkstra.


Recuperado de http://matematicos.matem.unam.mx/otras-efemerides-de-
todo-el-mes-de-mayo/1403-11-de-mayo-natalicio-de-edsger-dijkstra

También podría gustarte