¿Qué es mejor, listas de adyacencia o matriz de adyacencia, para problemas de gráficos en C ++? ¿Cuales son las ventajas y desventajas de cada uno?
c++
graph
adjacency-list
adjacency-matrix
magiix
fuente
fuente
std::list
(o mejor aúnstd::vector
).std::deque
ostd::set
. Depende de la forma en que el gráfico cambiará con el tiempo y qué algoritmos tiene la intención de ejecutar en ellos.Respuestas:
Depende del problema.
Matriz de adyacencia
entre cualquiera de los dos nodos O (1)
Lista de adyacencia
que podría ahorrar mucha memoria si la matriz de adyacencia es escasa
es ligeramente más lento que con la matriz O (k); donde k es el número de nodos vecinos
fuente
Esta respuesta no es solo para C ++, ya que todo lo mencionado se refiere a las estructuras de datos en sí mismas, independientemente del lenguaje. Y, mi respuesta es asumir que conoces la estructura básica de las listas y matrices de adyacencia.
Memoria
Si la memoria es su principal preocupación, puede seguir esta fórmula para un gráfico simple que permita bucles:
Una matriz de adyacencia ocupa n 2 /8 Superficie byte (un bit por entrada).
Una lista de adyacencia ocupa 8e espacio, donde e es el número de bordes (computadora de 32 bits).
Si definimos la densidad del gráfico como d = e / n 2 (número de aristas dividido por el número máximo de aristas), podemos encontrar el "punto de ruptura" donde una lista ocupa más memoria que una matriz:
8e> n 2 /8 cuando d> 1/64
Entonces, con estos números (aún específicos de 32 bits), el punto de ruptura cae en 1/64 . Si la densidad (e / n 2 ) es mayor que 1/64, entonces es preferible una matriz si desea ahorrar memoria.
Puede leer sobre esto en wikipedia (artículo sobre matrices de adyacencia) y en muchos otros sitios.
Nota al margen : se puede mejorar la eficiencia espacial de la matriz de adyacencia mediante el uso de una tabla hash donde las claves son pares de vértices (solo no dirigidos).
Iteración y búsqueda
Las listas de adyacencia son una forma compacta de representar solo los bordes existentes. Sin embargo, esto tiene el costo de una posible búsqueda lenta de bordes específicos. Dado que cada lista es tan larga como el grado de un vértice, el peor tiempo de búsqueda de un borde específico puede convertirse en O (n), si la lista no está ordenada. Sin embargo, buscar a los vecinos de un vértice se vuelve trivial, y para un gráfico escaso o pequeño, el costo de iterar a través de las listas de adyacencia puede ser insignificante.
Las matrices de adyacencia, por otro lado, usan más espacio para proporcionar un tiempo de búsqueda constante. Dado que existen todas las entradas posibles, puede verificar la existencia de un borde en tiempo constante utilizando índices. Sin embargo, la búsqueda de vecinos toma O (n) ya que debe verificar todos los vecinos posibles. El inconveniente obvio del espacio es que para gráficos dispersos se agrega mucho relleno. Consulte la discusión sobre la memoria anterior para obtener más información al respecto.
Si todavía no está seguro de qué usar : la mayoría de los problemas del mundo real producen gráficos dispersos y / o grandes, que son más adecuados para las representaciones de listas de adyacencia. Puede parecer más difícil de implementar, pero le aseguro que no lo son, y cuando escribe un BFS o DFS y desea buscar a todos los vecinos de un nodo, están a solo una línea de código. Sin embargo, tenga en cuenta que no estoy promocionando listas de adyacencia en general.
fuente
e = n / s
dóndes
está el tamaño del puntero.Bien, he compilado las complejidades de tiempo y espacio de las operaciones básicas en gráficos.
La imagen a continuación debe explicarse por sí misma.
Observe cómo es preferible la matriz de adyacencia cuando esperamos que el gráfico sea denso, y cómo es preferible la lista de adyacencia cuando esperamos que el gráfico sea escaso.
He hecho algunas suposiciones. Pregúnteme si una complejidad (Tiempo o Espacio) necesita aclaración. (Por ejemplo, para un gráfico disperso, he considerado que En es una constante pequeña, ya que supuse que la adición de un nuevo vértice agregará solo unos pocos bordes, porque esperamos que el gráfico permanezca disperso incluso después de agregar eso vértice.)
Por favor, dime si hay algún error.
fuente
Depende de lo que estés buscando.
Con las matrices de adyacencia , puede responder rápidamente a preguntas sobre si un borde específico entre dos vértices pertenece al gráfico, y también puede tener inserciones y eliminaciones rápidas de bordes. La desventaja es que debe usar un espacio excesivo, especialmente para gráficos con muchos vértices, lo cual es muy ineficiente, especialmente si su gráfico es escaso.
Por otro lado, con las listas de adyacencia es más difícil verificar si un borde dado está en un gráfico, porque debe buscar en la lista apropiada para encontrar el borde, pero son más eficientes en cuanto al espacio.
Generalmente, sin embargo, las listas de adyacencia son la estructura de datos correcta para la mayoría de las aplicaciones de gráficos.
fuente
Supongamos que tenemos un gráfico que tiene n número de nodos ym número de aristas,
Gráfico de ejemplo
Matriz de adyacencia: estamos creando una matriz que tiene n número de filas y columnas, por lo que en la memoria ocupará un espacio proporcional a n 2 . Comprobar si dos nodos nombrados como u y v tienen una ventaja entre ellos llevará Θ (1) tiempo. Por ejemplo, la comprobación de (1, 2) es un borde similar al siguiente en el código:
Si desea identificar todos los bordes, debe iterar sobre la matriz, ya que esto requerirá dos bucles anidados y tomará Θ (n 2 ). (Puede usar la parte triangular superior de la matriz para determinar todos los bordes, pero será nuevamente Θ (n 2 ))
Lista de adyacencia: estamos creando una lista que cada nodo también apunta a otra lista. Su lista tendrá n elementos y cada elemento apuntará a una lista que tenga una cantidad de elementos que sea igual a la cantidad de vecinos de este nodo (busque una mejor visualización en la imagen). Por lo tanto, ocupará un espacio en la memoria que es proporcional a n + m . Comprobar si (u, v) es un borde llevará tiempo O (deg (u)) en el que deg (u) es igual al número de vecinos de u. Porque a lo sumo, debe iterar sobre la lista que señala la u. Identificar todos los bordes tomará Θ (n + m).
Lista de adyacencia del gráfico de ejemplo
Debe hacer su elección según sus necesidades. Debido a mi reputación, no pude poner una imagen de matriz, lo siento.
fuente
Si está buscando análisis de gráficos en C ++, probablemente el primer lugar para comenzar sería la biblioteca de gráficos de impulso , que implementa una serie de algoritmos, incluido BFS.
EDITAR
Esta pregunta anterior sobre SO probablemente ayudará:
cómo-crear-ac-boost-undirected-graph-and-traverse-it-in-depth-first-searc h
fuente
Esto se responde mejor con ejemplos.
Piense en Floyd-Warshall por ejemplo. Tenemos que usar una matriz de adyacencia, o el algoritmo será asintóticamente más lento.
¿O qué pasa si es un gráfico denso en 30,000 vértices? Entonces, una matriz de adyacencia podría tener sentido, ya que almacenará 1 bit por par de vértices, en lugar de los 16 bits por borde (el mínimo que necesitaría para una lista de adyacencia): eso es 107 MB, en lugar de 1.7 GB.
Pero para algoritmos como DFS, BFS (y aquellos que lo usan, como Edmonds-Karp), búsqueda de prioridad primero (Dijkstra, Prim, A *), etc., una lista de adyacencia es tan buena como una matriz. Bueno, una matriz puede tener una ligera ventaja cuando el gráfico es denso, pero solo por un factor constante no notable. (¿Cuánto? Es cuestión de experimentar).
fuente
an adjacency list is as good as a matrix
en esos casos?Para agregar a las respuestas de keyser5053 sobre el uso de la memoria.
Para cualquier gráfico dirigido, una matriz de adyacencia (a 1 bit por borde) consume
n^2 * (1)
bits de memoria.Para un gráfico completo , una lista de adyacencia (con punteros de 64 bits) consume
n * (n * 64)
bits de memoria, excluyendo la sobrecarga de la lista.Para un gráfico incompleto, una lista de adyacencia consume
0
bits de memoria, excluyendo la sobrecarga de la lista.Para una lista de adyacencia, puede usar la siguiente fórmula para determinar el número máximo de aristas (
e
) antes de que una matriz de adyacencia sea óptima para la memoria.edges = n^2 / s
para determinar el número máximo de bordes, dondes
está el tamaño del puntero de la plataforma.Si su gráfico se está actualizando dinámicamente, puede mantener esta eficiencia con un conteo de bordes promedio (por nodo) de
n / s
.Algunos ejemplos con punteros de 64 bits y gráfico dinámico (un gráfico dinámico actualiza la solución de un problema de manera eficiente después de los cambios, en lugar de volver a calcularlo desde cero cada vez que se realiza un cambio).
Para un gráfico dirigido, donde
n
es 300, el número óptimo de aristas por nodo que usa una lista de adyacencia es:Si conectamos esto a la fórmula de keyser5053
d = e / n^2
(dondee
está el conteo total de bordes), podemos ver que estamos por debajo del punto de ruptura (1 / s
):Sin embargo, 64 bits para un puntero pueden ser excesivos. Si en su lugar utiliza enteros de 16 bits como compensaciones de puntero, podemos ajustar hasta 18 bordes antes del punto de ruptura.
Cada uno de estos ejemplos ignora la sobrecarga de las listas de adyacencia (
64*2
para un vector y punteros de 64 bits).fuente
d = (4 * 300) / (300 * 300)
, ¿no debería ser asíd = 4 / (300 * 300)
? Ya que la fórmula esd = e / n^2
.Dependiendo de la implementación de la Matriz de adyacencia, la 'n' del gráfico debe conocerse antes para una implementación eficiente. Si el gráfico es demasiado dinámico y requiere la expansión de la matriz de vez en cuando, ¿eso también puede contarse como una desventaja?
fuente
Si usa una tabla hash en lugar de una matriz o lista de adyacencia, obtendrá un mejor o el mismo tiempo de ejecución y espacio de O grande para todas las operaciones (comprobar si hay un borde
O(1)
, obtener todos los bordes adyacentes esO(degree)
, etc.).Sin embargo, hay una sobrecarga de factor constante tanto para el tiempo de ejecución como para el espacio (la tabla hash no es tan rápida como la lista vinculada o la búsqueda de matriz, y ocupa una cantidad decente de espacio extra para reducir las colisiones).
fuente
Solo voy a tratar de superar la compensación de la representación regular de la lista de adyacencia, ya que otras respuestas han cubierto otros aspectos.
Es posible representar un gráfico en la lista de adyacencia con la consulta EdgeExists en tiempo constante amortizado, aprovechando las estructuras de datos Dictionary y HashSet . La idea es mantener los vértices en un diccionario, y para cada vértice, mantenemos un conjunto de hash que hace referencia a otros vértices con los que tiene bordes.
Una compensación menor en esta implementación es que tendrá una complejidad de espacio O (V + 2E) en lugar de O (V + E) como en la lista de adyacencia regular, ya que los bordes se representan dos veces aquí (porque cada vértice tiene su propio conjunto de hash) de bordes). Pero las operaciones como AddVertex , AddEdge , RemoveEdge se pueden realizar en tiempo amortizado O (1) con esta implementación, a excepción de RemoveVertex que toma O (V) como matriz de adyacencia. Esto significaría que, aparte de la simplicidad de implementación, la matriz de adyacencia no tiene ninguna ventaja específica. Podemos ahorrar espacio en un gráfico disperso con casi el mismo rendimiento en esta implementación de lista de adyacencia.
Eche un vistazo a las implementaciones a continuación en el repositorio de Github C # para obtener más detalles. Tenga en cuenta que para el gráfico ponderado utiliza un diccionario anidado en lugar de una combinación de conjunto de diccionario-hash para acomodar el valor de peso. Del mismo modo para el gráfico dirigido, hay conjuntos de hash separados para los bordes de entrada y salida.
Algoritmos Avanzados
Nota: Creo que con la eliminación diferida podemos optimizar aún más la operación RemoveVertex a O (1) amortizado, aunque no he probado esa idea. Por ejemplo, después de la eliminación, simplemente marque el vértice como eliminado en el diccionario y luego borre perezosamente los bordes huérfanos durante otras operaciones.
fuente