Grafos 1/2

Anuncio
Grafos: algunas definiciones
Un grafo dirigido G es un par (V, E), donde V es un conjunto finito de nodos
(o vértices) y E es una relación binaria sobre V .
Un grafo no dirigido G es un par (V, E), donde V es un conjunto finito de
nodos (o vértices) y E es una conjunto de pares no ordenados de vértices.
Un camino de largo k desde un vértice u a un vértice u0 es una secuencia
de vértices hv0, v1, . . . , vk i tal que u = v0 y u0 = vn, y (vi, vi+1) ∈ E, para
i = 1, 2, . . . , k.
Un nodo u es alcanzable desde un nodo v, si existe un camino entre v y u.
Un ciclo es un camino hv0, . . . , vk i, donde v0 = vk .
Un grafo no dirigido es conexo si cada par de vértices están conectados por un
camino.
Un grafo dirigido es fuertemente conexo si hay un camino entre cada par de
vértices.
Jorge Baier Aranda, PUC
41
A veces se asocian costos a los arcos a través de una función w : E → N.
Jorge Baier Aranda, PUC
42
Representando Grafos
Existen dos maneras estándar de representar grafos; a través de listas de
adyacencia y matrices de adyacencia.
Listas de Adyacencia
Una lista de adyacencia en un grafo G = (V, E) consiste en un arreglo que
contiene una lista para cada elemento de V . En la lista del nodo v (v ∈ V ) se
encuentran todos los nodos u tales que (v, u) ∈ V .
Ventaja: Representación “compacta”.
Desventaja: No es O(1) saber si hay un nodo entre un par de nodos.
Jorge Baier Aranda, PUC
43
Matrices de Adyacencia
En una matriz de adyacencia suponemos que los nodos están numerados 1, . . . , |V |.
El grafo se representa por una matriz A tal que
(
1 si (i, j) ∈ E
A[i, j] =
0 en otro caso
Ventaja: O(1) saber si hay un nodo entre un par de nodos.
Desventaja: Representación puede ser poco “compacta”.
Ambas representaciones pueden ser trivialmente extendidas si se necesita agregar
costos a los arcos.
Jorge Baier Aranda, PUC
44
Búsqueda en grafos
Muchos problemas pueden ser planteados como problemas de búsqueda en grafos.
En un problema de búsqueda interesa encontrar cómo es posible llegar desde un
nodo fuente s, hasta un nodo destino que cumpla con una propiedad P .
Búsqueda en Profundidad (BFS)
La búsqueda es un tipo de búsqueda sistemática que “descubre” primero los
nodos que se encuentran más cercanos a la fuente.
De esta manera, en una primera fase, encuentra todos los nodos que se encuentran a distancia 1 de la fuente, luego los que se encuentran a distancia k, y
ası́ sucesivamente.
Durante la búsqueda, BFS evita “visitar” un nodo por segunda vez. Por esta
razón, en el proceso de búsqueda los nodos pueden tener 3 colores:
Jorge Baier Aranda, PUC
45
• Blanco: El nodo aún no ha sido visitado.
• Gris: El nodo es sucesor de un nodo visitado y pronto será visitado.
• Negro: El nodo ha sido visitado.
La siguiente versión de BFS calcula la distancia desde la fuente (s) hasta todos
los nodos del grafo. Además, calcula el predecesor de cada nodo u, π[u], que
está sobre el camino más corto de s a u.
El conjunto de nodos adyacentes a v se obtiene con la operación Adj[v]. En d[u]
queda almacenada el costo desde s al nodo u.
Jorge Baier Aranda, PUC
46
BFS(G, s)
1 for each u ∈ V [G] − {s}
2 do color[u] ← blanco
3
d[u] ← ∞
4
π[u] ← nil
5 color[s] ← gris
6 d[s] ← 0
7 π[s] ← nil
8 Q ← {s}
9 while Q 6= ∅
10 do u ← head[Q]
11
for each v ∈ Adj[u]
12
do if color[v] = blanco
13
then color[v] ← gris
14
d[v] ← d[u] + 1
15
π[v] ← u
16
Enqueue(Q, v)
17
Dequeue(Q)
18
color[u] ← negro
Jorge Baier Aranda, PUC
47
Búsqueda en Profundidad (DFS)
Como su nombre lo indica, es una búsqueda que avanza en profundidad. El
comportamiento final es parecido a hacer una especie de backtracking.
Igual que en el caso anterior, mientras el algoritmo ejecuta, cambia el color de
los nodos por blanco, gris o negro.
En la siguiente versión de DFS, se calcula un arreglo d, tal que d[u] contiene el
número de nodos que se han visitado antes de visitar al nodo u. Además, en π[u]
queda almacenado el nodo que fue visitado antes que u.
Jorge Baier Aranda, PUC
48
DFS(G)
1 for each u ∈ V [G]
2 do color[u] ← blanco
3
π[u] ← nil
4
tiempo ← 0
5 for each u ∈ V [G]
6 do if color[u] = blanco
7
then DFS-Visit(u)
DFS-Visit(u)
1 color[u] ← gris
2 d[u] ← tiempo ← tiempo + 1
3 for each v ∈ Adj[u]
4 do if color[v] = blanco
5
then π[v] ← u
6
DFS-Visit(v)
7 color[u] ← negro
Jorge Baier Aranda, PUC
49
Camino más corto entre dos nodos
Consiste en encontrar el camino de menor costo entre un nodo fuente s y un
nodo destino e.
Si todos los arcos tienen asociado el mismo costo, entonces este problema se
puede resolver fácilmente haciendo una búsqueda BFS. En otro caso es necesario
un algoritmo levemente distinto.
El algoritmo de Dijkstra resuelve este problema para cuando las aristas tienen
costos no negativos.
Este algoritmo mantiene, en todo momento, un conjunto S de nodos cuyo costo
mı́nimo desde la fuente ya ha sido calculado.
Para todo elemento u ∈ V , se almacena en d[u] el costo entre s y u estimada
hasta el momento.
Observación: Si u ∈ S, entonces d[u] contiene el costo mı́nimo entre s y u.
Jorge Baier Aranda, PUC
50
En todo momento, el algoritmo mantiene una cola de prioridades, en donde
almacena los nodos del grafo que aún no están en S. El ı́ndice de la cola
está dato por el valor de d para estos nodos.
Además, el algoritmo almacena en π[u] al antecesor de u en el camino de menor
costo hasta u desde s.
Jorge Baier Aranda, PUC
51
El siguiente es el algoritmo:
Dijkstra(G, w, s)
1 for each v ∈ V [G]
2 do d[v] ← ∞
3
π[v] ← nil
4 d[s] ← 0
5 S←∅
6 Q ← V [G]
7 while Q 6= ∅
8 do u ← Extract-Min(Q)
9
S ← S ∪ {u}
10
for v ∈ Adj[u]
11
do if d[v] > d[u] + w(u, v)
12
then d[v] ← d[u] + w(u, v)
13
π[v] ← u
Nota 1: La función Extract-Min(Q) extra el nodo de Q que tiene menor
distancia estimada desde s.
Nota 2: Este algoritmo sólo funciona cuando las aristas tienen costos positivos.
En caso de haber costos negativos, es posible usar el algoritmo de Bellman-Ford
(no lo veremos).
Jorge Baier Aranda, PUC
52
Caminos más cortos entre todo par de nodos
Este problema consiste en encontrar el costo del camino más corto entre todo
par de nodos.
El algoritmo de Floyd-Warshall, ataca este problema con la técnica de programación dinámica.
Supongamos que tenemos los nodos de un grafo G = (V, E) numerados entre 1
y |V |.
Sea dkij la distancia mı́nima entre el nodo i y el nodo j, con todos los nodos
intermedios en el conjunto {1, . . . , k}.
Podemos escribir una expresión recursiva para dkij :
(
wij
si k = 0
dkij =
k−1
k−1
mı́n{dk−1
,
d
+
d
ij
ik
kj } en otro caso
Jorge Baier Aranda, PUC
53
|V |
A nosotros nos interesará calcular dij , y es posible hacerlo de manera bottom-up:
Floyd-Warshall(W )
1 n ← rows[W ]
2 for i ← 1 to n
3 do for j ← 1 to n
4
do d0ij = wij
5 for k ← 1 to n
6 do for i ← 1 to n
7
do for j ← 1 to n
k−1
k−1
8
do dkij ← mı́n{dk−1
ij , dik + dkj }
Jorge Baier Aranda, PUC
54
Tareas
Usando matrices de adyacencia (matrices de costo), programar:
1. Algoritmo BFS en C y C++, extendiéndolo para que se detenga cuando ha
encontrado un nodo que cumple alguna propiedad.
2. Algoritmo de Dijkstra en C y C++.
3. Algoritmo de Bellman-Ford, en C y C++.
4. Algoritmo de Floyd-Warshall en C, extendiéndolo para que retorne una matriz de
predecesores.
Todos los algoritmos deben estar precisa y concisamente documentados. Debe
quedar claro, el input, el output (de existir) y qué hace el algoritmo.
Jorge Baier Aranda, PUC
55
Documentos relacionados
Descargar