31. Algoritmo de Prim

Prim construye un árbol de expansión mínima haciendo crecer una única región conectada. En cada paso incorpora la arista más barata que une el árbol actual con un vértice exterior.

31.1 Introducción

El algoritmo de Prim resuelve el problema del árbol de expansión mínima en grafos no dirigidos, conectados y ponderados. Comienza desde cualquier vértice y agrega una conexión por vez.

La selección siempre conserva un único árbol: cada nueva arista conecta un vértice incluido con otro que todavía se encuentra fuera.

31.2 Idea fundamental

  1. Elegir cualquier vértice inicial.
  2. Considerar las aristas que salen del árbol actual.
  3. Seleccionar la de menor peso.
  4. Incorporar la arista y el nuevo vértice.
  5. Repetir hasta incluir todos los vértices.
árbol actual → arista mínima de la frontera → nuevo vértice

31.3 La frontera

La frontera está formada por las aristas con exactamente un extremo dentro del árbol actual.

u ∈ incluidos y v ∉ incluidos ⇒ {u,v} es candidata

Las aristas con ambos extremos dentro se descartan porque crearían un ciclo. Las que tienen ambos extremos fuera todavía no conectan con el árbol.

31.4 Relación con la propiedad del corte

Los vértices incluidos y los no incluidos forman un corte. La propiedad del corte garantiza que una arista de peso mínimo que lo cruza es segura para algún MST.

Prim aplica esta propiedad repetidamente. Después de incorporar la arista segura, el corte cambia y se calcula una nueva frontera.

31.5 El vértice inicial

Prim puede comenzar en cualquier vértice. El costo mínimo final será el mismo, aunque el orden de selección y el MST obtenido pueden cambiar cuando existen empates.

Cambiar el origen puede cambiar el recorrido, pero no el costo óptimo.

31.6 Simulación interactiva paso a paso

Selecciona un nodo como origen antes de iniciar. Avanza para observar el árbol, la frontera ordenada por peso y la arista mínima elegida en cada corte.

Vértices incluidos

Frontera ordenada

vacía

Acción actual

Selecciona el origen.
Origen seleccionado: A.
OrigenA
Vértices0 / 7
Aristas del árbol0 / 6
Costo acumulado0

Celeste indica el árbol construido, verde las aristas de frontera y rosa la arista elegida en el paso actual.

31.7 Ejemplo conceptual

Si el árbol contiene A y B, y la frontera ofrece A—C con peso 5, B—C con peso 2 y B—D con peso 4, Prim elige B—C.

Después de incorporar C se eliminan de la frontera las conexiones que ahora tienen ambos extremos dentro y se agregan las aristas que salen de C hacia vértices exteriores.

31.8 Implementación sencilla O(V²)

Una versión matricial mantiene para cada vértice exterior el costo de su conexión más barata al árbol.

function primMatriz(matriz, origen = 0) {
  const n = matriz.length;
  const incluido = Array(n).fill(false);
  const clave = Array(n).fill(Infinity);
  const padre = Array(n).fill(-1);
  clave[origen] = 0;

  for (let paso = 0; paso < n; paso++) {
    let u = -1;
    for (let v = 0; v < n; v++) {
      if (!incluido[v] && (u === -1 || clave[v] < clave[u])) u = v;
    }
    if (u === -1 || clave[u] === Infinity) break;
    incluido[u] = true;

    for (let v = 0; v < n; v++) {
      const peso = matriz[u][v];
      if (!incluido[v] && peso !== null && peso < clave[v]) {
        clave[v] = peso;
        padre[v] = u;
      }
    }
  }
  return padre;
}

31.9 Cola de prioridad

Con listas de adyacencia, una cola de prioridad guarda las mejores conexiones conocidas hacia los vértices exteriores. La entrada de menor peso se extrae primero.

Cuando aparecen varias entradas para un mismo vértice, las desactualizadas se descartan si el nodo ya fue incluido.

31.10 Implementación con cola de prioridad

