Está en la página 1de 30

Módulo 2

Lectura 2
Unidad 2: Grafos

Materia: Taller de Algoritmos y estructura de Datos II


Profesor: Maria Paula Abruzzini
Introducción
El objetivo de esta unidad es realizar una introducción a una estructura de
datos muy utilizada como son los grafos. En esta unidad se presentarán los
conceptos esenciales para comprender su funcionamiento, lo cual nos
permitirá realizar nuestra propia implementación en Java. Además,
analizaremos distintos problemas tipos, las distintas representaciones y los
algoritmos para el recorrido y solución de problemas.

Finalmente, realizaremos un breve análisis de la implementación de los


grafos propios de Java.

Conceptos básicos
Un grafo es un conjunto de vértices o nodos unidos por aristas o arcos.
Típicamente, un grafo se representa gráficamente como un conjunto de
puntos (vértices o nodos) unidos por líneas (aristas) como se puede ver en
la Figura 1 y 2.

La definición formal de esta estructura es:

Un grafo es una tupla <V, E>, donde:

V: conjunto de vértices (nodos)

E: conjunto de aristas (arcos)

y cada arista es un par (v,w), donde v,w ε V

Grafos dirigidos y no dirigidos


Existen 2 tipos de grafos, los dirigidos y los no dirigidos. Los grafos de
denominan no dirigidos cuando sus aristas no tienen un sentido dado, es
decir, si existe una arista desde un nodo A a un nodo B, significa que puedo
recorrer el grado desde A  B como así también desde B  A.

La Figura 1 muestra un ejemplo de grafo no dirigido.

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 2


Figura 1: Grafo no dirigido

Los grafos también pueden ser dirigidos (o dígrafos), y en ese caso (v,w) se
trata un par ordenado.

En la figura 2 se muestra un ejemplo de grafo dirigido.

Observe que en este caso es válido ir de 12, pero el camino directo de 21
no existe.

Figura 2: Grafo dirigido

Adyacencia
Decimos que un vértice w es adyacente a un vértice v si existe una arista
(v,w) en E. En el caso de grafos no dirigidos, si w es adyacente a v, entonces
v es adyacente a w. En el caso de dígrafos, esto no se cumple, a no ser que
tanto (v,w) y (w.v) sean aristas del grafo.

La Figura 3 muestra un ejemplo de adyacencia para grafos no dirigidos.

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 3


Figura 3: Adyacencia en grafos no dirigidos

La Figura 4 muestra un ejemplo de adyacencia para grafos dirigidos.

Figura 4: Adyacencia en grafos dirigidos

Algunas veces, las aristas tienen un peso o costo asociado (v,w,p), v,w ε
V, p ε R.

Figura 5: Grafo con pesos

Grafos conexos
Un grafo es conexo si cada par de vértices está conectado por un
camino, es decir, si para cualquier par de vértices (1, 2), existe al menos
un camino posible desde 1 hacia 2.

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 4


En la siguiente figura se muestra un ejemplo de grafos conexos y no
conexos.

Figura 6: Grafo conexo

Caminos
Un camino es una secuencia de vértices w1,..,wN donde (wi,wi+1) ε E,
para 1≤ i < N.

La longitud de un camino (sin pesos) es el número de aristas en el


camino. En el caso de grafos con pesos, la longitud de un camino está
dada por la suma de los pesos.

Por ejemplo, en la figura 5, un posible camino desde el vértice 1 al


vértice 3 es: 123 y su longitud es 2070 (1085 + 985)

Un ciclo es un camino w1,..,wN donde w1 y wN son el mismo vértice, es


decir, es un camino donde el vértice origen y destino coinciden.

Se dice que un ciclo es hamiltoniano cuando tiene que recorrer


todos los vértices exactamente una vez (excepto el vértice del que parte
y al cual llega).

Un grafo acíclico es, como su nombre lo sugiere, simplemente un grafo


dirigido y que no contiene ciclos.

Un orden topológico ordena los vértices de un grafo dirigido, donde si


existe un camino de un vértice v a otro vértice w, entonces w debe aparecer
después de u en la ordenación.

Uno de los problemas más comunes en la Teoría de Grafos es encontrar el


camino mínimo entre 2 vértices dados. Para ello vamos a analizar los
siguientes algoritmos:

