Algoritmo de Prim
Última actualización
El algoritmo de Prim construye un árbol de expansión mínima (MST) - el conjunto de aristas más barato que conecta todos los nodos sin ciclos - haciendo crecer un único árbol hacia afuera desde un nodo inicial. En cada paso observa todas las aristas que cruzan del árbol a un nodo fuera de él y añade la más barata. Pulsa reproducir arriba para ver cómo se expande el árbol, tomando siempre la arista de menor peso que alcanza un nuevo nodo.
Como siempre elige la arista de cruce mínima, cada adición es segura (garantizada como parte de algún MST). Con una cola de prioridad basada en un montículo binario indexada por la arista más barata hacia cada nodo exterior, Prim se ejecuta en O(E log V). Contrasta con Kruskal, que ordena todas las aristas globalmente en lugar de hacer crecer un único árbol conexo.
Complejidad temporal y espacial
| Implementación | Complejidad | Notas |
|---|---|---|
| Montículo binario | O(E log V) | Cola de prioridad de aristas de cruce |
| Matriz de adyacencia | O(V²) | Más simple; buena para grafos densos |
| Espacio | O(V + E) | Pertenencia al árbol + cola de prioridad |
| Mejor para | Grafos densos | Crece desde un único nodo inicial |
Paso a paso
| Paso | Qué ocurre |
|---|---|
| 1 | Inicia el árbol con cualquier nodo único. |
| 2 | Observa todas las aristas que cruzan del árbol a un nodo fuera de él. |
| 3 | Elige la arista de cruce con el menor peso. |
| 4 | Añade esa arista y su nuevo nodo al árbol. |
| 5 | Repite hasta que todos los nodos estén en el árbol. |
Ejemplo resuelto
Construyendo el MST de un grafo de 4 nodos con aristas A-B=1, A-C=3, B-C=2, B-D=4, C-D=5, comenzando desde A:
| Paso | Árbol | Aristas de cruce | Arista elegida |
|---|---|---|---|
| 1 | {A} | A-B=1, A-C=3 | A-B (peso 1) |
| 2 | {A, B} | A-C=3, B-C=2, B-D=4 | B-C (peso 2) |
| 3 | {A, B, C} | B-D=4, C-D=5 | B-D (peso 4) |
| 4 | {A, B, C, D} | ninguna - todos los nodos en el árbol | listo: peso del MST 1+2+4 = 7 |
Cuándo usar el algoritmo de Prim
| Úsalo cuando | Evítalo cuando |
|---|---|
| Necesitas un árbol de expansión mínima de un grafo conexo, no dirigido y ponderado. | El grafo es dirigido o necesitas caminos más cortos - usa Dijkstra o Bellman-Ford en su lugar. |
El grafo es denso (E cercano a V²); la forma matricial O(V²) es simple y rápida. | El grafo es disperso y las aristas ya están ordenadas o son fáciles de ordenar - Kruskal suele ser más simple. |
| Quieres que el árbol crezca desde una región hacia afuera (p. ej., diseño incremental de una red). | El grafo está desconectado - Prim solo abarca un componente; necesitas un bosque de expansión mínima. |
| Ya tienes una estructura de adyacencia y una cola de prioridad disponibles. | Necesitas detectar ciclos en un conjunto global de aristas - union-find (Kruskal) encaja mejor con esa forma. |
Código de Prim's Algorithm
Una implementación limpia y ejecutable de Prim's Algorithm en Python, JavaScript, Java, C++, C. Elige un lenguaje, copia el código o ábrelo ya cargado en el Playground de Coddy.
Código de Prim's Algorithm en Python
1import heapq2
3
4def prim(graph, start):5 visited = {start}6 heap = [(w, start, v) for v, w in graph[start]]7 heapq.heapify(heap)8 mst, total = [], 09 while heap and len(visited) < len(graph):10 w, u, v = heapq.heappop(heap)11 if v in visited:12 continue13 visited.add(v)14 mst.append((u, v, w))15 total += w16 # Offer the new node's edges to the frontier17 for neighbor, weight in graph[v]:18 if neighbor not in visited:19 heapq.heappush(heap, (weight, v, neighbor))20 return mst, total21
22
23graph = {24 "A": [("B", 4), ("C", 1)],25 "B": [("A", 4), ("C", 3), ("D", 2)],26 "C": [("A", 1), ("B", 3), ("D", 5)],27 "D": [("B", 2), ("C", 5), ("E", 7)],28 "E": [("D", 7)],29}30
31mst, total = prim(graph, "A")32for u, v, w in mst:33 print(f"{u} - {v} (weight {w})")34print("Total MST weight:", total)Código de Prim's Algorithm en JavaScript
1const graph = {2 A: { B: 4, C: 2 },3 B: { A: 4, C: 1, D: 5 },4 C: { A: 2, B: 1, D: 8, E: 10 },5 D: { B: 5, C: 8, E: 2 },6 E: { C: 10, D: 2 },7};8
9function prim(start) {10 const inTree = new Set([start]);11 const mst = [];12 let total = 0;13 while (inTree.size < Object.keys(graph).length) {14 // JS has no built-in heap: scan all crossing edges for the cheapest15 let best = null;16 for (const u of inTree) {17 for (const [v, w] of Object.entries(graph[u])) {18 if (!inTree.has(v) && (best === null || w < best[2])) {19 best = [u, v, w];20 }21 }22 }23 const [u, v, w] = best;24 inTree.add(v);25 mst.push(`${u}-${v} (${w})`);26 total += w;27 }28 return { mst, total };29}30
31const { mst, total } = prim("A");32console.log("MST edges:", mst.join(", "));33console.log("Total weight:", total);Código de Prim's Algorithm en Java
1import java.util.ArrayList;2import java.util.List;3import java.util.PriorityQueue;4
5public class Main {6 public static void main(String[] args) {7 int n = 6;8 List<List<int[]>> adj = new ArrayList<>();9 for (int i = 0; i < n; i++) adj.add(new ArrayList<>());10 int[][] edges = {11 {0, 1, 4}, {0, 2, 3}, {1, 2, 1}, {1, 3, 2},12 {2, 3, 4}, {3, 4, 2}, {4, 5, 6}, {2, 5, 7}13 };14 for (int[] e : edges) {15 adj.get(e[0]).add(new int[]{e[1], e[2]});16 adj.get(e[1]).add(new int[]{e[0], e[2]});17 }18
19 boolean[] inMst = new boolean[n];20 // Always grow the tree along the cheapest crossing edge21 PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]);22 pq.add(new int[]{0, 0}); // {node, edge weight}23 int total = 0, taken = 0;24 while (!pq.isEmpty() && taken < n) {25 int[] cur = pq.poll();26 if (inMst[cur[0]]) continue;27 inMst[cur[0]] = true;28 total += cur[1];29 taken++;30 for (int[] edge : adj.get(cur[0])) {31 if (!inMst[edge[0]]) pq.add(edge);32 }33 }34 System.out.println("MST nodes reached: " + taken);35 System.out.println("MST total weight: " + total);36 }37}Código de Prim's Algorithm en C++
1#include <iostream>2#include <queue>3#include <vector>4
5int main() {6 // Undirected weighted graph: adj[u] = {(neighbor, weight), ...}7 std::vector<std::vector<std::pair<int, int>>> adj = {8 {{1, 2}, {3, 6}}, // 09 {{0, 2}, {2, 3}, {3, 8}, {4, 5}}, // 110 {{1, 3}, {4, 7}}, // 211 {{0, 6}, {1, 8}}, // 312 {{1, 5}, {2, 7}}, // 413 };14 int n = static_cast<int>(adj.size());15 std::vector<bool> inMST(n, false);16 using State = std::pair<int, int>; // (edge weight, node)17 std::priority_queue<State, std::vector<State>, std::greater<State>> pq;18 pq.push({0, 0});19 int total = 0, picked = 0;20 // Always grow the tree along the cheapest crossing edge21 while (!pq.empty() && picked < n) {22 auto [w, u] = pq.top();23 pq.pop();24 if (inMST[u]) continue;25 inMST[u] = true;26 total += w;27 ++picked;28 std::cout << "Add node " << u << " (edge weight " << w << ")\n";29 for (auto [v, weight] : adj[u]) {30 if (!inMST[v]) pq.push({weight, v});31 }32 }33 std::cout << "MST total weight: " << total << "\n";34 return 0;35}Código de Prim's Algorithm en C
1#include <stdbool.h>2#include <stdio.h>3
4#define N 55#define INF 10000000006
7int main(void) {8 // Undirected weighted graph: w[u][v] = 0 means no edge9 int w[N][N] = {10 {0, 2, 0, 6, 0},11 {2, 0, 3, 8, 5},12 {0, 3, 0, 0, 7},13 {6, 8, 0, 0, 0},14 {0, 5, 7, 0, 0},15 };16 int key[N], parent[N];17 bool inMST[N] = {false};18 for (int v = 0; v < N; v++) {19 key[v] = INF;20 parent[v] = -1;21 }22 key[0] = 0;23 int total = 0;24 // O(V^2) scan for the cheapest crossing edge (no heap needed here)25 for (int iter = 0; iter < N; iter++) {26 int u = -1;27 for (int v = 0; v < N; v++) {28 if (!inMST[v] && (u == -1 || key[v] < key[u])) u = v;29 }30 inMST[u] = true;31 total += key[u];32 if (parent[u] != -1) {33 printf("Add edge %d - %d (weight %d)\n", parent[u], u, key[u]);34 }35 for (int v = 0; v < N; v++) {36 if (w[u][v] > 0 && !inMST[v] && w[u][v] < key[v]) {37 key[v] = w[u][v];38 parent[v] = u;39 }40 }41 }42 printf("MST total weight: %d\n", total);43 return 0;44}Preguntas frecuentes sobre el algoritmo de Prim
¿Cuál es la complejidad temporal del algoritmo de Prim?
O(E log V). Una versión más simple con matriz de adyacencia que busca la arista de cruce mínima en cada paso es O(V²), que puede ser más rápida en grafos densos. Ambas usan espacio O(V + E).¿Cuál es la diferencia entre los algoritmos de Prim y Kruskal?
¿El algoritmo de Prim siempre encuentra el MST óptimo?
¿Cuándo debería usar el algoritmo de Prim en lugar del de Kruskal?
O(V²) evita ordenar las E aristas y hace crecer un único árbol desde un nodo inicial. Kruskal destaca en grafos dispersos donde ordenar la lista de aristas es barato y union-find mantiene rápidas las comprobaciones de ciclos. Ambos producen un MST correcto, así que la elección depende sobre todo de la densidad de aristas y de qué estructuras de datos ya tienes.