Algorithme de Prim
Dernière mise à jour
L'algorithme de Prim construit un arbre couvrant minimal (MST) - l'ensemble d'arêtes le moins cher reliant tous les nœuds sans cycle - en faisant croître un seul arbre vers l'extérieur à partir d'un nœud de départ. À chaque étape, il examine toutes les arêtes qui traversent de l'arbre vers un nœud extérieur et ajoute la moins chère. Appuyez sur lecture ci-dessus pour voir l'arbre s'étendre, en prenant toujours l'arête de plus faible poids qui atteint un nouveau nœud.
Comme il choisit toujours l'arête de coupe minimale, chaque ajout est sûr (garanti de faire partie d'un MST). Avec une file de priorité basée sur un tas binaire indexée par l'arête la moins chère vers chaque nœud extérieur, Prim s'exécute en O(E log V). Il contraste avec Kruskal, qui trie toutes les arêtes globalement au lieu de faire croître un seul arbre connexe.
Complexité en temps et en espace
| Implémentation | Complexité | Notes |
|---|---|---|
| Tas binaire | O(E log V) | File de priorité des arêtes de coupe |
| Matrice d'adjacence | O(V²) | Plus simple ; adaptée aux graphes denses |
| Espace | O(V + E) | Appartenance à l'arbre + file de priorité |
| Idéal pour | Graphes denses | Croît à partir d'un seul nœud de départ |
Étape par étape
| Étape | Ce qui se passe |
|---|---|
| 1 | Démarrez l'arbre avec un seul nœud quelconque. |
| 2 | Examinez toutes les arêtes qui traversent de l'arbre vers un nœud extérieur. |
| 3 | Choisissez l'arête de coupe de plus faible poids. |
| 4 | Ajoutez cette arête et son nouveau nœud à l'arbre. |
| 5 | Répétez jusqu'à ce que tous les nœuds soient dans l'arbre. |
Exemple résolu
Construction du MST d'un graphe à 4 nœuds avec les arêtes A-B=1, A-C=3, B-C=2, B-D=4, C-D=5, en partant de A :
| Étape | Arbre | Arêtes de coupe | Arête choisie |
|---|---|---|---|
| 1 | {A} | A-B=1, A-C=3 | A-B (poids 1) |
| 2 | {A, B} | A-C=3, B-C=2, B-D=4 | B-C (poids 2) |
| 3 | {A, B, C} | B-D=4, C-D=5 | B-D (poids 4) |
| 4 | {A, B, C, D} | aucune - tous les nœuds dans l'arbre | terminé : poids du MST 1+2+4 = 7 |
Quand utiliser l'algorithme de Prim
| À utiliser quand | À éviter quand |
|---|---|
| Vous avez besoin d'un arbre couvrant minimal d'un graphe connexe, non orienté et pondéré. | Le graphe est orienté ou vous avez besoin des plus courts chemins - utilisez Dijkstra ou Bellman-Ford. |
Le graphe est dense (E proche de V²) ; la forme matricielle O(V²) est simple et rapide. | Le graphe est creux et les arêtes sont déjà triées ou faciles à trier - Kruskal est souvent plus simple. |
| Vous voulez que l'arbre croisse d'une région vers l'extérieur (par ex. disposition incrémentale d'un réseau). | Le graphe est déconnecté - Prim ne couvre qu'un composant ; vous avez besoin d'une forêt couvrante minimale. |
| Vous disposez déjà d'une structure d'adjacence et d'une file de priorité. | Vous devez détecter des cycles sur un ensemble global d'arêtes - union-find (Kruskal) convient mieux à ce cas. |
Code de Prim's Algorithm
Une implémentation propre et exécutable de Prim's Algorithm en Python, JavaScript, Java, C++, C. Choisissez un langage, copiez le code ou ouvrez-le préchargé dans le Playground Coddy.
Code 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)Code 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);Code 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}Code 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}Code 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}FAQ sur l'algorithme de Prim
Quelle est la complexité temporelle de l'algorithme de Prim ?
O(E log V). Une version plus simple avec matrice d'adjacence qui balaie l'arête de coupe minimale à chaque étape est en O(V²), ce qui peut être plus rapide sur les graphes denses. Les deux utilisent un espace O(V + E).Quelle est la différence entre les algorithmes de Prim et de Kruskal ?
L'algorithme de Prim trouve-t-il toujours le MST optimal ?
Quand devrais-je utiliser l'algorithme de Prim plutôt que celui de Kruskal ?
O(V²) évite de trier les E arêtes et fait croître un seul arbre à partir d'un nœud de départ. Kruskal brille sur les graphes creux où trier la liste d'arêtes est peu coûteux et où union-find garde les vérifications de cycles rapides. Les deux produisent un MST correct, donc le choix dépend surtout de la densité des arêtes et des structures de données dont vous disposez déjà.