Comenzamos el curso de nuevo. Ya hace unos nueve años que conozco lo que son las estructuras de datos avanzadas para la programación, pero no obstante, qué le vamos a hacer, hay que sacarse los estudios y me toca profundizar un poco en este tipo de datos. He pensado que, para hacer más llevadero el estudio, voy a comenzar a publicar aquí mis apuntes, así, además de tenerlos siempre disponibles, los comparto.
Los grafos son nodos (o vertices) unidos por líneas (o aristas). Según las aristas podemos encontrarnos grafos no dirigidos o grafos dirigidos, según si las aristas tienen dirección o no.
Un ejemplo de grafo no dirigido:
La forma de representar el gráfo en modo de ecuación es G = (N, A), siendo N(G) el conjunto de nodos (vértices) y A(G) el conjunto de aristas del grafo. La representación del grafo de ejemplo sería el siguiente:
N(G) = { 1, 2, 3, 4, 5, 6 }
A(G) = { (1,2), (1,6), (6,4), (4,5), (2,3), (2,4) }
Una arista que se enlaza un nodo consigo mismo se la denomina bucle o lazo.
Dos nodos son adyacentes si siendo distintos existe una arista que los une.
El grado de un vértice (o nodo) es el número de aristas que entran o salen de él.
Un camino es una secuencia de aristas consecutivas. La longitud del camino corresponde al número de aristas que contiene. Hay dos tipos de caminos:
Un ciclo (o circuito) es un camino simple que empieza y termina en el mismo vértice.
Un ejemplo de grafo dirigido:
La forma de representar el gráfo en modo de ecuación es G = <N, A>, siendo N<G> el conjunto de nodos (vértices) y A<G> el conjunto de aristas del grafo. La representación del grafo de ejemplo sería el siguiente:
N(G) = { 1, 2, 3, 4, 5, 6 }
A(G) = { <2,1>, <1,6>, <6,4>, <4,5>, <3,2>, <2,4>, <4,4> }
El grado de un vértice (o nodo) en el grafo dirigido, puede ser de:
Un camino, en un grafo dirigido, es una secuencia finita de aristas entre dos vértices, siendo el extremo final de cada arista el extremo inicial de la siguiente.
Un grafo dirigido es fuertemente conexo si para cada par de vértices distinos n y m hay un camino de n a m y viceversa. El grafo dirigido expuesto como ejemplo tiene esta propiedad, ya que se puede acceder a cualquier nodo desde cualquier otro.
Dos vértices o nodos están conectados si hay un camino que los une.
Las aristas, al igual que los vértices, también pueden contener información, este es el caso de los grafos etiquetados (o valorados). Se representan de la siguiente forma:
G = (N,A,P)
N(G) = { 1, 2, 3, 4, 5, 6 }
A(G) = { <2,1>, <1,4>, <4,3>, <3,2>, <2,4>, <4,5>, <6,4> }
P(G) = { 5, 8, 1, 10, 2, 6, 7 }
La representación gráfica:
Realizando, según sus propiedades, una categorización de grafos podemos distinguir los siguientes tipos:
Un subgrafo conexo maximal, es un grafo G' que deriva de G, que está contenido, de ninguna forma, en otro subconjunto posible de G. Por ejemplo:
Este subgrafo (G), puede contener a dos subgrafos:
N(G') = { 1, 2, 3 }
A(G') = { <1,2>, <2,3> }
N(G') = { 4, 5, 6 }
A(G') = { <6,4>, <4,5> }
Como cada uno de estos subgrafos es máximo, es decir, de forma conexa, no se puede incluir ningún otro nodo del grafo padre que pueda completar más el subgrafo, a cada uno de los subgrafos se les llama conexo maximal.
Se denomina componente conexa de un grafo, a un subgrafo conexo maximal.
En cambio, si el grafo fuese así:
Sería posible, para el primer N(G') agregar el nodo 4, e incluso el 5 o el 6, de modo que, este subgrafo, deja de ser conexo maximal.
El máximo número de aristas en un grafo no dirigido de n vértices es n(n-1)/2. Si tiene ese número de aristas se dice que es completo. No se cuentan los bucles.
El grafo dirigido que no es fuertemente conexo, un subgrafo fuertemente conexo maximal recibe el nombre de componente fuertemente conexa del grafo. El ejemplo tiene dos componentes fuertemente conexas:
Las dos componentes fuertemente conexas serían:
N(G') = { 1, 2, 3 }
A(G') = { <1,2>, <2,1>, <2,3>, <3,2> }
N(G') = { 4, 5, 6 }
A(G') = { <6,4>, <4,6>, <4,5>, <5,4> }
El máximo número de aristas en un grafo dirigido de n vértices es n(n-1). No teniendo en cuenta los bucles.
Los grafos se pueden representar de varias formas, pero las dos más comunes son: la matriz de adyacencias y la lista de adyacencias.
Se crea una matriz (la llamaremos MA por Matriz de Adyacencias) asociada a G, de modo que tenga n x n elementos. Indicaremos sus contenidos con 0 y 1, siendo 1 que en la posición MA[i,j] existe una arista y 0 lo contrario.
Un ejemplo, viendo este grafo dirigido:
Su matriz de adyacencia sería la siguiente (teniendo en cuenta las filas como el origen y las columnas como el destino):
1 | 2 | 3 | 4 | 5 | 6 | |
---|---|---|---|---|---|---|
1 | 1 | |||||
2 | 1 | 1 | ||||
3 | 1 | |||||
4 | 1 | 1 | ||||
5 | ||||||
6 | 1 |
En los grafos no dirigidos cada arista es simétrica. En el caso de los grafos valorados, los valores que se representan en la tabla ya no serían 0 y 1, sino el valor del peso de cada arista.
Determinar cuántas aristas hay en un grafo tiene un coste de O(n<sup>2</sup>), y requiere de un espacio de memoria de θ(n<sup>2</sup>). Por lo que no es aconsejado si hay pocas aristas.
Se representa por un vector de n listas, siendo n el número de nodos, por lo que contiene tantas listas como nodos hay en el grafo. Por lo que el grafo que se muestra a continuación:
Tendrá esta representación en formato de listas de adyacencia:
En el caso de los grafos etiquetados, las listas deberían incluir un campo adicional para almacenar la etiqueta asociada a cada arista.
Determinar si existe una arista entre el nodo i y el nodo j puede llevar un tiempo de O(n), ya que puede haber n vértices en la lista asociada de ese vértice en concreto. El coste en espacio de esta representación está en θ(n+a), siendo n el número de nodos y a el número de aristas.
Hasta aquí he querido llegar de momento en este tema de los grafos. La parte de algoritmia, costes asociados y demás elementos, lo dejo para el siguiente día de estudio.