En informática , el algoritmo de Prim es un algoritmo codicioso que encuentra un árbol de expansión mínimo para un gráfico no dirigido ponderado . Esto significa que encuentra un subconjunto de las aristas que forma un árbol que incluye cada vértice , donde se minimiza el peso total de todas las aristas del árbol. El algoritmo opera construyendo este árbol un vértice a la vez, desde un vértice inicial arbitrario, agregando en cada paso la conexión más barata posible del árbol a otro vértice.
El algoritmo fue desarrollado en 1930 por el matemático checo Vojtěch Jarník [1] y posteriormente redescubierto y reeditado por los científicos informáticos Robert C. Prim en 1957 [2] y Edsger W. Dijkstra en 1959. [3] Por lo tanto, a veces también se le llama Algoritmo de Jarník , [4] Algoritmo de Prim-Jarník , [5] Algoritmo de Prim-Dijkstra [6] o el algoritmo DJP . [7]
Otros algoritmos bien conocidos para este problema incluyen el algoritmo de Kruskal y el algoritmo de Borůvka . [8] Estos algoritmos encuentran el bosque de expansión mínima en un gráfico posiblemente desconectado; por el contrario, la forma más básica del algoritmo de Prim sólo encuentra árboles de expansión mínimos en gráficos conectados. Sin embargo, al ejecutar el algoritmo de Prim por separado para cada componente conectado del gráfico, también se puede utilizar para encontrar el bosque de expansión mínima. [9] En términos de su complejidad de tiempo asintótica , estos tres algoritmos son igualmente rápidos para gráficos dispersos , pero más lentos que otros algoritmos más sofisticados. [7] [6] Sin embargo, para gráficos que son suficientemente densos, se puede hacer que el algoritmo de Prim se ejecute en tiempo lineal , cumpliendo o mejorando los límites de tiempo de otros algoritmos. [10]
El algoritmo puede describirse informalmente realizando los siguientes pasos:
Con más detalle, se puede implementar siguiendo el pseudocódigo siguiente.
Como se describió anteriormente, el vértice inicial del algoritmo se elegirá arbitrariamente, porque la primera iteración del bucle principal del algoritmo tendrá un conjunto de vértices en Q todos con pesos iguales, y el algoritmo iniciará automáticamente un nuevo árbol en F cuando completa un árbol de expansión de cada componente conectado del gráfico de entrada. El algoritmo se puede modificar para comenzar con cualquier vértice s en particular estableciendo C [ s ] como un número menor que los otros valores de C (por ejemplo, cero), y se puede modificar para encontrar solo un único árbol de expansión en lugar de un bosque completo (que coincide más estrechamente con la descripción informal) deteniéndose cada vez que encuentra otro vértice marcado como sin borde asociado.
Las diferentes variaciones del algoritmo se diferencian entre sí en cómo se implementa el conjunto Q : como una lista enlazada simple o una matriz de vértices, o como una estructura de datos de cola de prioridad más complicada . Esta elección conduce a diferencias en la complejidad temporal del algoritmo. En general, una cola de prioridad será más rápida para encontrar el vértice v con un costo mínimo, pero implicará actualizaciones más costosas cuando cambie el valor de C [ w ].
La complejidad temporal del algoritmo de Prim depende de las estructuras de datos utilizadas para el gráfico y para ordenar los bordes por peso, lo que se puede hacer mediante una cola de prioridad . La siguiente tabla muestra las opciones típicas:
Una implementación simple de Prim, usando una matriz de adyacencia o una representación gráfica de lista de adyacencia y buscando linealmente una matriz de pesos para encontrar el borde de peso mínimo para agregar, requiere tiempo de ejecución O (|V| 2 ). Sin embargo, este tiempo de ejecución se puede mejorar enormemente mediante el uso de montones para implementar la búsqueda de bordes de peso mínimo en el bucle interno del algoritmo.
Una primera versión mejorada utiliza un montón para almacenar todos los bordes del gráfico de entrada, ordenados por su peso. Esto conduce a un tiempo de ejecución en el peor de los casos O(|E| log |E|). Pero almacenar vértices en lugar de aristas puede mejorarlo aún más. El montón debe ordenar los vértices por el peso de borde más pequeño que los conecta a cualquier vértice en el árbol de expansión mínima (MST) parcialmente construido (o infinito si no existe tal borde). Cada vez que se elige un vértice v y se agrega al MST, se realiza una operación de clave de disminución en todos los vértices w fuera del MST parcial de manera que v esté conectado a w , estableciendo la clave al mínimo de su valor anterior y el costo del borde. de ( v , w ).
Utilizando una estructura de datos de montón binario simple , ahora se puede demostrar que el algoritmo de Prim se ejecuta en el tiempo O (|E| log |V|) donde |E| es el número de aristas y |V| es el número de vértices. Usando un montón de Fibonacci más sofisticado , esto se puede reducir a O (|E| + |V| log |V|), que es asintóticamente más rápido cuando el gráfico es lo suficientemente denso como para que |E| es ω (|V|), y el tiempo lineal cuando |E| es al menos |V| registro |V|. Para gráficos de densidad aún mayor (que tienen al menos |V| c aristas para algunos c > 1), se puede hacer que el algoritmo de Prim se ejecute en tiempo lineal de manera aún más simple, usando un montón d -ario en lugar de un montón de Fibonacci. [10] [11]
Sea P un gráfico ponderado y conexo . En cada iteración del algoritmo de Prim, se debe encontrar una arista que conecte un vértice en un subgrafo con un vértice fuera del subgrafo. Como P es conexo, siempre habrá un camino a cada vértice. La salida Y del algoritmo de Prim es un árbol , porque el borde y el vértice agregados al árbol Y están conectados. Sea Y 1 un árbol de expansión mínimo del gráfico P. Si Y 1 = Y entonces Y es un árbol de expansión mínimo. De lo contrario, sea e la primera arista agregada durante la construcción del árbol Y que no está en el árbol Y 1 , y V sea el conjunto de vértices conectados por las aristas agregadas antes de la arista e . Entonces un punto final del borde e está en el conjunto V y el otro no. Dado que el árbol Y 1 es un árbol de expansión del gráfico P , hay una ruta en el árbol Y 1 que une los dos puntos finales. A medida que uno viaja a lo largo del camino, debe encontrar una arista f que une un vértice en el conjunto V con uno que no está en el conjunto V. Ahora, en la iteración cuando se agregó el borde e al árbol Y , también se podría haber agregado el borde f y se agregaría en lugar del borde e si su peso fuera menor que e , y como el borde f no se agregó, concluimos que
Sea el árbol Y 2 el gráfico obtenido eliminando el borde f y agregando el borde e al árbol Y 1 . Es fácil demostrar que el árbol Y 2 está conectado, tiene el mismo número de aristas que el árbol Y 1 y los pesos totales de sus aristas no son mayores que los del árbol Y 1 , por lo tanto, también es un árbol de expansión mínimo del gráfico. P y contiene el borde e y todos los bordes agregados antes durante la construcción del conjunto V. Repita los pasos anteriores y eventualmente obtendremos un árbol de expansión mínimo del gráfico P que es idéntico al árbol Y. Esto muestra que Y es un árbol de expansión mínimo. El árbol de expansión mínimo permite que el primer subconjunto de la subregión se expanda a un subconjunto X más grande , que suponemos que es el mínimo.
El bucle principal del algoritmo de Prim es inherentemente secuencial y, por tanto, no paralelizable . Sin embargo, el bucle interno, que determina la siguiente arista de peso mínimo que no forma un ciclo, se puede paralelizar dividiendo los vértices y aristas entre los procesadores disponibles. [12] El siguiente pseudocódigo demuestra esto.
Este algoritmo generalmente se puede implementar en máquinas distribuidas [12] así como en máquinas de memoria compartida. [13] El tiempo de ejecución es , suponiendo que las operaciones de reducción y transmisión se pueden realizar en . [12] También se ha explorado una variante del algoritmo de Prim para máquinas de memoria compartida, en la que el algoritmo secuencial de Prim se ejecuta en paralelo, comenzando desde diferentes vértices. [14] Sin embargo, cabe señalar que existen algoritmos más sofisticados para resolver el problema del árbol de expansión mínimo distribuido de una manera más eficiente.