• Camino Mínimo sin Pesos con un único origen desde el


vértice de origen a cualquier otro vértice del grafo  BFS (Búsqueda
en anchura)

• Camino Mínimo con Pesos positivos y origen único, desde el


vértice origen al resto de vértices del grafo  Dijkstra

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 5


Figura 7: Ciclos

2.1- Representación de
Grafos
Uno de los primeros problemas a resolver es cómo se representan
internamente los grafos.

Una implementación sencilla de esta estructura de datos es la matriz de


adyacencia. La matriz de adyacencia es la forma más común de
representación y la más directa. Consiste en una tabla de tamaño nxn,
donde n es el número de vértices y para cada arista (v,w), a[v][w]
representa el costo de la arista. Las aristas que no existen pueden
representarse con un valor infinito. Esta representación resulta útil cuando
el grafo es denso.

Si el grafo es no dirigido hay que asegurarse de que se marca tanto la


entrada a[v][w] como la entrada a[w][v], puesto que se puede recorrer en
ambos sentidos.

En la Figura 8 se muestra un grafo de ejemplo y la matriz resultante de su


implementación.

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 6


Figura 8: Representación matricial

Dado que la matriz de adyacencia siempre ocupa un espacio de n*n, es


decir, depende solamente del número de nodos y no del de aristas, esta
representación será útil para representar grafos densos.

Por otro lado, cuando nos encontramos ante un grafo disperso, resulta más
eficiente utilizar una estructura de datos de tipo lista de adyacencia.

Una lista de adyacencia consiste de una lista de los vértices del grafo y para
cada vértice de una lista de sus vértices vecinos.

Lo que se hace es definir una lista enlazada para cada nodo, que contendrá
los nodos a los cuales es posible acceder. Es decir, un vértice i tendrá una
lista enlazada asociada en la que aparecerá un elemento con una referencia al
vértice j si i y j tienen una arista que los une. Obviamente, si el grafo es no
dirigido, en la lista enlazada de j aparecerá la correspondiente referencia al
vértice i. En este caso el espacio ocupado es n*m, muy distinto del necesario
en la matriz de adyacencia, que era de n2. La representación por listas de
adyacencia, por tanto, será más adecuada para grafos dispersos.

Un aspecto importante es que la implementación con listas de adyacencias


determina fuertemente el tratamiento del grafo posterior. Una consecuencia
de esto es que si un problema tiene varias soluciones la primera que se
encuentre dependerá de la entrada dada. Podría presentarse el caso de tener
varias soluciones y tener que mostrarlas siguiendo un determinado orden.
Ante una situación así podría ser conveniente modificar la forma de meter
los nodos en la lista de manera que el algoritmo mismo diera las soluciones
ya ordenadas.1

En la siguiente figurase muestra un ejemplo y su representación con listas


de adyacencia.

1
http://docencia.udea.edu.co/regionalizacion/teoriaderedes/representacionordenadoru1.html

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 7


Figura 9: Representación con listas de adyacencia

Observe que en la lista de


adyacentes se hace En el ejemplo anterior se muestra un grafo con 3 vértices representando 3
referencia a la posición en aeropuertos (Jujuy, Ezeiza, Iguazú). Cada uno de estos vértices es
el vector del nodo representado por una posición en el arreglo. A partir de cada posición del
adyacente en lugar de su arreglo se listan los vértices adyacentes. Por ejemplo, para a[1] que
nombre. Esto nos permite representa Ezeiza, se adjunta una lista de nodos adyacentes en el que se
un acceso directo a los encuentra Iguazú (2) y Jujuy (0).
datos para la ejecución de
algoritmos. A la derecha de
la Figura 9 se puede ver el
diccionario que mapea la
ciudad con la posición del
vector.

2.2- Problemas tipo


Esta estructura de datos es muy utilizada dado que nos sirve para
representar numerosas situaciones y problemas conocidos:

Ciudades y rutas: cada nodo representa una ciudad y cada arista una
ruta que une dichas ciudades. ¿Cuál es el camino más corto? ¿Hay
un camino que partiendo de una ciudad visite todas las ciudades
una sola vez volviendo a la ciudad de partida?

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 8


Figura 10: Ciudades y rutas