function prim(grafo, origen, cola) {
  const incluidos = new Set();
  const aristasMST = [];
  let costo = 0;
  cola.insertar({ peso: 0, desde: null, hasta: origen });

  while (!cola.estaVacia()) {
    const arista = cola.extraerMinimo();
    if (incluidos.has(arista.hasta)) continue;

    incluidos.add(arista.hasta);
    if (arista.desde !== null) {
      aristasMST.push(arista);
      costo += arista.peso;
    }

    for (const vecino of grafo[arista.hasta]) {
      if (!incluidos.has(vecino.destino)) {
        cola.insertar({
          peso: vecino.peso,
          desde: arista.hasta,
          hasta: vecino.destino
        });
      }
    }
  }
  return { aristasMST, costo, completo: incluidos.size === Object.keys(grafo).length };
}

31.11 Por qué no crea ciclos

Cada arista elegida tiene exactamente un extremo dentro del árbol. El otro extremo es un vértice nuevo.

Agregar un vértice nuevo mediante una sola arista no puede cerrar un ciclo.

Si ambos extremos ya estuvieran incluidos, la arista se descartaría.

31.12 Grafos desconectados

Desde un origen, Prim solo puede cubrir su componente. Si la cola se vacía antes de incluir todos los vértices, el grafo no tiene un árbol generador global.

Para obtener un bosque mínimo se reinicia Prim desde cada vértice todavía no incluido.

31.13 Empates

Si varias aristas de la frontera comparten el peso mínimo, cualquiera es segura. La elección puede producir árboles diferentes con el mismo costo.

Para obtener resultados reproducibles se puede desempatar por identificador del origen, destino o posición original de la arista.

31.14 Prim y Dijkstra

Ambos hacen crecer un conjunto desde un origen y utilizan una cola de prioridad, pero sus prioridades representan objetivos diferentes.

AlgoritmoPrioridadObjetivo
PrimPeso de la arista que conecta al árbolMinimizar costo total de conexión
DijkstraDistancia total desde el origenMinimizar cada ruta desde el origen

31.15 Prim y Kruskal

CaracterísticaPrimKruskal
Estructura que creceUn árbolUn bosque
SelecciónMínima de la fronteraMínima global que no crea ciclo
Estructura auxiliarCola de prioridadUnion-Find
Suele convenirGrafos densosGrafos dispersos o lista de aristas

31.16 Complejidad

RepresentaciónImplementaciónTiempo
Matriz de adyacenciaBúsqueda linealO(V²)
Lista de adyacenciaMontículo binarioO(E log V)

La versión matricial puede ser excelente en grafos densos, donde E se aproxima a V².

31.17 Aplicaciones

  • Diseño incremental de redes físicas.
  • Cableado y conexión de instalaciones.
  • Redes densas almacenadas mediante matrices.
  • Construcción de una infraestructura desde un punto inicial.
  • Subrutinas de problemas de optimización y aproximación.

31.18 Errores comunes

  • Elegir la arista más barata del grafo aunque no cruce la frontera.
  • Agregar una arista cuyos dos extremos ya están incluidos.
  • Usar la distancia acumulada como prioridad y convertirlo en Dijkstra.
  • No descartar entradas obsoletas de la cola.
  • Suponer que el origen cambia el costo mínimo.
  • No detectar que el grafo está desconectado.
  • Aplicar Prim directamente a un grafo dirigido.

31.19 Qué debes recordar de este tema

  • Prim hace crecer un único árbol.
  • En cada paso elige la arista mínima de la frontera.
  • La propiedad del corte garantiza que la elección es segura.
  • Puede comenzar desde cualquier vértice.
  • Nunca crea ciclos porque incorpora un vértice exterior.
  • Con montículo y listas trabaja en O(E log V).

31.20 Conclusión

Prim convierte la propiedad del corte en un procedimiento concreto: mantiene un árbol conectado y lo amplía mediante la opción más barata disponible. Su implementación con cola de prioridad resulta eficiente y natural.

En el próximo tema estudiaremos Kruskal, que ordena globalmente las aristas y une componentes sin crear ciclos.