Grafos
Cómo se describe una red de nodos, las dos formas de guardarla en memoria y los algoritmos clásicos de camino mínimo, recubrimiento, flujo y conexidad.
Qué es un grafo y de qué tipos los hay
Un grafo es una red: un conjunto de nodos o vértices unidos por aristas. A diferencia del árbol, aquí no hay raíz, no hay jerarquía y cualquier nodo puede conectarse con cualquier otro. Es el modelo natural de una red de carreteras, de una red de comunicaciones o de las relaciones entre usuarios de una red social.
| Clasificación | Tipos | Qué cambia |
|---|---|---|
| Por el sentido de las aristas | Dirigido (dígrafo) o no dirigido | En el dirigido la arista tiene sentido: se puede ir de A a B y no al revés. |
| Por la accesibilidad | Conexo o inconexo | En el conexo hay camino entre cualquier par de vértices; el inconexo tiene partes aisladas. |
| Por la existencia de ciclos | Cíclico o acíclico | El acíclico no tiene ningún camino que salga de un nodo y vuelva a él. |
| Por la multiplicidad | Grafo simple o multigrafo | El multigrafo admite más de una arista entre los mismos dos vértices. |
| Por la información de la arista | Etiquetado o ponderado | La arista lleva un valor asociado: distancia, coste, tiempo o capacidad. |
Un árbol es un caso particular de grafo: un grafo conexo y acíclico en el que se ha designado una raíz.
Para el examen
Qué es: vértices unidos por aristas, sin raíz ni jerarquía
Clasificaciones: dirigido o no, conexo o inconexo, cíclico o acíclico
Ponderado: cuando la arista lleva un valor asociado
Orden, tamaño y grado
Las tres medidas del grafo se preguntan juntas y se confunden entre sí, sobre todo porque «orden» y «grado» significan aquí cosas distintas de lo que significaban en los árboles.
| Medida | Qué cuenta |
|---|---|
| Orden del grafo | Número de vértices o nodos |
| Tamaño del grafo | Número de aristas |
| Grado de un vértice | Número de aristas que inciden en ese vértice |
| Grado del grafo | Suma de los grados de todos sus vértices |
En el grafo el orden cuenta vértices; en el árbol el orden era el máximo de hijos por nodo. Es la misma palabra para dos cosas distintas y hay que leer bien el enunciado.
Para el examen
Orden: número de vértices
Tamaño: número de aristas
Grado de un vértice: aristas que inciden en él
Cómo se guarda un grafo en memoria
Hay dos formas clásicas y la elección no es de estilo: cambia lo que ocupa el grafo y lo que tarda cada consulta.
La matriz de adyacencia es una tabla de tantas filas y columnas como vértices, donde la celda de la fila i y la columna j indica si existe arista entre esos dos vértices (o cuánto pesa, si el grafo es ponderado). Es inmediata de consultar, pero reserva espacio para todas las conexiones posibles aunque casi ninguna exista, así que desperdicia mucha memoria en grafos grandes y poco conectados.
La lista de adyacencia es un array de vértices en el que cada posición cuelga una lista enlazada con los vecinos de ese vértice. Solo ocupa lo que hay, pero comprobar si dos vértices concretos están unidos obliga a recorrer la lista de uno de ellos.
| Aspecto | Matriz de adyacencia | Listas de adyacencia |
|---|---|---|
| Memoria | O(V²) | O(V + E) |
| Comprobar si existe la arista (i, j) | O(1) | O(grado de i) |
| Recorrer todos los vecinos de un vértice | O(V) | O(grado del vértice) |
| Cuándo conviene | Grafo denso, con muchas aristas | Grafo disperso, con pocas aristas |
Para el examen
Matriz de adyacencia: V² celdas, consulta O(1); buena para grafos densos
Lista de adyacencia: memoria V + E; buena para grafos dispersos
Algoritmos de camino mínimo
El problema es el del navegador de un coche: dado un grafo ponderado, encontrar la ruta de menor coste entre dos puntos. Los algoritmos se distinguen por dos cosas: si resuelven un origen contra todos los destinos o todos los pares a la vez, y si admiten pesos negativos.
- Dijkstra: es voraz y va cerrando el vértice más cercano todavía no visitado. Resuelve un origen contra todos los destinos y es el más usado, pero no admite pesos negativos, porque su decisión de dar un vértice por definitivo deja de ser válida.
- Bellman-Ford: también parte de un origen, pero en lugar de decidir en firme relaja repetidamente todas las aristas. Es más lento que Dijkstra y a cambio sí admite pesos negativos, y además detecta si existe un ciclo de peso negativo, en el que el camino mínimo no estaría definido.
- Floyd-Warshall: calcula de una vez el camino mínimo entre todos los pares de vértices del grafo, no solo desde un origen. Su resultado es una matriz completa de distancias.
- Johnson: resuelve también todos los pares admitiendo pesos negativos, pero por otra vía: reajusta los pesos para dejarlos no negativos y después lanza Dijkstra desde cada vértice. Compensa en grafos dispersos.
- A*: es Dijkstra al que se le añade una heurística que estima lo que falta hasta el destino, de modo que explora primero por donde parece prometedor. Es el habitual en videojuegos y en navegación cuando interesa un destino concreto y no todos.
- Viterbi: aplica programación dinámica para hallar la secuencia de estados más probable en un modelo de Markov oculto, problema que equivale a buscar el camino óptimo en un grafo por capas. Se usa en reconocimiento del habla y en decodificación de señales.
Si aparecen pesos negativos, Dijkstra queda descartado: la respuesta es Bellman-Ford si hay un solo origen y Floyd-Warshall o Johnson si se piden todos los pares.
Para el examen
Dijkstra: un origen; NO admite pesos negativos
Bellman-Ford: un origen; admite negativos y detecta ciclos negativos
Floyd-Warshall: todos los pares de vértices
A*: Dijkstra con heurística hacia un destino concreto
Árbol de recubrimiento mínimo
Aquí el problema es otro y conviene no mezclarlo con el anterior. Partiendo de un grafo conexo y ponderado, se busca el subconjunto de aristas que conecta todos los vértices con el menor coste total posible. El resultado es un árbol, porque conectar todo sin ciclos es precisamente lo que define un árbol.
El caso típico es tender una red (cableado, tuberías, fibra) que llegue a todos los puntos gastando el mínimo de material. Los dos algoritmos clásicos son voraces y llegan siempre a un coste total óptimo, aunque el árbol concreto puede variar si hay aristas empatadas.
- Prim: crece desde un vértice inicial. En cada paso añade la arista más barata que une el árbol ya construido con un vértice que aún no está dentro.
- Kruskal: no crece desde ningún sitio. Ordena todas las aristas de menor a mayor y las va aceptando si no forman ciclo, de modo que durante el proceso hay varios fragmentos sueltos que acaban uniéndose.
Camino mínimo y recubrimiento mínimo responden a preguntas distintas: el primero minimiza el coste de ir de un punto a otro, el segundo minimiza el coste total de conectarlo todo. El camino entre dos nodos dentro de un árbol de recubrimiento mínimo no tiene por qué ser el camino mínimo entre ellos.
Para el examen
Qué busca: conectar todos los vértices al menor coste total
Prim: crece desde un vértice
Kruskal: acepta las aristas más baratas que no formen ciclo
Familia: los dos son voraces
Flujo máximo y componentes fuertemente conexas
El problema del flujo máximo trata las aristas como conductos con una capacidad limitada y pregunta cuánto se puede enviar de un origen a un destino sin superar ninguna capacidad. Sirve para dimensionar redes, tráfico o cadenas logísticas.
Ford-Fulkerson lo resuelve buscando repetidamente un camino desde el origen al destino que todavía admita más caudal, aumentando el flujo por él y descontando la capacidad usada. Se repite hasta que no queda ningún camino con capacidad libre. Edmonds-Karp es la versión concreta que elige siempre el camino con menos aristas, buscándolo en anchura, con lo que acota el número de repeticiones.
El algoritmo de Tarjan resuelve algo distinto: en un grafo dirigido, localiza sus componentes fuertemente conexas, que son los grupos de vértices en los que desde cualquiera se puede llegar a cualquier otro y volver. Lo hace en un solo recorrido en profundidad. Es la herramienta para detectar bucles de dependencias, por ejemplo entre módulos de software o entre tareas de un plan.
Para el examen
Flujo máximo: Ford-Fulkerson
Edmonds-Karp: variante de Ford-Fulkerson que busca en anchura
Componentes fuertemente conexas: algoritmo de Tarjan
Lo que cuesta cada algoritmo sobre grafos
V es el número de vértices y E el de aristas. Estas cifras son las que explican por qué se elige uno u otro y son las que más se preguntan de este bloque.
| Algoritmo | Qué resuelve | Complejidad | Pesos negativos |
|---|---|---|---|
| Recorrido en anchura o en profundidad | Explorar el grafo | O(V + E) | No aplica |
| Dijkstra con cola de prioridad | Camino mínimo desde un origen | O((V + E) log V) | No admite |
| Bellman-Ford | Camino mínimo desde un origen | O(V · E) | Admite y detecta ciclos negativos |
| Floyd-Warshall | Camino mínimo entre todos los pares | O(V³) | Admite si no hay ciclos negativos |
| Prim con montículo | Árbol de recubrimiento mínimo | O(E log V) | Admite |
| Kruskal | Árbol de recubrimiento mínimo | O(E log E) | Admite |
| Tarjan | Componentes fuertemente conexas | O(V + E) | No aplica |
Floyd-Warshall es cúbico pero resuelve todos los pares de una vez, así que en un grafo denso puede salir más barato que lanzar Dijkstra desde cada vértice.
Para el examen
Recorridos (DFS y BFS): O(V + E)
Dijkstra: O((V + E) log V)
Bellman-Ford: O(V · E)
Floyd-Warshall: O(V³)
Kruskal y Tarjan: O(E log E) y O(V + E)