Representación de grafos
El mismo grafo se puede guardar como matriz o como listas de adyacencia. La elección cambia qué preguntas son baratas y cuánta memoria hace falta.
Para este tema conviene tener claro:Listas enlazadas
Un mapa de rutas, las dependencias de un proyecto y una red social son el mismo objeto: elementos y relaciones entre ellos. Antes de recorrerlo o de buscar caminos hay que decidir cómo se guarda, porque esa decisión fija el costo de todo lo que venga después.
Vértices, aristas y las variantes
Un grafo son vértices y aristas. Si la relación es simétrica el grafo es no dirigido; si no lo es, cada arista tiene origen y destino. Las aristas pueden llevar peso: distancia, costo, capacidad.
Dos números mandan en el análisis: la cantidad de vértices y la de aristas . Un grafo es denso cuando se acerca a y ralo cuando se parece a . Casi todos los grafos reales son ralos, y eso decide gran parte de lo que sigue.
Antes de seguir, predecí
La matriz de adyacencia
La matriz de adyacencia es un arreglo de donde la celda dice si existe la arista, o cuánto pesa. Preguntar si dos vértices son vecinos es una lectura: .
El precio es la memoria, sin importar cuántas aristas haya, y el costo de recorrer los vecinos de un vértice, que obliga a mirar la fila entera: , la mayoría celdas vacías. En un grafo no dirigido la matriz es simétrica y alcanza con guardar media.
Las listas de adyacencia
Las listas de adyacencia guardan, para cada vértice, sólo sus vecinos reales. La memoria pasa a ser y recorrer los vecinos de un vértice cuesta lo que ese vértice tiene, no lo que podría tener.
Lo que se pierde es la consulta directa: saber si existe una arista puntual obliga a recorrer la lista del vértice, . Para un grafo ralo la cuenta cierra a favor de las listas, y por eso son la representación por defecto.
El grafo completo. V = 5, E = 5: bien ralo, como casi cualquier grafo real.
Comparar E contra V al cuadrado
La regla práctica sale de comparar con . Con un grafo denso o algoritmos que consultan aristas sueltas todo el tiempo, la matriz gana. Con un grafo ralo que se recorre —BFS, DFS, caminos mínimos—, ganan las listas: esos algoritmos terminan en con listas y arrastran un con matriz.
Hay un caso a favor de la matriz aunque el grafo sea ralo: los algoritmos que la usan como matriz, por ejemplo calcular caminos entre todos los pares.
Representaciones intermedias
Hay representaciones intermedias. La lista de aristas guarda sólo las ternas origen, destino y peso: es lo mínimo para algoritmos que las ordenan por peso, como el árbol generador mínimo.
Para grafos grandes y estáticos se usa la forma comprimida: un arreglo con todos los vecinos concatenados y otro con dónde empieza cada vértice. Ocupa poco, recorre rápido por localidad de memoria y no admite cambios sin reconstruirla.
Dos detalles que se pagan caro
Dos detalles se pagan caro si se ignoran. El primero: en un grafo no dirigido cada arista aparece dos veces en las listas, así que la memoria es y un recorrido ingenuo puede procesarla dos veces.
El segundo: los vértices conviene numerarlos de a y traducir los nombres reales con una tabla hash. Con índices enteros, el estado de los algoritmos —visitados, distancias, padres— son arreglos comunes en vez de diccionarios.
Elegir la representación
| Lista de adyacencia | Matriz de adyacencia | Lista de aristas | |
|---|---|---|---|
| Memoria | O(V + E) | O(V²) | O(E) |
| ¿Hay arista entre u y v? | O(grado de u) | O(1) | O(E) |
| Recorrer los vecinos de u | O(grado de u) | O(V) | O(E) |
| Agregar una arista | O(1) | O(1) | O(1) |
| Recorrer todas las aristas | O(V + E) | O(V²) | O(E) |
| Conviene cuando | el grafo es ralo, que es casi siempre | es denso o se consulta mucho por pares | sólo hay que ordenar aristas, como en Kruskal |
La cuenta que ordena la decisión es cuántas aristas tiene el grafo comparado con cuántas podría tener. Un grafo con vértices puede tener hasta aristas, y los grafos reales casi nunca se acercan: una red social con mil millones de cuentas no tiene mil millones de amigos por cuenta, tiene unos cientos. Por eso la lista de adyacencia es el default, y la matriz aparece sólo cuando el grafo es chico o genuinamente denso.
Más a fondo · formalRalo y denso, con números
Un grafo es denso cuando se acerca a y ralo cuando se acerca a . El punto donde conviene cambiar de representación no es una constante sino el cruce de los dos costos: la matriz gana cuando es comparable a , o sea cuando es del orden de .
Con : la matriz ocupa un millón de casillas siempre, y la lista, . Con —cinco vecinos promedio, que es un grafo perfectamente normal— la lista usa unas 170 veces menos memoria. Para que la matriz empiece a convenir haría falta que cada vértice tuviera cientos de vecinos.
Hay un caso en que la matriz gana igual, y es cuando el algoritmo la necesita como matriz: Floyd-Warshall recorre combinaciones y consulta pares directamente, así que ahí la representación no es una elección de almacenamiento sino parte del algoritmo. Y multiplicar matrices de adyacencia tiene un significado propio: la potencia -ésima cuenta cuántos caminos de largo exactamente hay entre cada par.
Cierre
Matriz y listas describen el mismo grafo con costos opuestos: la matriz responde rápido por una arista y paga de memoria; las listas ocupan y recorren vecinos sin desperdicio. Con grafos ralos, que son casi todos, las listas son el default.
Autoevaluación
¿Lo entendiste?
Práctica