Tema 14 – Grafos y su Implementación en Java Tema 14 – Grafos y su Implementación en Java Parte II Germán Moltó Escuela Técnica Superior de Ingeniería Informática Índice general: 1. Conceptos básicos sobre Grafos 2. Representación de un Grafo: Matriz vs Listas de Adyacencia 3. Representación de un Grafo Ponderado: la clase Adyacente 4. Representación de un Grafo ponderado y etiquetado: La clase GrafoDEtiquetado. 5. Recorrido en Profundidad (DFS) de un Grafo 6. Recorrido en Amplitud (BFS) de un Grafo Universidad Politécnica de Valencia 1 Recorrido de un Grafo: Ampliación de la clase Grafo Los recorridos permiten obtener una secuencia de sus elementos. Recorrido en Profundidad o Depth First Search (DFS) 3 Recorrido en Profundidad (DFS) de un Grafo En Árboles Binarios conseguíamos hacer un recorrido en profundidad de la siguiente manera: PreOrden (Árbol) { PreOrden(RaízÁrbol); } Generalización del Recorrido Pre-Orden de un Árbol Explora las aristas del Grafo de manera que se visitan los vértices adyacentes al recién visitado, con lo que se consigue “profundizar” en las ramas del Grafo. Recorrido en Anchura o Breadth First Search (BFS) 2 Generalización del Recorrido por Niveles de un Árbol. Explora las Aristas del Grafo de manera que se visitan los vértices adyacentes al explorado actualmente. Para cada uno de esos vértices, se exploran sus vecinos, provocando un recorrido en anchura. tratar(RaízÁrbol); Hijo(RaízÁrbol) PreOrden(Hijo(RaízÁrbol)); En Grafos lo haremos de manera similar: DFS(Grafo) { Verticei Grafo DFS(Verticei); } 4 tratar(Vérticei); adyacente(Vérticei) DFS(adyacente(Vérticei)); Gestión de Visitados para DFS Ejemplo de Recorrido en Profundidad b b 4 aa 11 8 7 hh 8 2 i 7 c 2 f 9 a 2 (8) 3 (4) 2 h 3 (11) 1 (8) 4 (1) 3 b 1 (4) 2 (11) 6 (8) 4 g .... 5 i .... .... .... 6 c 5 .. .. 10 1 Utilizar atributos para marcar los Vértices ya visitados: e 14 4 g 1 d 5 (7) si visitados[i] == 0, el Vértice i NO se ha visitado aún si visitados[i] > 0, el Vértice i SÍ se ha visitado. El valor de visitados[i] indica el orden en el que se ha visitado el Vértice: De esta manera se interpretará: visitados[3] = 5 El Vértice 3 se ha visitado en 5º posición, es decir, antes se han visitado otros 4 vértices. • Deberemos anotar los vértices ya visitados para no recorrerlos varias veces. • Utilizamos un vector de vértices ya visitados. 6 Traza de Recorrido en Profundidad: DFS (1/9) • Vértice origen: 1 Vertice’s 7 2 6 3 1 8 4 5 7 9 1:2,3,4,5 2 3:2,6,8 6:7 7:2 8:6 4:3,8 5 9:5,8 1 0 11 1 2 0 2 2 3 0 3 3 visitados 4 5 6 7 0 0 0 0 - - - - - - - - - - - 4 - - - 5 - - - 7 - - - 8 - - - - 7 8 4 5 Traza de Recorrido en Profundidad: DFS (2/9) Vertice’s 8 0 6 6 9 0 9 9 7 2 6 3 1 1 8 4 5 8 9 1:2,3,4,5 2 3:2,6,8 6:7 7:2 8:6 4:3,8 5 9:5,8 1 0 1 1 2 0 2 2 2 3 0 3 3 visitados 4 5 6 7 0 0 0 0 - - - - - - - - - - - 4 - - - 5 - - - 7 - - - 8 - - - - 7 8 4 5 8 0 6 6 9 0 9 9 Traza de Recorrido en Profundidad: DFS (3/9) Vertice’s 7 2 6 3 1 1 8 4 5 1:2,3,4,5 2 3:2,6,8 6:7 7:2 8:6 4:3,8 5 9:5,8 9 1 0 1 1 2 0 2 2 3 0 33 3 visitados 4 5 6 7 0 0 0 0 - - - - - - - - - - - 4 - - - 5 - - - 7 - - - 8 - - - - 7 8 4 5 Traza de Recorrido en Profundidad: DFS (4/9) Vertice’s 8 0 6 6 9 0 9 9 6 3 1 1 8 4 5 9 1:2,3,4,5 2 3:2,6,8 6:7 7:2 8:6 4:3,8 5 9:5,8 9 2 0 2 2 3 0 3 3 visitados 4 5 6 7 0 0 0 0 - - - - - - - - - - - 4 - - - 5 - - - 7 - - - 8 - - - - 7 8 4 5 8 0 6 6 9 0 9 9 10 Traza de Recorrido en Profundidad: DFS (5/9) Vertice’s 7 2 6 3 1 1 8 4 5 11 7 2 1 0 1 1 9 1:2,3,4,5 2 3:2,6,8 6:7 7:2 8:6 4:3,8 5 9:5,8 1 0 1 1 2 0 2 2 3 0 3 3 visitados 4 5 6 7 0 0 0 0 - - - - - - - - - - - 44 - - - 5 - - - 7 - - - 8 - - - - 7 8 4 5 Traza de Recorrido en Profundidad: DFS (6/9) Vertice’s 8 0 6 6 9 0 9 9 7 2 6 3 1 1 8 4 5 12 9 1:2,3,4,5 2 3:2,6,8 6:7 7:2 8:6 4:3,8 5 9:5,8 1 0 1 1 2 0 2 2 3 0 3 3 visitados 4 5 6 7 0 0 0 0 - - - - - - - - - - - 4 - - - 55 - - - 7 - - - 8 - - - - 7 8 4 5 8 0 6 6 9 0 9 9 Traza de Recorrido en Profundidad: DFS (7/9) Vertice’s 7 2 6 3 1 1 8 4 5 1:2,3,4,5 2 3:2,6,8 6:7 7:2 8:6 4:3,8 5 9:5,8 9 1 0 1 1 2 0 2 2 3 0 3 3 visitados 4 5 6 7 0 0 0 0 - - - - - - - - - - - 4 - - - 5 - - - 7 - - - 8 - - - - 7 8 4 5 Traza de Recorrido en Profundidad: DFS (8/9) Vertice’s 8 0 6 6 9 0 9 9 7 2 6 3 1 1 8 4 5 13 1:2,3,4,5 2 3:2,6,8 6:7 7:2 8:6 4:3,8 5 9:5,8 9 1 0 1 1 2 0 2 2 3 0 3 3 visitados 4 5 6 7 0 0 0 0 - - - - - - - - - - - 4 - - - 5 - - - 7 - - - 8 - - - - 7 8 4 5 9 0 9 9 8 0 6 6 9 0 9 9 14 Traza de Recorrido en Profundidad: DFS (9/9) Vertice’s 7 2 6 3 1 1 8 4 5 9 1:2,3,4,5 2 3:2,6,8 6:7 7:2 8:6 4:3,8 5 9:5,8 ..... 1 0 1 1 2 0 2 2 3 0 3 3 visitados 4 5 6 7 0 0 0 0 - - - - - - - - - - - 4 - - - 5 - - - 77 - - - 8 - - - - 7 8 4 5 8 0 6 6 9 0 9 9 Traza de Recorrido en Profundidad: DFS (Final) Vertice’s 7 2 6 3 1 8 4 5 9 1:2,3,4,5 2 3:2,6,8 6:7 7:2 8:6 4:3,8 5 9:5,8 1 0 1 1 2 0 2 2 3 0 3 3 visitados 4 5 6 7 0 0 0 0 - - - - - - - - - - - 4 - - - 5 - - - 7 - - - 8 - - - - 7 8 4 5 • La secuencia de vértices visitados (DFS) es: 1 2 3 6 7 8 4 5 9 15 8 0 6 6 6 16 Ampliación de la clase Grafo para Soporte de DFS Ampliamos la clase Grafo para soportar la implementación del recorrido DFS. public abstract class Grafo<E>{ protected int visitados[]; //Para el recorrido DFS protected int ordenVisita; //Orden de visita de los vértices public String toStringDFS() {...} protected String arrayToString(int v[]){ … } public int[] toArrayDFS() { … } protected void toArrayDFS(int origen, int res[]) { … } } /* Fin de la clase Grafo */ 17 Métodos de Soporte para DFS (I) public int[] toArrayDFS() { int res[] = new int[numVertices() + 1]; visitados = new int[numVertices() + 1]; ordenVisita = 1; for (int i = 1; i <= numVertices(); i++) if ( visitados[i] == 0 ) toArrayDFS(i, res); return res; } Es necesario realizar el bucle for por si el grafo tiene varias componentes conexas (en ese caso no sería posible alcanzar todos los vértices desde el vértice origen). 18 Métodos de Soporte para DFS (II) Métodos de Soporte para DFS (III) protected void toArrayDFS(int origen, int res[]) { res[ordenVisita] = origen; visitados[origen] = ordenVisita++; ListaConPI<Adyacente> l = adyacentesDe(origen); for (l.inicio(); !l.esFin(); l.siguiente()) { Adyacente a = l.recuperar(); if (visitados[a.destino] == 0) toArrayDFS(a.destino, res); } } El array res contiene directamente la public String toStringDFS() { return arrayToString(toArrayDFS()); } secuencia de vértices en el orden en el que son recorridos mediante DFS. 19 protected String arrayToString(int v[]){ StringBuilder sb = new StringBuilder(); for (int i = 1; i < v.length; i++) sb.append(v[i] + "\n"); return sb.toString(); } 20 Recorrido en Anchura (BFS) de un Grafo • En Árboles Binarios conseguíamos hacer un recorrido por niveles (o en anchura) de la siguiente manera: PorNiveles (Árbol) { PorNiveles(RaízÁrbol); } • En Grafos se hace: BFS(Grafo) { Cola<Integer> q = new ArrayCola<Integer>(); Verticei Grafo BFS(Verticei); } Traza de Recorrido en Anchura: BFS Cola<NodoABB<E>> q = new ArrayCola<NodoABB<E>>(); q.encolar(RaízÁrbol); mientras ( !q.esVacia() ){ r = q.desencolar(); tratar(r); Hijo(r): q.encolar(Hijo(r)); } tratar(Vérticei); q.encolar(Vérticei); mientras ( !q.esVacia() ) { v = q.desencolar(); Adyacente(v): tratar(Adyacente(v) ); q.encolar(Adyacente(v)); } 21 Traza de Recorrido en Anchura: BFS (Final) Vértice • Vértice origen: 1 6 3 1 8 4 5 100000000 7 2 9 Visitados Cola 123456789 000000000 1 1: 2 3 4 5 123450000 2345 2: 123450000 345 3: 2 6 8 123456070 4568 4: 3 8 123456070 568 5: 123456070 68 6: 7 123456870 87 8: 6 123456870 7 7: 2 123456870 9: 5 8 123456879 aa 11 8 0 7 h 2 i 1 7 cc 9 e 14 4 gg dd 2 f • Estado de la Cola de Vértices por procesar: 0| 1| 2| 3| 4| 5| 6| 7| 10 • Vértice origen: a a 1 (8) 2 (4) 1 h 2 (11) 0 (8) 3 (1) 2 b 0 (4) 1 (11) 5 (8) 3 g 6(2) 1 (1) 4 i 1 (7) 5 (2) 5 c 4 (2) 6 (4) 7 (7) 6 f 8 (10) .. ..22 ........ 7 (14) 5 (4) 4 (7) Aunque en esta traza los vértices se numeren de 0..N, de normal los numeramos de 1..N 2 (8) Ampliación de la clase Grafo para Soporte de BFS public abstract class Grafo<E>{ … /** Para el recorrido DFS y BFS */ protected int visitados[]; protected int ordenVisita; /** para el Recorrido en Anchura (BFS) */ protected Cola<Integer> q; public String toStringBFS() { … } public int[] toArrayBFS() { … } protected void toArrayBFS(int origen, int res[]) { … } } /* Fin de la clase Grafo */ • La secuencia de vértices visitados (BFS) es: 1 2 3 4 5 6 8 7 9 23 8 b 4 24 El método toArrayBFS (1/2) El método toArrayBFS (2/2) public String toStringBFS() { protected void toArrayBFS(int origen, int res[]) { res[ordenVisita] = origen; visitados[origen] = ordenVisita++; q.encolar(new Integer(origen)); while (!q.esVacia()) { int u = q.desencolar().intValue(); ListaConPI<Adyacente> l = adyacentesDe(u); for (l.inicio(); !l.esFin(); l.siguiente()) { Adyacente a = l.recuperar(); if (visitados[a.destino] == 0) { res[ordenVisita] = a.destino; visitados[a.destino] = ordenVisita++; q.encolar(new Integer(a.destino)); } } } } return arrayToString(toArrayBFS()); } } public int[] toArrayBFS() { int res[] = new int[numVertices() + 1]; visitados = new int[numVertices() + 1]; ordenVisita = 1; q = new ArrayCola<Integer>(); for ( int i = 1; i <= numVertices(); i++ ) if (visitados[i] == 0) toArrayBFS(i, res); return res; } 25 26 Aplicaciones de las Estrategias de Recorrido El recorrido en profundidad de un Grafo permite: Determinar el número de componentes conexas, a partir del número de invocaciones al método toArrayDFS desde el bucle principal. Determinar si un Grafo es acíclico. Si se trata de visitar un nodo ya visitado entonces existe un ciclo en el Grafo. El recorrido en anchura de un Grafo permite: 27 Exploración parcial de un grafo de tamaño elevado. Encontrar el camino más corto entre dos nodos.