Actividades y dependencias: cada nodo representa una actividad.


Las aristas sirven para representar dependencias entre actividades y
de esta forma identificar tareas predecesoras. ¿Cuál es el camino
crítico?

Problema del viajante de comercio, conocido como TSP por sus


siglas en Ingles “Travelling salesman problem”. Dada una lista de las
ciudades y la distancia entre ellas, la tarea consiste en encontrar la
ruta más corta posible que visita cada ciudad exactamente una vez y
regresa a la ciudad de origen.

Países limítrofes: cada vértice representa un país y cada arista


muestra la relación de países limítrofes. ¿Cuántos colores como
mínimo se necesita utilizar para colorear cada país de manera tal
que ningún país limítrofe tenga el mismo color?

El problema de las casas y servicios: Supongamos 3 casas y 3


servicios (Luz, Gas, Teléfono). ¿Es posible brindarle servicio a cada
casa sin que los cables se crucen?

Juego de ajedrez: Cada vértice representa un casillero y cada arista


una posición válida para un caballo en el juego de ajedrez. ¿Es
posible que el caballo parta de un casillero y visite todos los otros 63
casilleros una solo vez volviendo al punto inicial?

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 9


Juego de la casa: ¿Es posible dibujar la casa de la Figura sin levantar
el lápiz y sin trazar 2 veces el mismo segmento?

Ordenamiento topológico: Un orden topológico ordena los vértices


de un grafo dirigido, donde si existe un camino de un vértice v a otro
vértice w, entonces w debe aparecer después de u en la ordenación.

Podemos utilizar este algoritmo para mostrar orden entre


actividades. Supongamos el grafo de la Figura 11, donde se muestra
la relación en el orden de la ropa para poder vestirse correctamente.

Figura 11: Grafo representando orden de vestimenta

El orden topológico nos puede dar distintos resultados dado que no


hay una única forma de ordenar los elementos. En la Figura 10 se
muestras 2 ordenamientos correctos y uno que no lo es.

Figura 11: Resultados de orden de elementos

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 10


2.3- Recorridos de búsqueda
Una de las operaciones más importantes para realizar dentro de las
estructuras de grafos es la búsqueda de un determinado vértice. Por
ejemplo, imagine en un mapa de ciudades y las carreteras que las unen
si queremos identificar qué ciudades pueden alcanzarse a partir de una
ciudad dada. Algunas ciudades podrán ser visitadas mientras que otras
no.

Supongamos que creamos un grafo para modelar la situación anterior.


Ahora necesitamos un algoritmo que pueda comenzar la búsqueda
desde un nodo de origen y recorriendo las aristas moverse a otros
vértices de modo tal que, una vez que finalice el recorrido, se puedan
identificar los nodos que fueron alcanzados desde el origen y aquellos
que no.

Para resolver esta problemática existen 2 soluciones distintas:

Búsqueda en anchura (BFS, Breadth first search)

Búsqueda en profundidad (DFS, Depth first search)

Si bien estos 2 algoritmos van a encontrar todos los vértices que se


encuentran conectados al vértice origen, la forma en que recorren el
grafo para resolverlo es distinta. La búsqueda en anchura recorre el
grafo a lo ancho, es decir, recorre todos los adyacentes al vértice origen
antes de seguir procesando los siguientes adyacentes. Este algoritmo
utiliza una cola como estructura auxiliar para su implementación.

La búsqueda en profundidad recorre en grafo de manera tal que va


entrando en los distintos niveles de profundidad de un grafo antes de
recorrer toda la lista de adyacentes. Este algoritmo utiliza una pila
como estructura auxiliar para su implementación.

En la figura 12 se muestran los pasos de ejecución del algoritmo BFS.


Comenzando con el vértice A como origen se pide la lista de adyacentes
recorriendo cada uno de ellos como se muestra en la figura los pasos
2,3,4, y 5. Una vez que se procesaron todos los vértices adyacentes a A,
comenzamos a procesar la siguiente lista de adyacentes. De esta
manera los próximos vértices a visitar son los adyacentes al vértice B y
esto se describe en el paso 6 del algoritmo. Como el vértice B no tiene
más adyacentes continuo procesando los vértices adyacentes al vértice
C. Dado que el vértice C no posee vértices adyacentes sin visitar
seguimos con los adyacentes al vértice D y de esta forma se visita el
adyacente G, paso 7 del algoritmo.

