Implementacion Prim
Introducción teórica¶
El algoritmo de Prim construye un árbol de cobertura mínimo (MST) para un grafo conexo y ponderado. En cada paso, mantiene un corte fijo: el conjunto \(V_T\) de vértices ya incluidos en el árbol y su complemento \(V \setminus V_T\). La arista de peso mínimo que cruza este corte (arista ligera) es siempre segura, según la propiedad de corte, y se añade al MST.
Propiedad de corte (cut property):
Sea \(A\) un subconjunto de aristas de algún MST y sea \((S, V \setminus S)\) un corte que respeta a \(A\) (ninguna arista de \(A\) cruza el corte). Si \(e\) es la arista de peso mínimo entre todas las que cruzan el corte, entonces \(e\) también pertenece a algún MST.
En Prim, al inicio \(V_T = \{r\}\) (raíz) y \(A = \emptyset\). El corte \((V_T, V \setminus V_T)\) respeta a \(A\). La arista ligera se añade, extendiendo \(V_T\) y manteniendo la propiedad. Esto se repite hasta que \(V_T\) contiene todos los vértices.
El algoritmo utiliza una estructura de clave (distancia mínima desde \(V_T\) a cada vértice externo) y padre para reconstruir las aristas.
Código original comentado¶
INFTY = float('inf') # Representa distancia infinita
def extraer_minimo(clave, en_mst):
"""Retorna el vértice no incluido en MST con la clave mínima."""
minimo = None
mejor = INFTY
for v in clave:
if not en_mst[v] and clave[v] < mejor:
mejor = clave[v]
minimo = v
return minimo
def prim(grafo, raiz):
"""Ejecuta el algoritmo de Prim sobre 'grafo' (lista de adyacencia) iniciando en 'raiz'."""
clave = {} # distancia mínima desde el árbol actual a cada vértice
padre = {} # vértice predecesor en el MST
en_mst = {} # marca si el vértice ya fue incluido en el árbol
# Inicialización: todas las claves a infinito, padre a None, no incluidos
for v in grafo:
clave[v] = INFTY
padre[v] = None
en_mst[v] = False
clave[raiz] = 0 # la raíz tiene distancia 0
peso = 0
restantes = len(grafo)
while restantes > 0:
u = extraer_minimo(clave, en_mst) # vértice con clave mínima fuera del MST
if u is None:
restantes = 0 # grafo no conexo, no hay más vértices alcanzables
else:
en_mst[u] = True
if padre[u] is not None: # saltamos la raíz (sin arista entrante)
peso = peso + clave[u]
# Recorremos los vecinos de u
i = 0
while i < len(grafo[u]):
v, w = grafo[u][i]
# Si v no está en el MST y el peso w es menor que su clave actual, actualizamos
if not en_mst[v] and w < clave[v]:
clave[v] = w
padre[v] = u
i = i + 1
restantes = restantes - 1
return padre, peso
if __name__ == '__main__':
# Grafo de ejemplo: lista de adyacencia (vértice: [(vecino, peso), ...])
G = {
0: [(1, 7), (3, 5)],
1: [(0, 7), (2, 8), (3, 9), (4, 7)],
2: [(1, 8), (4, 5)],
3: [(0, 5), (1, 9), (4, 15), (5, 6)],
4: [(1, 7), (2, 5), (3, 15), (5, 8), (6, 9)],
5: [(3, 6), (4, 8), (6, 11)],
6: [(4, 9), (5, 11)],
}
padre, peso = prim(G, 0)
print('peso del MST =', peso)
print('aristas del MST (padre[v], v):')
for v in padre:
if padre[v] is not None:
print(' (', padre[v], ',', v, ')')
Tres ejemplos paso a paso que explican la propiedad de corte en Prim¶
Ejemplo 1: Grafo pequeño con 3 vértices¶
Grafo:
- Vértices: \(A, B, C\)
- Aristas: \(A-B\) (2), \(A-C\) (4), \(B-C\) (3)
Proceso:
- Inicio: \(V_T = \{A\}\), corte \((\{A\}, \{B,C\})\), aristas que cruzan: \(A-B\) (2), \(A-C\) (4). La arista ligera es \(A-B\) (peso 2). \(A\) respeta el corte (\(A = \emptyset\)). Se añade \(A-B\).
- Ahora \(V_T = \{A, B\}\), corte \((\{A,B\}, \{C\})\), aristas que cruzan: \(A-C\) (4), \(B-C\) (3). La arista ligera es \(B-C\) (peso 3). Se añade \(B-C\).
- \(V_T = \{A, B, C\}\), MST completo. Peso total: \(2 + 3 = 5\).
Observación: En cada paso, la arista ligera del corte actual es exactamente la que Prim selecciona.
Ejemplo 2: Grafo con empate de pesos¶
Grafo:
- Vértices: \(X, Y, Z, W\)
- Aristas: \(X-Y\) (1), \(X-Z\) (1), \(Y-Z\) (2), \(Z-W\) (3), \(Y-W\) (4)
Proceso:
- Inicio: \(V_T = \{X\}\), corte \((\{X\}, \{Y,Z,W\})\), aristas que cruzan: \(X-Y\) (1), \(X-Z\) (1). Hay dos aristas ligeras (peso 1). Cualquiera es segura. Supongamos que se elige \(X-Y\).
- \(V_T = \{X, Y\}\), corte \((\{X,Y\}, \{Z,W\})\), aristas que cruzan: \(X-Z\) (1), \(Y-Z\) (2), \(Y-W\) (4). La ligera es \(X-Z\) (peso 1). Se añade.
- \(V_T = \{X, Y, Z\}\), corte \((\{X,Y,Z\}, \{W\})\), aristas que cruzan: \(Z-W\) (3), \(Y-W\) (4). La ligera es \(Z-W\) (peso 3). Se añade.
- MST: aristas \(\{(X-Y,1), (X-Z,1), (Z-W,3)\}\), peso total 5.
Observación: Aunque hubo empate en el primer corte, la propiedad de corte sigue garantizando que ambas aristas ligeras son seguras.
Ejemplo 3: Grafo más grande (el del código de ejemplo, paso a paso)¶
Grafo:
(Vértices 0 a 6, aristas según el código \(G\))
Proceso detallado (raíz = 0):
- \(V_T = \{0\}\), corte \((\{0\}, \{1,\dots,6\})\). Aristas que cruzan: \(0-1\) (7), \(0-3\) (5). Ligera: \(0-3\) (5). Se añade, \(V_T = \{0,3\}\).
- Corte \((\{0,3\}, \{1,2,4,5,6\})\). Aristas: \(0-1\) (7), \(3-1\) (9), \(3-4\) (15), \(3-5\) (6), \(1\) desde \(0\) también. La más ligera es \(3-5\) (6). Se añade, \(V_T = \{0,3,5\}\).
- Corte \((\{0,3,5\}, \{1,2,4,6\})\). Aristas: \(0-1\) (7), \(3-1\) (9), \(5-4\) (8), \(5-6\) (11), \(3-4\) (15). Ligera: \(0-1\) (7) o \(5-4\) (8) – en realidad \(0-1\)=7 es más ligera que \(5-4\)=8. Se añade \(0-1\), \(V_T = \{0,1,3,5\}\).
- Corte \((\{0,1,3,5\}, \{2,4,6\})\). Aristas: \(1-2\) (8), \(1-4\) (7), \(5-4\) (8), \(5-6\) (11), \(3-4\) (15). Ligera: \(1-4\) (7). Se añade, \(V_T = \{0,1,3,4,5\}\).
- Corte \((\{0,1,3,4,5\}, \{2,6\})\). Aristas: \(1-2\) (8), \(4-2\) (5), \(4-6\) (9), \(5-6\) (11). Ligera: \(4-2\) (5). Se añade, \(V_T = \{0,1,2,3,4,5\}\).
- Corte \((\{0,1,2,3,4,5\}, \{6\})\). Aristas: \(4-6\) (9), \(5-6\) (11). Ligera: \(4-6\) (9). Se añade, \(V_T = \{0,1,2,3,4,5,6\}\).
MST obtenido: aristas \((0,3,5)\), \((3,5,6)\), \((0,1,7)\), \((1,4,7)\), \((4,2,5)\), \((4,6,9)\) – peso total \(5+6+7+7+5+9 = 39\).
(Nota: el resultado del código es el mismo que se calcula manualmente; el orden de selección puede variar si hay empates, pero el conjunto final es un MST.)
Tabla resumen de conceptos¶
| Concepto | Definición | Comentarios adicionales |
|---|---|---|
| Algoritmo de Prim | Construye el MST comenzando desde un vértice y añadiendo siempre la arista más ligera del corte \((V_T, V \setminus V_T)\). | Complejidad \(O(V^2)\) con búsqueda lineal; \(O(E \log V)\) con cola de prioridad. |
| Propiedad de corte | Si \(A\) está contenido en algún MST y \(e\) es la arista más ligera que cruza un corte que respeta \(A\), entonces \(e\) es segura. | Garantiza la corrección de Prim (y de Kruskal). |
| Corte en Prim | Siempre es \((V_T, V \setminus V_T)\). Responde a \(A\) automáticamente porque \(A\) son aristas internas a \(V_T\), nunca lo cruzan. | La raíz inicial es un vértice cualquiera; el resultado es independiente de la raíz. |
| Clave | Distancia mínima conocida desde un vértice fuera de \(V_T\) hasta cualquier vértice en \(V_T\). | Se actualiza al añadir un nuevo vértice al MST. |
| Función extraer_minimo | Busca linealmente el vértice no incluido con clave más pequeña. | Implementación simple; puede optimizarse con un heap. |
| MST | Árbol de cobertura mínimo: subconjunto de aristas que conecta todo el grafo con peso total mínimo y sin ciclos. | Puede haber varios si hay pesos repetidos. |