Parcourir, ordonner, trouver le plus court chemin et l’arbre couvrant minimal
Département de Mathématiques et Informatique, Faculté des Sciences et Techniques, Université Cheikh Anta Diop de Dakar
Ce que la séance construit
Les graphes représentent des relations : des villes reliées par des routes, des cours liés par leurs prérequis. La séance construit leurs deux représentations, les parcours en largeur et en profondeur, le tri topologique, les plus courts chemins de Dijkstra et l’arbre couvrant minimal de Kruskal, chacun montré sur le code Java du cours, pas à pas.
D’où l’on vient, où l’on va
Amont. Les files et les piles (Séance 1) ; l’analyse des coûts (Séance 2) ; les listes des cases d’une table de hachage (Séance 3) ; les parcours préfixe et postfixe des arbres, et le tas binaire comme file de priorité (Séance 4).
Aval. La Séance 6 traite les ensembles disjoints (Union-Find), qui remplacent le test de cycle de Kruskal par une opération presque constante, puis les algorithmes de tri.
Un réseau routier est fait de relations : huit villes du centre et du sud, dix tronçons.
Graphe
Un graphe \(G=(V,E)\) : un ensemble \(V\) de sommets et un ensemble \(E\) d’arêtes, chacune reliant deux sommets. Ici \(|V|=8\) villes, \(|E|=10\) routes ; le poids d’une arête est sa longueur en km.
Lecture. Les numéros 0 à 7, en gris, sont ceux du code Java ; les noms ne servent qu’à l’affichage.
Arêtes et degrés
Somme des degrés
\(\sum_{v\in V}\deg(v)=2\,|E|\) : chaque arête compte pour ses deux extrémités.
Chemins
Lecture. \(\deg(\text{Kolda})=4\) ; \(3+2+3+1+2+4+2+3=20=2\times10\) ; Kolda–Sédhiou–Ziguinchor–Kolda est un cycle.
Matrice : a[v][w] vaut 1 si l’arête \(v\)–\(w\) existe, symétrique pour un graphe non orienté. Listes : adj[v] contient les voisins de \(v\), dans l’ordre des ajouts.
Lecture. La ligne 5 (Kolda) de la matrice contient quatre 1, comme la liste 5 : le degré. La matrice range aussi les 44 zéros.
Grapheprivate final int V; // sommets 0..V-1
private final boolean oriente;
private int E; // aretes (ou arcs)
private final List<Integer>[] adj; // adj[v] : voisins de v
public void addEdge(int v, int w) {
adj[v].add(w); // ajout en queue
if (!oriente) adj[w].add(v); // non oriente : aussi dans adj[w]
E++;
}
public List<Integer> adj(int v) {
return adj[v];
}Lecture. Constructeur non montré : \(V\) listes vides. Non orienté, chaque arête entre dans deux listes : \(2E\) entrées, ajoutées en queue. degre(v) rend adj[v].size().
Sortie \(\rightarrow\)
8 10
[0, 4, 6, 7]
| opération | matrice | listes |
|---|---|---|
| mémoire | \(\Theta(V^2)\) | \(\Theta(V+E)\) |
| l’arête \(v\)–\(w\) existe-t-elle ? | \(\Theta(1)\) | \(\mathcal{O}(\deg v)\) |
| parcourir les voisins de \(v\) | \(\Theta(V)\) | \(\Theta(\deg v)\) |
| parcourir tout le graphe | \(\Theta(V^2)\) | \(\Theta(V+E)\) |
Le choix suit la densité
Un réseau routier est creux : \(E\) est de l’ordre de \(V\). Pour \(V=10\,000\) villes et \(E=30\,000\) routes, la matrice compte \(10^8\) cases, les listes \(60\,000\) entrées. La matrice convient à un graphe dense (\(E\) proche de \(V^2\)) ou à des tests d’arête répétés.
Lecture. Le cours utilise les listes, comme Sedgewick et Wayne (§ 4.1).
Random r = new Random(221);
for (int V : new int[] {1000, 2000, 4000}) {
Graphe g = new Graphe(V, false);
GrapheMatrice m = new GrapheMatrice(V);
for (int[] a : Generateur.aretes(V, 4 * V, r)) { // E = 4V
g.addEdge(a[0], a[1]);
m.addEdge(a[0], a[1]);
}
ParcoursLargeur p = new ParcoursLargeur(g, 0);
long cases = m.parcoursLargeur(0);
System.out.printf("V = %d, E = %d : listes %d lues, matrice %d lues%n",
V, g.E(), p.examines, cases);
}Résultat \(\rightarrow\)
V = 1000, E = 4000 : listes 8000 lues, matrice 1000000 lues
V = 2000, E = 8000 : listes 16000 lues, matrice 4000000 lues
V = 4000, E = 16000 : listes 32000 lues, matrice 16000000 lues
Lecture. \(E=4V\) : les listes lisent \(2E\) entrées, la matrice \(V^2\) cases : 125 fois plus à \(V=1\,000\), 500 fois à \(4\,000\).
Un graphe de six sommets
Arêtes, dans cet ordre : A–B, A–C, B–C, B–D, C–E, D–E, D–F, E–F.
Correction
| sommet | A | B | C | D | E | F |
|---|---|---|---|---|---|---|
| degré | 2 | 3 | 3 | 3 | 3 | 2 |
Parcours en largeur {(breadth-first search)}
Depuis une source \(s\) : marquer \(s\) et la mettre dans une file. Tant que la file n’est pas vide, en retirer le sommet \(v\) de tête, puis marquer chaque voisin \(w\) non marqué, avec \(\texttt{distTo[w]}=\texttt{distTo[v]}+1\) et \(\texttt{edgeTo[w]}=v\), et le mettre en queue.
Invariant de distTo
La file ne contient que des sommets à distance \(d\), suivis de sommets à distance \(d+1\) : les sommets sortent par distance croissante, et distTo[v] est le plus petit nombre d’arêtes d’un chemin de \(s\) à \(v\). Chaque sommet entre une fois dans la file et chaque liste est lue une fois : \(\Theta(V+E)\).
Lecture. La file est l’ArrayDeque du JDK (Séance 1, § 6.2) : add en queue, remove en tête. Les arêtes edgeTo forment l’arbre de parcours.
Sortie \(\rightarrow\)
KaolackKaolack KaffrineKaolack Kaffrine ZiguinchorKaolack Kaffrine Ziguinchor KoldaKaolack Kaffrine Ziguinchor Kolda TambacoundaKaolack Kaffrine Ziguinchor Kolda Tambacounda SedhiouKaolack Kaffrine Ziguinchor Kolda Tambacounda Sedhiou VelingaraKaolack Kaffrine Ziguinchor Kolda Tambacounda Sedhiou Velingara Kedougou
Lecture. Lignes 1–3 : Kaolack (0) est marqué et entre dans la file ; en gras, distTo : distTo[0] vaut 0.
Lecture. Lignes 5–12 : Kaolack sort ; ses voisins 1, 7, 5 sont marqués à distance 1 et entrent en file.
Lecture. Lignes 5–12 : Kaffrine sort ; Tambacounda est marqué à distance 2 et entre en file.
Lecture. Lignes 5–12 : Ziguinchor sort ; Sédhiou est marqué à distance 2 et entre en file.
Lecture. Lignes 5–12 : Kolda sort ; Vélingara est marqué à distance 2 et entre en file.
Lecture. Lignes 5–12 : Tambacounda sort ; Kédougou est marqué à distance 3 et entre en file.
Lecture. Lignes 5–8 : Sédhiou sort ; tous ses voisins sont déjà marqués : la file ne reçoit rien.
Lecture. Lignes 5–8 : Vélingara sort ; tous ses voisins sont déjà marqués : la file ne reçoit rien.
Lecture. Lignes 5–8 : Kédougou (distance 3) sort, la file est vide ; en foncé, l’arbre de parcours.
public List<Integer> chemin(int v) {
if (!marque[v]) return null;
LinkedList<Integer> c = new LinkedList<>();
for (int x = v; x != s; x = edgeTo[x]) c.addFirst(x);
c.addFirst(s); // puis la source, en tete
return c;
}
// dans main :
String[] nom = Reseau.VILLES;
ParcoursLargeur p = new ParcoursLargeur(Reseau.routes(), 0);
for (int v : new int[] {3, 4}) {
System.out.println(nom[v] + " : " + p.distance(v) + " troncons, "
+ Reseau.noms(p.chemin(v), nom));
}Résultat \(\rightarrow\)
Kedougou : 3 troncons, Kaolack Kaffrine Tambacounda Kedougou
Velingara : 2 troncons, Kaolack Kolda Velingara
Lecture. chemin(v) remonte l’arbre de parcours de \(v\) à \(s\) par edgeTo : Kédougou à 3 tronçons, Vélingara à 2.
Le graphe de six sommets, depuis A
Listes : A : B, C ; B : A, C, D ; C : A, B, E ; D : B, E, F ; E : C, D, F ; F : D, E.
distTo et l’arbre de parcours.
| tour | sort | marqués | file après |
|---|---|---|---|
| 1 | A | B, C | B C |
| 2 | B | D | C D |
| 3 | C | E | D E |
| 4 | D | F | E F |
| 5 | E | --- | F |
| 6 | F | --- | vide |
Correction
Parcours en profondeur {(depth-first search)}
dfs(v) marque \(v\), puis appelle dfs(w) sur chaque voisin \(w\) non marqué, avant de passer au voisin suivant : le parcours s’enfonce aussi loin que possible, puis revient en arrière. La pile est celle des appels récursifs.
Deux ordres de visite
préfixe : \(v\) à l’entrée de dfs(v) ; postfixe : \(v\) à la sortie, après tous les sommets atteints depuis lui — comme pour les arbres de la Séance 4. Chaque sommet est marqué une fois, chaque liste lue une fois : \(\Theta(V+E)\).
Lecture. Le parcours en profondeur sert à trouver les composantes connexes, les cycles et, sur un graphe orienté, un tri topologique.
Sortie \(\rightarrow\)
Kaolack Kaffrine Tambacounda Kedougou Velingara Kolda Sedhiou Ziguinchor
Kedougou Ziguinchor Sedhiou Kolda Velingara Tambacounda Kaffrine Kaolack
Lecture. Lignes 10, 2–3 : dfs(0) marque Kaolack et l’ajoute au préfixe ; la pile d’appels contient dfs(0). Les nombres en gras donnent l’ordre préfixe.
Lecture. Lignes 4–5, 2–3 : le premier voisin de Kaolack, 1 (Kaffrine), n’est pas marqué : dfs(1) le marque, avant d’examiner les autres voisins.
Lecture. Lignes 4–5, 2–3 : Kaffrine examine 0 (marqué), puis 2 : dfs(2), Tambacounda.
Lecture. Lignes 4–5, 2–3 : Tambacounda examine 1 (marqué), puis 3 : dfs(3), Kédougou. Le parcours s’enfonce.
Lecture. Lignes 4–5, 7 : Kédougou n’a qu’un voisin, déjà marqué : dfs(3) se termine, 3 entre dans le postfixe et l’appel est dépilé. Retour dans dfs(2).
Lecture. Lignes 4–5, 2–3 : Tambacounda passe à son voisin suivant, 4 : dfs(4), Vélingara.
Lecture. Lignes 4–5, 2–3 : Vélingara examine 2 (marqué), puis 5 : dfs(5), Kolda.
Lecture. Lignes 4–5, 2–3 : Kolda examine 0 et 4 (marqués), puis 6 : dfs(6), Sédhiou.
Lecture. Lignes 4–5, 2–3 : Sédhiou examine 5 (marqué), puis 7 : dfs(7), Ziguinchor. La pile contient 7 appels.
Lecture. Lignes 4–5, 7 : les voisins de Ziguinchor (0, 5, 6) sont tous marqués : dfs(7) se termine, puis dfs(6) ; 7 et 6 entrent dans le postfixe.
Lecture. Lignes 4–5, 7 : en remontant, chaque appel trouve ses voisins restants marqués et se termine : 5, 4, 2, 1, puis 0. Les 7 arêtes rouges et foncées forment l’arbre de parcours.
Lecture. Ligne 11 : l’ordre préfixe, celui des entrées dans dfs : chaque ville est visitée avant ses descendants.
Lecture. Ligne 12 : l’ordre postfixe, celui des sorties : une ville n’y entre qu’après toutes celles atteintes depuis elle. Kaolack, la racine, sort en dernier.
Quoi
Largeur : une file ; les sommets sortent par distance croissante. Profondeur : la pile des appels ; un chemin aussi long que possible, puis le retour.
Pourquoi on les utilise
Les deux coûtent \(\Theta(V+E)\). La largeur donne les plus courts chemins en nombre d’arêtes ; la profondeur donne les ordres préfixe et postfixe, d’où les cycles et le tri topologique.
Quand ça échoue
La largeur compte des tronçons, pas des kilomètres : Kaolack–Kolda–Vélingara a 2 tronçons et 395 km, Kaolack–Kaffrine–Tambacounda–Vélingara 3 tronçons et 376 km. La profondeur récursive sur une chaîne de quelques milliers de sommets dépasse la pile par défaut de la machine virtuelle (StackOverflowError, sauf avec -Xss).
Le même graphe, depuis A
Listes : A : B, C ; B : A, C, D ; C : A, B, E ; D : B, E, F ; E : C, D, F ; F : D, E.
| rang | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| préfixe | A | B | C | E | D | F |
| postfixe | F | D | E | C | B | A |
Correction
Graphe orienté et tri topologique
Un arc \(v\to w\) : le cours \(v\) est un prérequis du cours \(w\). Un tri topologique est un ordre des sommets où tout arc va d’un sommet vers un sommet placé après lui. Il en existe un si et seulement si le graphe est sans cycle (DAG).
Lecture. Algo : algorithmique ; ProgC : programmation C ; Maths : mathématiques discrètes ; Java : programmation Java ; BD : bases de données ; SDA : structures de données ; GL : génie logiciel ; IA : intelligence artificielle.
Sortie \(\rightarrow\)
Maths Algo BD ProgC Java GL SDA IA
Lecture. Lignes 9–10, 2 : \(v=0\) : Algo n’est pas marqué, dfs(0) le marque. Ses successeurs sont 1 (ProgC) et 4 (BD).
Lecture. Lignes 3–4, 2 : le premier successeur, 1 (ProgC), n’est pas marqué : dfs(1).
Lecture. Lignes 3–4, 2 : ProgC a pour successeur 3 (Java) : dfs(3).
Lecture. Lignes 3–4, 2 : Java a pour successeurs 5 (SDA) et 6 (GL) ; le premier, 5, est appelé.
Lecture. Lignes 3–4, 2, 6 : dfs(7) : IA, marqué, n’a aucun successeur, l’appel se termine et 7 est empilé en tête de ordre, le premier.
Lecture. Lignes 2–4, 6 : SDA se termine (5 en tête, devant 7) ; Java passe à 6 (GL), sans successeur, aussitôt empilé.
Lecture. Ligne 6 : Java puis ProgC se terminent : 3, puis 1, en tête. Chaque cours entre devant tous ceux qu’il précède.
Lecture. Lignes 2–4, 6 : retour dans Algo : dfs(4), BD, dont le successeur GL est déjà marqué ; BD, puis Algo, sont empilés.
Lecture. Lignes 9–10, 2–4, 6 : \(v=1\) est marqué ; \(v=2\), Maths, ne l’est pas : ses successeurs SDA et IA sont marqués, il est empilé en tête. Les sommets 3 à 7 sont marqués.
Lecture. Ligne 12 : l’ordre de la pile, du sommet vers le fond : l’ordre postfixe inversé. Chaque arc va d’un cours vers un cours placé après lui.
Un arc \(v\to w\) examiné pendant dfs(v)
dfs(w) est appelé et se termine avant dfs(v).Dans les cas 1 et 2, \(w\) sort avant \(v\) : dans l’ordre inversé, \(v\) précède \(w\).
Détecter un cycle
Le cas 3 est le test : un arc vers un sommet dont l’appel est en cours (surPile) révèle un cycle.
Lecture. remonte(v, w), non montrée, suit edgeTo de \(v\) à \(w\). Les prérequis forment un DAG : null ; un arc SDA\(\to\)ProgC saisi par erreur ferme un cycle.
Une inscription
Tâches 0 à 5 et arcs, dans cet ordre : 0\(\to\) 1, 1\(\to\) 3, 2\(\to\) 3, 1\(\to\) 4, 3\(\to\) 4, 3\(\to\) 5.
| rang | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| postfixe | 4 | 5 | 3 | 1 | 0 | 2 |
| ordre | 2 | 0 | 1 | 3 | 5 | 4 |
Correction
Le problème
Un graphe pondéré, à poids positifs ou nuls ; la longueur d’un chemin est la somme de ses poids, et la distance \(d(s,v)\) la plus petite longueur d’un chemin de \(s\) à \(v\).
Relâcher l’arête \(v\)–\(w\)
\[ \text{si } \texttt{distTo[v]}+p(v,w)<\texttt{distTo[w]}~: \texttt{distTo[w]}\leftarrow\texttt{distTo[v]}+p(v,w), \texttt{edgeTo[w]}\leftarrow v\text{--}w \]
Dijkstra : à chaque tour, extraire le sommet non définitif de plus petite distance provisoire (le tas de la Séance 4), le déclarer définitif et relâcher ses arêtes.
Lecture. La largeur choisissait Kaolack–Kolda–Vélingara : 2 tronçons, \(272+123=395\) km. Le plus court en kilomètres passe par Kaffrine et Tambacounda : 376 km.
Une entrée par amélioration
Une Entree est un couple (sommet, distance), comparé par distance puis par numéro. Le tas de la Séance 4 ne sait pas diminuer une clé déjà rangée : on insère une nouvelle entrée, et l’ancienne, périmée, est ignorée à sa sortie (if (fait[v]) continue). Sedgewick et Wayne utilisent un tas indexé (IndexMinPQ), qui la remplace.
Sortie \(\rightarrow\)
00 630 63 2510 63 251 2720 63 251 272 2730 63 251 272 273 3570 63 251 272 273 357 3760 63 251 272 273 357 376 506
[0, 63, 273, 506, 376, 272, 357, 251]
Lecture. Lignes 1–3 : distTo vaut \(\infty\), sauf distTo[0] = 0 ; le tas reçoit 0:0.
Lecture. Lignes 5–9 : Kaolack sort ; Kaffrine reçoit 63, Ziguinchor 251, Kolda 272.
Lecture. Lignes 5–9 : Kaffrine sort à 63 ; Tambacounda reçoit 273. Trait épais foncé : les arêtes edgeTo.
Lecture. Lignes 5–9 : Ziguinchor sort à 251 ; Sédhiou reçoit 414, Kolda reste à 272.
Lecture. Lignes 5–9 : Kolda sort à 272 ; Sédhiou passe à 357, Vélingara reçoit 395.
Lecture. Lignes 5–9 : Tambacounda sort (273) ; Kédougou reçoit 506, Vélingara 376.
Lecture. Lignes 5–9 : Sédhiou sort à 357, définitif ; aucune distance n’est améliorée.
Lecture. Lignes 5–9 : Vélingara sort à 376, définitif ; aucune distance n’est améliorée.
Lecture. Lignes 5–6 : 395:4 sort, mais Vélingara est déjà définitif : entrée périmée.
Lecture. Lignes 5–6 : 414:6 sort, mais Sédhiou est déjà définitif : entrée périmée.
Lecture. Lignes 5–9 : Kédougou sort à 506, définitif ; aucune distance n’est améliorée.
Lecture. Ligne 11 : distances définitives ; en foncé, l’arbre des plus courts chemins.
public List<Integer> chemin(int v) {
if (distTo[v] == Integer.MAX_VALUE) return null;
LinkedList<Integer> c = new LinkedList<>();
for (int x = v; x != s; x = edgeTo[x].autre(x)) c.addFirst(x);
c.addFirst(s);
return c;
}
// dans main :
String[] nom = Reseau.VILLES;
Dijkstra d = new Dijkstra(Reseau.routesKm(), 0);
System.out.println(d.distance(4) + " km : " + Reseau.noms(d.chemin(4), nom));
System.out.println(d.extractions + " " + d.perimees + " " + d.insertions);Résultat \(\rightarrow\)
376 km : Kaolack Kaffrine Tambacounda Velingara
10 2 10
Lecture. 376 km par Kaffrine et Tambacounda (la largeur passait par Kolda). 10 extractions, dont 2 entrées périmées : Sédhiou 414, Vélingara 395.
Quoi
Un tas des sommets atteints, rangés par distance provisoire ; à chaque tour, le plus proche devient définitif et ses arêtes sont relâchées.
Justesse et coût
Quand \(v\) sort, \(\texttt{distTo[v]}=d(s,v)\) : un chemin plus court quitterait les sommets définitifs par un sommet \(u\) avec \(\texttt{distTo[u]}\geq\texttt{distTo[v]}\), et des poids positifs ou nuls ne le raccourciraient plus. Au plus \(E+1\) entrées dans le tas, chacune insérée et extraite en \(\mathcal{O}(\log E)=\mathcal{O}(\log V)\) : \(\mathcal{O}((V+E)\log V)\).
Quand ça échoue
Avec un poids négatif, un sommet déjà définitif peut encore être amélioré, et ses successeurs gardent une distance fausse. L’algorithme de Bellman-Ford (CLRS, § 22.1) traite ce cas.
Sortie \(\rightarrow\)
00 20 2 40 2 4 4
[0, 1, 4, 4]
Lecture. Lignes 1–3 : le même code, sur quatre sommets reliés par des arcs ; l’arc \(2\to1\) pèse \(-3\).
Lecture. Lignes 5–9 : 0 sort à 0 : le sommet 1 reçoit 2, le sommet 2 reçoit 4.
Lecture. Lignes 5–9 : 1 sort à 2 et devient définitif : le sommet 3 reçoit \(2+2=4\).
Lecture. Lignes 5–9 : 2 sort à 4 (égalité avec 3 : le plus petit numéro d’abord) ; l’arc \(2\to1\) donne \(4-3=1<2\) : distTo[1] passe à 1, bien que 1 soit définitif.
Lecture. Lignes 5–6 : l’entrée 1:1 sort, mais le sommet 1 est déjà définitif : elle est ignorée, et sa nouvelle distance n’est jamais propagée au sommet 3.
Lecture. Lignes 5–9 : 3 sort à 4, distance calculée depuis l’ancienne distance du sommet 1.
Lecture. Ligne 11 : [0, 1, 4, 4] : le chemin \(0\to2\to1\to3\) coûte \(4-3+2=3\), et distTo[3] vaut 4. Avec un poids négatif, un sommet extrait n’a pas forcément sa distance définitive.
{.t-scriptsize}Classe Dijkstra du cours, graphes connexes au hasard (Random(2026)), \(E=4V\), poids de 1 à 1 000.
Lecture. Les comparaisons restent entre \(0{,}64\) et \(0{,}68\) fois \((V+E)\log_2V\) : le coût suit la borne. Entrées insérées : \(1\,848\) pour \(4\,000\) arêtes à \(V=1\,000\).
Six sommets, depuis A
distTo.
| extrait | A | B | C | D | E | F |
|---|---|---|---|---|---|---|
| A (0) | 0 | 4 | 3 | \(\infty\) | \(\infty\) | \(\infty\) |
| C (3) | 0 | 4 | 3 | 11 | 10 | \(\infty\) |
| B (4) | 0 | 4 | 3 | 9 | 10 | \(\infty\) |
| D (9) | 0 | 4 | 3 | 9 | 10 | 15 |
| E (10) | 0 | 4 | 3 | 9 | 10 | 15 |
| D:11 | périmée, ignorée | |||||
| F (15) | 0 | 4 | 3 | 9 | 10 | 15 |
Correction
Arbre couvrant minimal
Dans un graphe connexe pondéré non orienté, un arbre couvrant relie tous les sommets par \(V-1\) arêtes, sans cycle. Un arbre couvrant de poids minimal {(minimum spanning tree)}, ou arbre couvrant minimal, a le plus petit poids total : relier les huit villes par de la fibre optique, le long des routes, au moindre coût.
Propriété de la coupe, algorithme de Kruskal
Pour tout partage des sommets en deux groupes, l’arête la plus légère qui relie les deux groupes appartient à un arbre couvrant minimal. Kruskal : examiner les arêtes par poids croissant et garder chacune qui ne ferme pas de cycle avec les arêtes déjà gardées.
Lecture. Une arête gardée relie deux morceaux de la forêt : toutes les arêtes plus légères ont déjà été examinées, elle est donc la plus légère entre son morceau et le reste.
Sortie \(\rightarrow\)
6363 8563 85 10363 85 103 12363 85 103 123 16363 85 103 123 163 21063 85 103 123 163 210 233
total 980
Lecture. Lignes 1–3 : les 10 arêtes entrent dans un tas rangé par poids ; forêt vide.
Lecture. Lignes 5–10 : Kaolack–Kaffrine (63 km) sort ; relies(foret, 0, 1) est faux : retenue.
Lecture. Lignes 5–10 : Kolda–Sédhiou (85 km) sort ; villes non reliées : retenue.
Lecture. Lignes 5–10 : Tambacounda–Vélingara (103 km) sort ; villes non reliées : retenue.
Lecture. Lignes 5–10 : Vélingara–Kolda (123 km) sort ; villes non reliées : retenue.
Lecture. Lignes 5–10 : Sédhiou–Ziguinchor (163 km) sort ; villes non reliées : retenue.
Lecture. Lignes 5–6 : Kolda–Ziguinchor (188 km) sort ; déjà reliées par Sédhiou : rejetée.
Lecture. Lignes 5–10 : Kaffrine–Tambacounda (210 km) sort ; villes non reliées : retenue.
Lecture. Lignes 5–10 : Tambacounda–Kédougou (233 km) retenue : 7 arêtes, \(V-1\), fin.
Lecture. Ligne 12 : 980 km relient les huit villes ; aucun arbre couvrant n’est plus court.
Choix de cette séance : relies(foret, v, w) lance un ParcoursLargeur sur la forêt depuis \(v\) et teste atteint(w) ; le compteur visites additionne les sommets atteints.
Random r = new Random(221);
for (int V : new int[] {500, 1000, 2000, 4000}) {
GraphePondere g = new GraphePondere(V, false);
for (int[] a : Generateur.aretes(V, 4 * V, r)) g.addEdge(a[0], a[1], a[2]);
Kruskal k = new Kruskal(g);
System.out.printf(Locale.ROOT, "%5d : %6d examinees, %6d rejets, "
+ "%9d visites, %.3f V^2%n", V, k.examinees, k.rejets,
k.visites, k.visites / ((double) V * V));
}Résultat \(\rightarrow\)
500 : 1718 examinees, 1219 rejets, 611838 visites, 2.447 V^2
1000 : 2971 examinees, 1972 rejets, 1986671 visites, 1.987 V^2
2000 : 5681 examinees, 3682 rejets, 7389527 visites, 1.847 V^2
4000 : 13589 examinees, 9590 rejets, 38476757 visites, 2.405 V^2
Lecture. Jusqu’à \(V\) sommets par arête examinée : les visites restent vers \(2V^2\) : elles sont multipliées par 3 à 5 quand \(V\) double. Avec Union-Find (Séance 6), Kruskal coûte \(\mathcal{O}(E\log E)\), le tri des arêtes.
Six sommets pondérés
| arête | poids | décision |
|---|---|---|
| D--E | 1 | gardée |
| B--C | 2 | gardée |
| A--C | 3 | gardée |
| A--B | 4 | rejetée : A--C--B |
| B--D | 5 | gardée |
| D--F | 6 | gardée, 5 arêtes : fin |
Correction
| question | algorithme | structure | coût | sur le réseau du cours |
|---|---|---|---|---|
| moins d’arêtes | largeur | file | \(\Theta(V+E)\) | Kédougou à 3 tronçons |
| ordres, cycles | profondeur | pile d’appels | \(\Theta(V+E)\) | préfixe, postfixe |
| ordre des prérequis | postfixe inversé | pile | \(\Theta(V+E)\) | 8 cours ordonnés |
| plus courts en km | Dijkstra | tas (Séance 4) | \(\mathcal{O}((V+E)\log V)\) | Vélingara à 376 km |
| arbre couvrant minimal | Kruskal | tas, test de cycle | \(\mathcal{O}(EV)\) ici | 980 km sur 1 691 |
Lecture. Tous sur les listes d’adjacence. Avec Union-Find (Séance 6), Kruskal passe à \(\mathcal{O}(E\log E)\). Les dix routes totalisent \(1\,691\) km ; l’arbre couvrant minimal en garde sept, 980 km.
À retenir
distTo = plus petit nombre d’arêtes ; edgeTo = arbre de parcours ; \(\Theta(V+E)\).Séance 6 — Ensembles disjoints et tris
Le test de cycle de Kruskal parcourt la forêt à chaque arête. La Séance 6 construit :
À préparer : le TP de la Séance 5 (Graphe, parcours, Dijkstra et Kruskal, tests JUnit et mesures).
Structures de données et algorithmes avancés — Séance 5 Dr. El Hadji Bassirou TOURÉ • DMI/FST/UCAD
Lecture. Ligne 1 : 8 listes vides, une par sommet (les villes sont numérotées de 0 à 7).
Lecture. Ligne 2 :
addEdge(0, 1), Kaolack–Kaffrine : 1 en queue de la liste 0, 0 en queue de la liste 1.Lecture. Ligne 2 :
addEdge(0, 7), Kaolack–Ziguinchor : 7 en queue de la liste 0, 0 en queue de la liste 7.Lecture. Ligne 2 :
addEdge(0, 5), Kaolack–Kolda : 5 en queue de la liste 0, 0 en queue de la liste 5.Lecture. Ligne 2 (suite) :
addEdge(1, 2), Kaffrine–Tambacounda : une entrée dans chacune des deux listes.Lecture. Ligne 2 (suite) :
addEdge(2, 3), Tambacounda–Kédougou : une entrée dans chacune des deux listes.Lecture. Ligne 2 (suite) :
addEdge(2, 4), Tambacounda–Vélingara : une entrée dans chacune des deux listes.Lecture. Ligne 2 (suite) :
addEdge(4, 5), Vélingara–Kolda : une entrée dans chacune des deux listes.Lecture. Ligne 2 (suite) :
addEdge(5, 6), Kolda–Sédhiou : une entrée dans chacune des deux listes.Lecture. Ligne 2 (suite) :
addEdge(5, 7), Kolda–Ziguinchor : une entrée dans chacune des deux listes.Lecture. Ligne 2 (suite) : dixième arête, Sédhiou–Ziguinchor : 10 arêtes, 20 entrées.
Lecture. Ligne 3 : 8 sommets, 10 arêtes ; non orienté, chaque arête figure dans deux listes.
Lecture. Ligne 4 : les voisins de Kolda, dans l’ordre des ajouts : Kaolack, Vélingara, Sédhiou, Ziguinchor. Son degré est 4.