La Figura 12 muestra la ejecución completa del algoritmo.

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 11


Figura 12 – Ejemplo BFS - Fuente: Libro “Data Structures and Algorithms in Java” – Robert
Lafore, pág. 625.

En el caso de la búsqueda en profundidad, el algoritmo comienza


analizando el vértice de origen A y toma uno de sus adyacentes, en este caso
B como se muestra en el paso 1 y 2 de la Figura 13. Luego procesa el primer
adyacente de B, que en este caso es F como lo muestra el paso 3 de la figura.
Observe que en este paso
este algoritmo procesa el
vértice F y no el vértice C Una vez procesado el vértice F, se prosigue con el adyacente a este vértice y
como hubiese sido en el en consecuencia se procesa el vértice H como 4to paso. Como el vértice H
caso del BFS. no tiene adyacentes vuelve a procesar desde la última lista de adyacentes
que en este caso corresponde al nodo C.

El algoritmo continua su ejecución hasta procesar todos los vértices


alcanzables desde A.

Figura 13 – Ejemplo DFS - Fuente: Libro “Data Structures and Algorithms in Java” – Robert
Lafore, pág. 625.

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 12


2.4 Búsqueda en anchura
(BFS), Búsqueda en
Profundidad (DFS), Floyd,
Dijstra.
En un grafo todos los caminos tienen un peso asociado, el cual está indicado
por las suma de los pesos de las aristas que forman el camino. Existen
numerosas situaciones problemáticas en donde queremos encontrar el
camino que minimice estos valores.

En la resolución de la problemática de caminos mínimos podemos


encontrar tres problemas diferentes:

Camino más corto origen-destino: Dados dos nodos v y w de un


grafo, encontrar el camino más corto que comience en v y culmine
w.

Camino más corto a partir de un origen: Dado un nodo v de un


grafo, encontrar el camino más corto desde v hasta cada uno de los
demás nodos.

Camino más corto entre cada par de nodos.

Podemos observar que el primer problema es un caso particular del


segundo, dado que si resolvemos el camino más corto desde un origen
a todos los demás nodos vamos a resolver el caso desde el origen a un
nodo particular. Para la resolución de este problema vamos a estudiar
el algoritmo de Dijkstra.

En caso de grafos sin pesos, podemos utilizar el algoritmo BFS visto en


el apartado anterior para resolver el problema 2 y en consecuencia el 1.
El algoritmo de BFS encuentra la ruta más corta cuando el peso entre
todos los nodos es 1 por la forma en que este algoritmo recorre el grafo.

Para el tercer caso, si bien podemos ejecutar el algoritmo de Dijkstra N


veces tomando los distintos vértices como origen, el Algoritmo de
Floyd resuelve esta problemática con una complejidad de tiempo
similar.

Algoritmo de Dijkstra

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 13


“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 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”.

Este algoritmo funciona de la siguiente manera:

“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értice
evaluaremos sus adyacentes, 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 lo conoce
como relajación.”

Figura 14. Fuente: http://jariasf.wordpress.com/2012/03/19/camino-mas-corto-


algoritmo-de-dijkstra/

“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.”
(Jhosimar George Arias Figueroa, 2012,
http://jariasf.wordpress.com/2012/03/19/camino-mas-corto-algoritmo-
de-dijkstra/)

En el Anexo 1 se muestra paso a paso la ejecución de este algoritmo.

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 14


Algoritmo de Floyd

Como se presenta en el libro de Robert Lafore, “Data Structures and


Algorithms in Java”, estudiaremos el algoritmo de Floyd a partir de un
ejemplo concreto. En la Figura 15 se muestra un grafo dirigido junto a su
representación por medio de una matriz de adyacencia.

Figura 15: Grafo dirigido con pesos. Fuente: Libro “Data Structures and Algorithms in
Java” – SAMS, pág. 708.

La matriz de adyacencia muestra el costo de las aristas del grafo. Vamos a


extender esta matriz para mostrar el camino más corto para cualquier par
de vértices.
En el grafo de la figura podemos ver que se puede ir de BC con un costo
de 30 (BD con costo 10 + 20 para ir de D C)

