Cap´ıtulo 7: Algoritmos en grafos
Del libro: Dise˜
no de Algoritmos - Nivio Ziviani (UFMG)
Noviembre - 2011
Ingenier´ıa de Software
´Indice
1
TAD - Grafos
Implementaci´
on mediante matrices de adyacencia
Listas de adyacencia - usando punteros
2
Arbol de recubrimiento m´ınimo
´
Algoritmo de Prim
Algoritmo de Kruskal
TAD - Grafos
TAD de los grafos
Dado un grafo G = (V, A), donde V son los v´
ertices y A las aristas:
1
Crear un grafo vac´ıo.
2
Insertar una arista al grafo.
3
Verificar si existe una determinada arista en el grafo.
4
Obtener una lista de vertices adyacentes a un determinado vertice.
5
Eliminar una arista de un grafo.
6
Imprimir un grafo.
7
Obtener el n´
umero de v´
erctices del grafo.
8
Obtener la transpuesta de un grafo direccionado.
9
Obtener la arista de menor peso en el grafo.
TAD - Grafos Implementaci´on mediante matrices de adyacencia
Operaci´
on obtener lista de adyacentes
1
Verificar que lista de adyacentes de un v´
ertice se encuentra vacia.
Retorna true si la lista de adyacentes se encuentra vac´ıa.
2
Obtener el primer v´
ertice adyacente a un v´
ertice v, en caso exista
alguno. Retorna la direcci´
on del primer v´
ertice de la lista de
adyacentes de v.
3
Obtener el pr´
oximo v´
ertice adyacente a un v´
ertice v, en caso exista.
Retorna la pr´
oxima arista en que el v´
ertice v participa.
TAD - Grafos Implementaci´on mediante matrices de adyacencia
Operaci´
on obtener lista de adyacentes
Lectura de los adyacentes de una grafo:
if ( !grafo. listaAdjVacia (v ) ) {
Arista aux = grafo.primeroListaAdj (v) ;
while (aux != null ) {
int u = aux.vertice2( );
int peso = aux.peso ( ) ;
aux = grafo.proxAdj (v) ;
}
TAD - Grafos Implementaci´on mediante matrices de adyacencia
Matriz de adyacencia
TAD - Grafos Implementaci´on mediante matrices de adyacencia
Matriz de adyacencia - An´
alisis
•
Debe ser implementada para grafos densos, donde |A| es pr´
oximo |V |
2
•
Es muy util para algoritmos en que necesitamos saber con rapidez si
existe una arista conectada a dos vertices.
TAD - Grafos Implementaci´on mediante matrices de adyacencia
Matriz de adyacencia - Implementaci´
on
La inserci´on de un nuevo v´ertice se puede realizar en un tiempo constante.
package Cap7.matrizadj; public class Grafo {
public static class Arista { private int v1, v2, peso;
public Arista (int v1, int v2, int peso) { this.v1 = v1; this.v2 = v2; this.peso = peso; }
public int peso () { return this.peso; } public int v1 () { return this.v1; } public int v2 () { return this.v2; } }
private int mat[][]; private int numVertices; private int pos[];
public Grafo (int numVertices) {
this.mat = new int[numVertices][numVertices]; this.pos = new int[numVertices];
this.numVertices = numVertices;
for (int i = 0; i < this.numVertices; i++) {
for (int j = 0; j < this.numVertices; j++) this.mat[i][j] = 0; this.pos[i] = -1;
} }
public Grafo (int numVertices, int numAristas) { this.mat = new int[numVertices][numVertices]; this.pos = new int[numVertices];
this.numVertices = numVertices;
for (int i = 0; i < this.numVertices; i++) {
for (int j = 0; j < this.numVertices; j++) this.mat[i][j] = 0; this.pos[i] = -1;
} }
TAD - Grafos Implementaci´on mediante matrices de adyacencia
Matriz de adyacencia - Implementaci´
on
public void insertaArista (int v1, int v2, int peso) { this.mat[v1][v2] = peso;
}
public boolean existeArista (int v1, int v2) { return (this.mat[v1][v2] > 0);
}
public boolean listaAdjVacia (int v) { for (int i =0; i < this.numVertices; i++)
if (this.mat[v][i] > 0) return false; return true;
}
public Arista primeiroListaAdj (int v) { this.pos[v] = -1; return this.proxAdj (v); }
public Arista proxAdj (int v) { this.pos[v] ++;
while ((this.pos[v] < this.numVertices) &&
(this.mat[v][this.pos[v]] == 0)) this.pos[v]++; if (this.pos[v] == this.numVertices) return null;
else return new Arista (v, this.pos[v], this.mat[v][this.pos[v]]); }
public Arista eliminaArista (int v1, int v2) {
if (this.mat[v1][v2] == 0) return null; // @{\it Arista n\~ao existe}@ else {
Arista arista = new Arista (v1, v2, this.mat[v1][v2]); this.mat[v1][v2] = 0; return arista;
} }
TAD - Grafos Implementaci´on mediante matrices de adyacencia
Matriz de adyacencia - Implementaci´
on
public void imprimir () { System.out.print (" ");
for (int i = 0; i < this.numVertices; i++) System.out.print (i + " ");
System.out.println ();
for (int i = 0; i < this.numVertices; i++) { System.out.print (i + " ");
for (int j = 0; j < this.numVertices; j++) System.out.print (this.mat[i][j] + " "); System.out.println ();
} }
public int numVertices () { return this.numVertices; } public Grafo grafoTranspuesto () {
Grafo grafoT = new Grafo (this.numVertices); for (int v = 0; v < this.numVertices; v++)
if (!this.listaAdjVacia (v)) {
Arista adj = this.primeiroListaAdj (v); while (adj != null) {
grafoT.insertaArista (adj.v2 (), adj.v1 (), adj.peso ()); adj = this.proxAdj (v); } } return grafoT; } }
TAD - Grafos Implementaci´on mediante matrices de adyacencia
Matriz de adyacencia - Implementaci´
on
public void imprimir () { System.out.print (" ");
for (int i = 0; i < this.numVertices; i++) System.out.print (i + " ");
System.out.println ();
for (int i = 0; i < this.numVertices; i++) { System.out.print (i + " ");
for (int j = 0; j < this.numVertices; j++) System.out.print (this.mat[i][j] + " "); System.out.println ();
} }
public int numVertices () { return this.numVertices; } public Grafo grafoTranspuesto () {
Grafo grafoT = new Grafo (this.numVertices); for (int v = 0; v < this.numVertices; v++)
if (!this.listaAdjVacia (v)) {
Arista adj = this.primeiroListaAdj (v); while (adj != null) {
grafoT.insertaArista (adj.v2 (), adj.v1 (), adj.peso ()); adj = this.proxAdj (v); } } return grafoT; } }
TAD - Grafos Listas de adyacencia - usando punteros
Listas de adyacencia - usando punteros
TAD - Grafos Listas de adyacencia - usando punteros
Listas de adyacencia - usando punteros
•
Se utiliza un arreglo de |V | listas, una para cada v´
ertice de V .
•
Para cada u ∈ V , adj[u] contiene todos los v´
ertices adyacentes a u en
G.
•
Posee una complejidad de espacio de O(|V | + |A|).
•
Indicada para grafos dispersos, donde |A| es mucho menor que |V |
2
•
En el peor caso puede tener un tiempo de O(|V |), es el n´
umero de
v´
ertices adyacentes m´
aximo.
TAD - Grafos Listas de adyacencia - usando punteros
Listas de adyacencia - usando punteros - implementacion
package Cap7.listaadj.autoreferencia; import Cap3.apuntadores.Lista; public class Grafo {
public static class Arista { private int v1, v2, peso;
public Arista (int v1, int v2, int peso) { this.v1 = v1; this.v2 = v2; this.peso = peso; }
public int peso () { return this.peso; } public int v1 () { return this.v1; } public int v2 () { return this.v2; } }
private static class Celda { int vertice, peso;
public Celda (int v, int p) {this.vertice = v; this.peso = p;} public boolean equals (Object obj) {
Celda item = (Celda) obj;
return (this.vertice == item.vertice); }
}
private Lista adj[]; private int numVertices;
TAD - Grafos Listas de adyacencia - usando punteros
Listas de adyacencia - usando punteros - implementacion
public Grafo (int numVertices) {
this.adj = new Lista[numVertices]; this.numVertices = numVertices; for (int i = 0; i < this.numVertices; i++) this.adj[i] = new Lista(); }
public Grafo (int numVertices, int numAristas) {
this.adj = new Lista[numVertices]; this.numVertices = numVertices; for (int i = 0; i < this.numVertices; i++) this.adj[i] = new Lista(); }
public void insertaArista (int v1, int v2, int peso) { Celda item = new Celda (v2, peso);
this.adj[v1].insertar (item); }
TAD - Grafos Listas de adyacencia - usando punteros
Listas de adyacencia - usando punteros - implementacion
public boolean existeArista (int v1, int v2) { Celda item = new Celda (v2, 0);
return (this.adj[v1].busqueda(item) != null); }
public boolean listaAdjVacia (int v) { return this.adj[v].vacia (); }
public Arista primeroListaAdj (int v) { Celda item = (Celda) this.adj[v].primero ();
return item != null ? new Arista (v, item.vertice, item.peso) : null; }
public Arista proxAdj (int v) {
Celda item = (Celda) this.adj[v].proximo ();
return item != null ? new Arista (v, item.vertice, item.peso) : null; }
public Arista eliminaArista (int v1, int v2) throws Exception { Celda clave = new Celda (v2, 0);
Celda item = (Celda) this.adj[v1].eliminar (clave); return item != null ? new Arista (v1, v2, item.peso) : null; }
TAD - Grafos Listas de adyacencia - usando punteros
Listas de adyacencia - usando punteros - implementacion
public void imprimir () {
for (int i = 0; i < this.numVertices; i++) { System.out.println ("Vertice " + i + ":"); Celda item = (Celda) this.adj[i].primero (); while (item != null) {
System.out.println (" " + item.vertice + " (" +item.peso+ ")"); item = (Celda) this.adj[i].proximo ();
} } }
public int numVertices () { return this.numVertices; } public Grafo grafoTranspuesto () {
Grafo grafoT = new Grafo (this.numVertices); for (int v = 0; v < this.numVertices; v++)
if (!this.listaAdjVacia (v)) { Arista adj = this.primeroListaAdj (v); while (adj != null) {
grafoT.insertaArista (adj.v2 (), adj.v1 (), adj.peso ()); adj = this.proxAdj (v); } } return grafoT; } }
´
Arbol de recubrimiento m´ınimo
´
Arbol de recubrimiento m´ınimo
•
Aplicaciones
•
Proyecto de comunicaciones conectando n localidades.
•
Rutas ´
areas con menor costo que conecten todas las ciudades.
•
Se deben realizar n − 1 conexiones, conectando dos localidades en
cada una.
•
Dentro de todas las posibilidades de conexiones, se debe encontrar la
que utiliza la menor cantidad de cables o vuelos.
•
Modelamiento: Sea G = (V, A) un grafo conectado no direccionado,
donde V es el conjunto de ciudades, y A las posibles conexiones.
p(u, v) es el costo de conectar la ciudad u y v.
•
Soluci´
on: se debe encontrar un subconjunto de aristas T ⊆ A que
conecta todos los v´
ertices del grafo G y cuyo peso total
p(T ) =
P
(u,v)∈T
p(u, v) es minimizado.
´
Arbol de recubrimiento m´ınimo
´
Arbol de recubrimiento M´ınimo
•
Como G
0
= (V, T ) es ac´ıclico y conecta todos los v´
ertices, T forma
un ´
arbol generador o ´
arbol de recubrimiento de G, ya que T
genera o recubre el grafo G.
•
Este problema es conocido como ´
Arbol Generador M´ınimo (AGM) o
´
´
Arbol de recubrimiento m´ınimo
AGM - Algoritmos gen´
erico
void GenericoAGM 1 S = 0;
2 while (S no forme un ´arbol de recubrimiento m´ınimo) 3 (u, v) = selecciona (A) ;
4 if (arista (u, v) es segura para S ) then S = S + {(u, v)} 5 return S ;
´
Arbol de recubrimiento m´ınimo Algoritmo de Prim
´
Arbol de recubrimiento m´ınimo Algoritmo de Prim
AGM - Algoritmo de Prim
package Cap7;
public class FPHeapMinIndirecto { private double p[];
private int n, pos[], fp[];
public FPHeapMinIndirecto (double p[], int v[]) { this.p = p; this.fp = v; this.n = this.fp.length-1; this.pos = new int[this.n];
for (int u = 0; u < this.n; u++) this.pos[u] = u+1; }
public void rehacer (int izq, int der) { int j = izq * 2;
int x = this.fp[izq]; while (j <= der) {
if ((j < der) && (this.p[fp[j]] > this.p[fp[j + 1]])) j++; if (this.p[x] <= this.p[fp[j]]) break;
this.fp[izq] = this.fp[j]; this.pos[fp[j]] = izq; izq = j; j = izq * 2;
}
this.fp[izq] = x; this.pos[x] = izq; }
public void construir () { int esq = n / 2 + 1;
while (esq > 1) { esq--; this.rehacer (esq, this.n); } }
´
Arbol de recubrimiento m´ınimo Algoritmo de Prim
AGM - Algoritmo de Prim
public int retirarMin () throws Exception { int minimo;
if (this.n < 1) throw new Exception ("Error: heap vacio"); else {
minimo = this.fp[1]; this.fp[1] = this.fp[this.n]; this.pos[fp[this.n--]] = 1; this.rehacer (1, this.n); }
return minimo; }
public void diminuirClave (int i, double claveNueva) throws Exception { i = this.pos[i]; int x = fp[i];
if (claveNueva < 0)
throw new Exception ("Error: clave nueva con valor incorrecto"); this.p[x] = claveNueva;
while ((i > 1) && (this.p[x] <= this.p[fp[i / 2]])) { this.fp[i] = this.fp[i / 2]; this.pos[fp[i / 2]] = i; i /= 2; }
this.fp[i] = x; this.pos[x] = i; }
boolean vacio () { return this.n == 0; }
public void imprimir () { for (int i = 1; i <= this.n; i++)
System.out.print (this.p[fp[i]] + " "); System.out.println ();
} }
´
Arbol de recubrimiento m´ınimo Algoritmo de Prim
Algoritmo de Prim
•
La clase F P HeapM inIndirecto nos sirve para realizar un heap
indirecto.
•
El arreglo pos[v] mantiene la posici´
on del vertice dentro de un heap
f p, permitiendo que el v´
ertice v pueda ser accedido a un costo de
O(1).
•
Este acceso es necesario para disminuirClave.
´
Arbol de recubrimiento m´ınimo Algoritmo de Prim
Algoritmo de Prim
package Cap7;
import Cap7.listaadj.autoreferencia.Grafo; public class AlgPrim {
private int antecesor[]; private double p[]; private Grafo grafo;
public AlgPrim (Grafo grafo) { this.grafo = grafo; }
public void obtenerAGM (int raiz) throws Exception { int n = this.grafo.numVertices();
this.p = new double[n]; // int vs[] = new int[n+1]; //
boolean itemsHeap[] = new boolean[n]; this.antecesor = new int[n]; for (int u = 0; u < n; u ++) { this.antecesor[u] = -1; p[u] = Double.MAX_VALUE; // vs[u+1] = u; // itemsHeap[u] = true; }
´
Arbol de recubrimiento m´ınimo Algoritmo de Prim
Algoritmo de Prim
p[raiz] = 0;
FPHeapMinIndirecto heap = new FPHeapMinIndirecto (p, vs); heap.construir ();
while (!heap.vacio ()) {
int u = heap.retirarMin (); itemsHeap[u] = false; if (!this.grafo.listaAdjVacia (u)) {
Grafo.Arista adj = grafo.primeroListaAdj (u); while (adj != null) {
int v = adj.v2 ();
if (itemsHeap[v] && (adj.peso () < this.peso (v))) { antecesor[v] = u; heap.disminuirClave (v, adj.peso ()); }
adj = grafo.proxAdj (u); }
} } }
public int antecesor (int u) { return this.antecesor[u]; } public double peso (int u) { return this.p[u]; } public void imprimir () {
for (int u = 0; u < this.p.length; u++) if (this.antecesor[u] != -1)
System.out.println ("(" +antecesor[u]+ "," +u+ ") -- p:" + peso (u));
} }
´
Arbol de recubrimiento m´ınimo Algoritmo de Prim
Algoritmo de Prim - An´
alisis
•
El cuerpo interno del blucle es ejecutado |V | veces.
•
El m´
etodo rehacer tiene un costo de O(log |V |).
•
Entonces, el tiempo total para ejecutar la operaci´
on retirarM in es
O(|V | log |V |).
•
El while interno para recorrer la lista de adyacencia es: O(|A|).
•
La operaci´
on disminuirClave tiene un costo de O(log |V |).
•
Entonces, el tiempo total para ejecutar el algoritmo es:
O(|V | log |V | + |A| log |V |).
´
Arbol de recubrimiento m´ınimo Algoritmo de Kruskal
Algoritmo de Kruskal
•
Siempre adiciona a la soluci´
on, la arista de menor peso que conecta
dos vertices distintos.
•
Al inicio del algoritmo se ordenan las aristas por el peso.
´
Arbol de recubrimiento m´ınimo Algoritmo de Kruskal
Algoritmo de Kruskal
•
Sean C
1
y C
2
dos subarboles conectados por los v´
ertices (u, v).
•
Si (u, v) es una arista ligera que permite la conexi´
on entre dos ´
arboles,
entonces es una arista segura.
•
Mediante este procedimiento, se obtiene un ´
Arbol de Recubrimiento
M´ınimo, a˜
nadiendo en cada paso la arista de menor peso que conecta
dos ´
arboles y no forma un ciclo.
•
Inicia con |V | ´
arboles de un s´
olo vertice. En |V | pasos, une dos
´
´
Arbol de recubrimiento m´ınimo Algoritmo de Kruskal
Algoritmo de Kruskal
•
Sean C
1
y C
2
dos subarboles conectados por los v´
ertices (u, v).
•
Si (u, v) es una arista ligera que permite la conexi´
on entre dos ´
arboles,
entonces es una arista segura.
•
Mediante este procedimiento, se obtiene un ´
Arbol de Recubrimiento
M´ınimo, a˜
nadiendo en cada paso la arista de menor peso que conecta
dos ´
arboles y no forma un ciclo.
•
Inicia con |V | ´
arboles de un s´
olo vertice. En |V | pasos, une dos
´
arboles hasta que exista un s´
olo ´
arbol.
´
Arbol de recubrimiento m´ınimo Algoritmo de Kruskal
Algoritmo de Kruskal
void Kruskal (Grafo grafo)
ConjuntoDisjunto conj = new ConjuntoDisjunto ( ) ; 1. S = {};
2. for ( int v=0; v<grafo.numVertices() ; v++) conj.creaConjunto(v) ; 3. Ordena las aristas por Peso;
4. for (cada (u, v) de A tomadas en orden ascendente del peso) 5. if ( conj.encontraConjunto (u) != conj.encontraConjunto (v) ) 6. S = S + {(u, v)};
´
Arbol de recubrimiento m´ınimo Algoritmo de Kruskal