Grafos dirigidos

Anuncio
Grafos dirigidos
Los grafos dirigidos son grafos en los que las aristas tienen una dirección definida;
por ejemplo, se puede dar el caso de poder ir del nodo A al nodo B, pero no al revés.
En la mayoría de los casos la dirección de las aristas indica algún tipo de relación de
precedencia entre los nodos. Los grafos dirigidos pueden ser usados para:

Modelar líneas de fabricación, en las que diferentes procesos dependen de
otros

Manejar dependencias en la compilación de archivos, como hace el make
Un ejemplo de grafo dirigido podría ser:
A
B
C
D
E
H
I
J
K
L
M
G
F
Podemos ver claramente que por ejemplo, existe una arista de A a G, pero no de G a
A. Sin embargo, está permitido que halla dos aristas de direcciones distintas entre los
mismos nodos, como por ejemplo entre H e I, y entre L y M.
Búsqueda primero en profundidad
En el caso de grafos dirigidos, la búsqueda primero en profundidad es casi igual a
la similar en el caso de grafos no dirigidos.
En ejemplo en pseudo-código sería:
Recorrer (grafo G) {
marcar_visitado(todos F);
for (todos vértices V de G)
DFS (G  V);
}
DFS (vértice V) {
visitar(V);
marcar_visitado(V);
for ( ; v ; v = SgteAdy)
if (!visitado(V))
DFS(V);
}
Aplicando este algoritmo al grafo anterior podemos visitar los nodos en el orden:
AFEDBGJKLMCHI
Este orden no es el único posible, podría haber empezado por otro nodo, o haber
seguido otro orden entre los adyacentes.
Notamos también que pese a que el H e I están conectados al gráfico, no pueden ser
accedidos desde A; es necesario cambiar de nodo, y empezar a recorrerlo de nuevo.
Es decir, en vez de representarse el recorrido como un árbol, se representa como un
bosque. Si se hubiera empezado a recorrer el grafo por H, podría haber recorrido
todos los nodos dentro del mismo árbol de recorrido, puesto que desde H puedo
acceder a cualquier nodo.
Cierre transitivo
El cálculo de cierre transitivo permite saber si existe un camino de longitud mayor o
igual a 1 entre dos nodos dados. Es decir, indica que nodos están de alguna forma
conectados.
Para esto es útil trabajar con la matriz de adyacencias, colocando un 1 en los nodos
que están conectados, y un 0 en los que no lo están. De esta forma, para este grafo la
matriz quedaría:
A
B
C
D
E
F
G
H
I
J
K
L
M
A
1
0
1
0
0
0
0
0
0
0
0
0
0
B
1
1
0
0
0
0
0
0
0
0
0
0
0
C
0
0
1
0
0
0
1
0
0
0
0
0
0
D
0
0
0
1
1
0
0
0
0
0
0
0
0
E
0
0
0
0
1
1
1
0
0
0
0
0
0
F
1
0
0
1
0
1
0
0
0
0
0
0
0
G
1
0
0
0
0
0
1
1
0
0
0
1
0
H
0
0
0
0
0
0
0
1
1
0
0
0
0
I
0
0
0
0
0
0
0
1
1
0
0
0
0
J
0
0
0
0
0
0
1
0
0
1
0
0
0
K
0
0
0
0
0
0
0
0
0
1
1
0
0
L
0
0
0
0
0
0
0
0
0
1
0
1
1
M
0
0
0
0
0
0
0
0
0
1
0
1
1
Si sobre esta matriz se ejecuta el siguiente algoritmo (inventado por S. Warshall en
1962), se obtiene la matriz de adyacencias que corresponde al cálculo del cierre
transitivo para todo par de nodos X e Y:
for (y = 1; y <= V; y++)
for (x = 1; x <= V; x++)
if (a[x][y])
for (j = 1; j <= V; j++)
if (a[y][j]) a[x][j] = 1;
El algoritmo de Warshall se basa en la afirmación de que si es posible llegar del nodo
X al nodo Y, y del nodo Y al nodo J, entonces existe un camino del nodo X al nodo J.
De hecho, para poder completar el cierre transitivo del grafo en una sola pasada usa la
afirmación de que “si existe un camino de X a Y usando nodos con índices menores a
Y, y existe una forma de llegar de Y a J, entonces hay un camino de X a J usando
nodos con índices menores a Y+1”.
Luego de ejecutar este algoritmo sobre nuestro grafo, obtenemos:
A
B
C
D
E
F
G
H
I
J
K
L
M
A
1
0
1
0
0
0
1
1
1
1
0
1
1
B
1
1
1
0
0
0
1
1
1
1
0
1
1
C
1
0
1
0
0
0
1
1
1
1
0
1
1
D
1
0
1
1
1
1
1
1
1
1
0
1
1
E
1
0
1
1
1
1
1
1
1
1
0
1
1
F
1
0
1
1
1
1
1
1
1
1
0
1
1
G
1
0
1
0
0
0
1
1
1
1
0
1
1
H
0
0
0
0
0
0
0
1
1
0
0
0
0
I
0
0
0
0
0
0
0
1
1
0
0
0
0
J
1
0
1
0
0
0
1
1
1
1
0
1
1
K
1
0
1
0
0
0
1
1
1
1
1
1
1
L
1
0
1
0
0
0
1
1
1
1
0
1
1
M
1
0
1
0
0
0
1
1
1
1
0
1
1
Fijándonos en el grafo, vemos que para cada fila aparece todos los nodos a los que
puedo llegar desde la misma. Como vimos en la búsqueda primero en profundidad, los
nodos H e I no pueden ser accedidos desde el resto del grafo, lo que se puede ver en
la matriz de cierre transitivo. A su vez, desde H o I puedo acceder al resto del grafo, lo
que también pude observarse en la matriz.
Vemos que el algoritmo contiene 3 bucles anidados, es claramente de tipo O(n3),
siendo n = V, la cantidad de nodos del grafo.
También existe, como puede suponerse, una versión recursiva de este algoritmo, que
es de tipo O(n2), pero es de implementación más complicada (aparte de por ser
recursiva, consumir más memoria).
Camino más corto
En algunos casos puede ser necesario no solo ver si existe un camino entre dos
nodos, sino calcular el más corto entre ellos. Para lograr esto puede usarse el
siguiente algoritmo:
for (y = 1; y <= V; y++)
for (x = 1; x <= V; x++)
if (a[x][y])
for (j = 1; j <= V; j++)
if (a[x][j] > 0)
if(!a[x][j] || (a[x][y] + a[y][j] < a[x][j]))
a[x][j] = a[x][y] + a[y][j];
Como se puede ver este algoritmo (inventado por R. W. Floyd) es muy similar al de
Warshall. La lógica que sigue es esencialmente la misma, estando la diferencia en la
6ª línea. En ese if, si no existe una arista entre X y J o si la arista que existe es mayor
a la suma de XY más YJ, le asigna la que corresponde a XY más YJ. Es decir,
siempre se queda con la menor posible.
De forma similar al algoritmo de Warshall, este es de tipo O(n3).
Orden Topológico
El orden topológico es el mismo que fue definido en clase. Como puede suponerse,
el orden puede solo ser determinado en un grafo dirigido acíclico, o DAG (Directed
Acyclic Graph). De esta forma, se puede observar un orden parcial entre nodos,
ordenándolos de forma tal que ningún nodo es visitado antes que los nodos que
apuntan a el. Si por ejemplo, el grafo representa los diferentes pasos en un proceso,
ningún paso es ejecutado antes de los que le preceden.
En algunas aplicaciones puede ser necesario usar un orden topológico inverso. En
este caso se tienen dos posibilidades:

Recorrer el grafo en orden y almacenar los nodos en una pila, para luego
sacarlos en el orden topológico inverso.

Recorrer el grafo primero en profundidad, pero marcando los nodos como
visitados al final del algoritmo, y no al principio.
Componentes fuertemente conectados
Como vimos en el recorrido primero en profundidad, puede ser que desde un nodo
dado no pueda acceder a todo el resto del grafo. Esto (más otras condiciones)
ocasiona que los nodos puedan ser divididos en componentes fuertemente
conectados. Todos los nodos de un mismo componente fuertemente conectado son
accesibles entre si, pero no es posible salir del componente y volver a entrar. En el
caso del grafo de ejemplo, los componentes fuertemente conectados serían (pueden
comprobarlo ustedes):

B

K

HI

DEF

ACGJLM
Existe un algoritmo para encontrar componentes fuertemente conectados, desarrollado
por R. E. Tarjan, en 1972. Es similar al usado para buscar componentes biconexos.
Descargar