Este algoritmo compara todos los posibles caminos entre cada par de
vértices. Se va a utilizar una matriz Ak[i][j], que contiene el camino más
corto que pasa por los primeros k primeros vértices.
Inicialmente Ak[i][j] = matriz de adyacencia i j. Si no hay arista de i a j
el costo será (infinito) y los elementos diagonales se ponen a 0.
En la iteración k (nodo k como pivote) se calcula, para cada camino de v a
Ak[i][j] = min (Ak-1[i][j] , Ak-1[i][k] + Ak-1[k][j] ), i j.

w, si es más corto pasando por k aplicando:

Básicamente la expresión anterior nos dice: “si para ir desde el vértice “i” al
“j” mejoramos pasando por el vértice “k”, éste se añade al camino. Además,
para reconstruir el camino se hace uso de una matriz de trayectorias, donde
en cada iteración, si se mejora el camino desde el vértice “i” al vértice “j”
pasado por “k”, éste se anota en la matriz de trayectorias.

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 15


Figura 16: Ejemplos Floyd. Fuente: Libro “Data Structures and Algorithms in Java” –
SAMS, pág. 708.

En el primer caso Figura 16.a, se muestra que el costo de DA es


originalmente . Si calculamos:

1. Ak[i][j] = min (Ak-1[i][j] , Ak-1[i][k] + Ak-1[k][j] ), i j.

2. Ak[3][0] = min (Ak-1[3][0], Ak-1[3][2] + Ak-1[2][0] ), i j. k=2

3. Ak[3][0] = min ( , 20+ 30 ), i j.

4. Ak[3][0] = min ( , 50 ), i j.

5. Ak[3][0] = 50

Luego, en la figura 16.b, se presenta una situación muy interesante, dado


que reduciremos el costo de un camino.

1. Ak[i][j] = min (Ak-1[i][j] , Ak-1[i][k] + Ak-1[k][j] ), i j.

2. Ak[1][0] = min (Ak-1[1][0], Ak-1[1][3] + Ak-1[3][0] ), i j. k=3

3. Ak[1][0] = min (70, 10+50 ), i j.

4. Ak[1][0] = min (70, 60 ), i j.

5. Ak[1][0] = 60

Finalmente, en la Figura 16.c, tenemos un caso similar al primero:

1. Ak[i][j] = min (Ak-1[i][j] , Ak-1[i][k] + Ak-1[k][j] ), i j.

2. Ak[1][2] = min (Ak-1[1][2], Ak-1[1][3] + Ak-1[3][2] ), i j. k=3

3. Ak[1][2] = min ( , 10+20 ), i j.

4. Ak[1][2] = min ( , 30), i j.

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 16


5. Ak[1][2] = 30

Búsqueda en anchura (BFS) como


camino mínimo
En la siguiente figura se puede observar uno de los pasos de ejecución
del algoritmo BFS. Observe que los vértices marcados en verde son los
que ya fueron visitados.

En este momento el algoritmo se encuentra procesando el nodo V7,


analizando sus vértices adyacentes. Si tomamos el caso particular del
adyacente V5, como ya está visitado no se vuelve a procesar. Si no
tuviésemos la condición de no volver a procesar un nodo ya visitado, se
sobrescribiría el costo 1 que obtuvimos de llegar al vértice V5 directo
desde el origen, por el costo 4 obtenido desde el vértice 7.

Este ejemplo nos muestra que por la forma de recorrer el grafo, este
algoritmo nos resuelve la problemática caminos mínimos en grafos sin
peso.

Figura 17: Paso de ejecución BFS. Fuente: http://jariasf.wordpress.com

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 17


2.5 Implementación de
Recorridos: DFS, BFS,
Dijkstra
En este punto, realizaremos la implementación de los algoritmos de
búsqueda en profundidad (DFS), búsqueda en anchura (BFS) y Dijkstra, los
cuales se describieron anteriormente.

Antes de comenzar con la implementación de los algoritmos comenzaron


con la implementación de las clases necesarias para la representación de la
estructura de grafos.

Implementación de la estructura de
Grafos

Utilizando todos los conceptos analizados en las secciones anteriores


realizaremos nuestra implementación en Java.

Para la implementación de la estructura de Grafos definiremos 3 clases.

Clases
Para la implementación de esta estructura de datos crearemos:

Clase Vértice: Esta clase representa los vértices o nodos del grafo.

Clase Arista: Esta clase representa las aristas o arcos del grafo.

Clase Grafo: Esta clase representa el grafo. En ella se guardara la lista de


vértices como así también se definirán las operaciones que nos permitirán
agregar los vértices y las aristas.

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 18


Figura 18: Diagrama de Clases para la implementación de Grafos

Clase Vértice y Arista


Los grafos están compuestos por Vértices y aristas. Si utilizamos la
estructura de datos de Lista de adyacencias, en la clase vértice vamos a
declarar algunas variables propias de esta clase. El primer atributo a
guardar es un String para el nombre del vértice, luego guardamos la lista de
aristas que, como se verá en breve, está compuesta por un costo y un
destino. Finalmente, vamos a guardar un atributo de tipo doublé que va a
marcar un costo y nos va a ser útil para la ejecución de algunos algoritmos
que analizaremos más adelante.

En la figura 19 vemos el código de la clase Vértice.

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 19


Figura 19: Clase Vértice

Para el caso de las aristas, creamos una clase llamada Arista en la que
guardamos el Vértice destino a donde esta arista se dirige y el costo que
insume ir desde el origen al destino. En el caso de grafos sin peso este valor
se va a establecer en 1.

En la figura 20 vemos el código de la clase Arista.

Figura 20: Clase Arista

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 20


Clase Grafo
La clase grafo es la contenedora de los vértices y aristas que representan el
grafo. Además, se implementaran en ella las operaciones para la
construcción del grafo con la implementación de agregado de vértices y
aristas.

En esta clase, vamos a utilizar una estructura auxiliar donde guardaremos


los vértices del grafo. Esta estructura es una tabla de hash y su declaración
se muestra en la línea 9 de la Figura 21.

El método encargado de insertar los vértices, valida en la tabla de hashing si


el vértice existe y lo inserta en caso contrario. Observe que se utiliza el
nombre del vértice como clave de la tabla de hash.

Figura 21: Clase Grafo

En los puntos siguientes estudiaremos distintos algoritmos y


realizaremos las modificaciones necesarias para agregar los métodos
necesarios para su implementación sobre estas clases.

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 21


Implementación BFS
Veamos una posible implementación del algoritmo BFS. Este algoritmo
necesita de una estructura auxiliar, que por lo general es una cola, para
el almacenamiento de las aristas que se van a visitar durante el
recorrido.

Pasos de ejecución:

1) El primer paso del algoritmo consiste en inicializar todos los vértices del
grafo. Este paso consiste en inicializar la variable distancia de cada uno
de los vértices en Infinito. (Ver llamada al método clearAll() de la Figura
22).

2) Luego, buscamos el vértice origen a partir del cual se comenzará a


ejecutar el algoritmo.

3) Se crea la estructura auxiliar, en este caso una cola q para el recorrido


de los vértices.

4) Se agrega el vértice inicial “origen” y se le asigna como valor de


distancia al vértice origen el costo 0 (cero).

5) Mientras que la cola q no esté vacía, repetir el paso 6.

6) Se toma el primer elemento de q como vértice activo v.

a) Por cada vértice w adyacente a v.

i) Si el vértice adyacente w, no fue visitado, es decir, la distancia


del vértice w es infinito.

7) Asignar al vértice w la distancia del nodo anterior + 1 (w.dist = v.dist +1)

8) Asignar al vértice w el vértice anterior por el cual fue visitado, es decir v.

9) Agregar w a la cola q para su futuro procesamiento.

En la figura 22 podemos ver la codificación de los pasos descriptos


anteriormente:

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 22


Figura 22 – Código Java para la Búsqueda en anchura. Fuente: Libro “Estructura de
Datos en Java” - Mark Weiss, Addison Wesley

El siguiente ejemplo, descripto por Ernesto Coto en Algoritmos Básicos de


Grafos, muestra el funcionamiento del algoritmo de BFS. La secuencia de
ilustraciones va de izquierda a derecha y de arriba hacia abajo.

Comenzamos introduciendo a la cola C todos las aristas adyacentes al nodo


inicial (el nodo 0). Luego, extraemos la arista 0-2 de la cola y procesamos
las aristas adyacentes a 2, la 0-2 y la 2-6. No colocamos la arista 0-2 en la
cola porque el vértice 0 ya fue visitado. Luego, extraemos la arista 0-5 de la
cola y procesamos las aristas adyacentes a 5.

Figura 23 – Ejemplo Búsqueda en anchura. Fuente: Algoritmos Básicos de Grafos

De manera similar a la anterior, no se toma en cuenta la arista 0-5, pero sí


se encolan las aristas 5-3 y 5-4. Seguidamente, extraemos la arista 0-7 y
encolamos la arista 7-1. La arista 7-4 esta impresa en color gris porque si

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 23


bien es un enlace adyacente a 7, podríamos evitar encolarla debido a que ya
existe una arista en la cola que nos lleva hasta el nodo 4.

Para completar el recorrido, tomamos las aristas que quedan en la cola,


ignorando aquellas que están impresas en color gris cuando queden de
primeras en la cola. Las aristas entran y salen de la cola en el orden de su
distancia del vértice 0.

Figura 24 – Ejemplo Búsqueda en anchura (continuación). Fuente: Algoritmos Básicos


de Grafos

Implementación DFS
Para efectuar un recorrido en profundidad de un grafo, se selecciona
cualquier nodo como punto de partida (por lo general el primer nodo del
grafo) y se marcan todos los nodos del grafo como “no visitados”. El nodo
inicial se marca como “visitado” y si hay un nodo adyacente a este que no
haya sido “visitado”, se toma este nodo como nuevo punto de partida del
recorrido. El recorrido culmina cuando todos los nodos hayan sido visitados.

Se dice que el recorrido es en profundidad, porque para visitar otro nodo


adyacente del nodo inicial, primero se deben visitar TODOS los nodos
adyacentes al que se eligió antes. Es así, como el número de ambientes
recursivos varía dependiendo de la profundidad que alcance el algoritmo.2

2 Algoritmos Básicos de Grafos, Ernesto Coto. Universidad Central de Venezuela, Facultad de


Ciencias Escuela de Computación (ISSN 1316-6239). Febrero, 2003
http://ccg.ciens.ucv.ve/~ernesto/nds/CotoND200302.pdf

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 24


Para poder programar dicho comportamiento vamos a apoyarnos en una
estructura de datos llamada pila. Igual que con las colas, Java nos brinda la
clase Stack<T> que nos va a servir el comportamiento deseado.

Pasos de ejecución:

1) Insertamos la raíz en la pila, para preparación.

2) Marcamos origen como visitado.

3) Mientras la pila no esté vacía:

i) Desapilamos un elemento de la pila llamado v

ii) Para cada vértice w adyacente a v

iii) Si w no ha sido visitado:

(a) marcamos como visitado w

(b) insertamos w dentro de la pila S

4) Repetimos hasta que la pila esté vacía.

En la figura 25 podemos ver la codificación en Java del algoritmo de


búsqueda en profundidad. En esta implementación, se utilizó recursión
para ir entrando en profundidad en la estructura de datos. De esta forma no
necesitamos utilizar una pila auxiliar para almacenar los vértices.

Figura 25 – Implementación Java Búsqueda en profundidad

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 25


Suponga el grafo de la Figura 26

Figura 26 – Ejemplo Búsqueda en profundidad

Cada uno de los vértices tiene los siguientes vértices adyacentes:

1 2 3 4 5 6 7 8 9 10 11 12

7 8 2 6 2 3 8 9 5 1 4 8

10 4 11 10 12 12 5 12

11

La traza de ejecución para el algoritmo se muestra en la siguiente tabla:

Cola Vértice Arista Visitados

V1 V7 V1-v7 V1,v7

V1,v7 V8 V7-v8 V1,v7,v8

V1,v7,v8 V9 V8-v9 V1,v7,v8,v9

V1,v7,v8,v9 V5 V9-v5 V1,v7,v8,v9,v5

V1,v7,v8,v9,v5 V2 V5-v2 V1,v7,v8,v9,v5,v2

V1,v7,v8,v9,v5,v2 * V1,v7,v8,v9,v5,v2

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 26


V1,v7,v8,v9,v5 * V1,v7,v8,v9,v5,v2

V1,v7,v8,v9, V12 V9-v12 V1,v7,v8,v9,v5,v2,v12

V1,v7,v8,v9,v12 * V1,v7,v8,v9,v5,v2,v12

V1,v7,v8,v9, * V1,v7,v8,v9,v5,v2,v12

V1,v7,v8, * V1,v7,v8,v9,v5,v2,v12

V1,v7 V10 V7-v10 V1,v7,v8,v9,v5,v2,v12,v10

V1,v7,v10 * V1,v7,v8,v9,v5,v2,v12,v10

V1,v7,v10 * V1,v7,v8,v9,v5,v2,v12,v10

V1,v7,v10 V11 V1,v7,v8,v9,v5,v2,v12,v10,v11

V1,v7,v10,v11 V4 V11-v4 V1,v7,v8,v9,v5,v2,v12,v10,v11,v4

V1,v7,v10,v11,v4 V6 V4-v6 V1,v7,v8,v9,v5,v2,v12,v10,v11,v4,v6

V1,v7,v10,v11,v4,v6 V3 V6-v3 V1,v7,v8,v9,v5,v2,v12,v10,v11,v


4,v6,v3

V1,v7,v10,v11,v4,v6,v3 *

V1,v7,v10,v11,v4,v6,v3 *

V1,v7,v10,v11,v4,v6 *

V1,v7,v10,v11,v4 *

V1,v7,v10,v11 *

V1,v7,v10 *

V1,v7 *

V1 *

Vacía

Orden en el que se visitan los vértices: 1, 7, 8, 9, 5, 2, 12, 10, 11, 4, 6, 3

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 27


Implementación Dijkstra
El algoritmo de dijkstra determina la ruta más corta desde un nodo origen
hacia los demás nodos.

Pasos de ejecución:

Método Dijkstra (Grafo,origen):

1) Creamos una cola de prioridad Q

2) Agregamos origen a la cola de prioridad Q

3) Mientras Q no esté vacío:

a) Sacamos un elemento de la cola Q llamado u

b) Si u ya fue visitado continuo sacando elementos de Q

c) marcamos como visitado u

d) para cada vértice v adyacente a u en el Grafo:

i) sea w el peso entre vértices ( u , v )

ii) si v no ha sido visitado  Relajacion( u , v , w )

Método Relajación (actual , adyacente , peso):

1) Si distancia[ actual ] + peso < distancia[ adyacente ]

a) distancia[ adyacente ] = distancia[ actual ] + peso

b) agregamos adyacente a la cola de prioridad Q (ordenado por costo)

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 28


En la figura 27 podemos ver la codificación en Java de los pasos descriptos
anteriormente:

Figura 27 – Implementación Java Algoritmo Dijkstra

Para que la cola de prioridad ordene los vértices por distancia, podemos
hacer que la clase Vértice implemente Comparable y el método compareTo
se va a definir de acuerdo al atributo distancia de cada uno de los vértices.

En el Anexo 1, se encuentra un ejemplo de la ejecución paso a paso del


algoritmo.

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 29


Bibliografía Lectura 2
Capítulo: 14

Mark Weiss (2000), “Estructura de Datos en Java”, Editorial Addison Wesley


Iberoamericana.

Capítulo: 13 y 14

Robert Lafore (2002), “Data Structures & Algorithms in Java (2nd Edition)”,
SAMS.

Código Fuente

Mark Weiss Home Page: http://users.cis.fiu.edu/~weiss/

Algoritmos Básicos de Grafos, Ernesto Coto. Universidad Central de


Venezuela, Facultad de Ciencias Escuela de Computación (ISSN 1316-6239).
Febrero, 2003 http://ccg.ciens.ucv.ve/~ernesto/nds/CotoND200302.pdf

Algorithms and More, Jhosimar George Arias Figueroa, Universidad


Nacional de San Agustin (UNSA, Arequipa – Perú).

http://jariasf.wordpress.com/2012/03/19/camino-mas-corto-algoritmo-de-
dijkstra/

Teoría de Redes

http://docencia.udea.edu.co/regionalizacion/teoriaderedes/representacionordena
doru1.html

www.uesiglo21.edu.ar

Taller de Algoritmos y Estructura de Datos II – Maria Paula Abruzzini | 30

También podría gustarte