Vous êtes sur la page 1sur 99

Algorithmique

Cristina Sirangelo, ENS-Cachan

Préparation à l'option Informatique


de l'agrégation de mathématiques
Plan
1. Analyse et complexité des algorithmes (S. Haddad)
2. Types abstraits et structures de données
3. Algorithmes de tri
4. Techniques classiques de conception d’algorithmes
5. Algorithmes de graphes
Graphes
Graphe orienté : Couple G=(S, A) où A ⊆ S×S 2
S: sommets
A: arcs 1 4

boucle: arc (v,v), v ∈ S


3

Graphe non orienté : Couple G=(S,A) où A est un ensemble d’ensembles {u, v} avec u, v ∈S et
u≠v
2
S: sommets
A: arêtes 1 4
boucles non admises

3
Convention (pour uniformiser les notations) : une arête {u,v} d’un graphe non-orienté est aussi
dénotée (u, v); et (u,v) et (v,u) dénotent la même arête.
Graphe pondéré : graphe G=(S,A) avec une fonction de poids w : A → R
Graphes - définitions de bases
Si G=(S,A) est un graphe (orienté ou non-orienté) et e=(u,v) ∈ A :

‣ v est adjacent à u ( relation symétrique si G est non-orienté)


‣ Si G est orienté
‣ e part de u et arrive à v
‣ Si G est non-orienté
‣ e est une arête incidente pour u et v
‣ Degré d’un sommet v d’un graphe non orienté: nombre d'arêtes incidentes à v
‣ Degré entrant (sortant) d’un sommet v d’un graphe orienté : nombre d'arcs arrivant à
(partants de) v
‣ Un sous graphe de G est un graphe (S’, A’) avec S’⊆ S et A’⊆A

‣ Si S’⊆S, le sous-graphe de G engendré par S’ est le graphe


G’=(S’, A’) avec A’ = { (u,v) ∈A | u, v ∈S’ }
Graphes - définitions de base
Si G=(S,A) est un graphe (orienté ou non-orienté)
‣ chemin de v1 à vk : séquence v1,v2...vk de sommets, k ≥ 1, telle que (vi, vi+1) ∈ A pour tout
i=1,..,k-1
(parfois appelé chaîne si G est non-orienté)
‣ longueur du chemin : nombre d’arcs/arêtes
‣ distance de u à v : longueur du plus court chemin de u à v.
‣ Si il existe un chemin de u à v : v accessible depuis u
‣ chemin simple (ou élémentaire) : sommets tous distincts
‣ Si G est non-orienté
‣ cycle: chemin v1,v2...vk avec v1=vk, k ≥ 4
‣ Si G est orienté
‣ circuit: chemin v1,v2...vk avec v1=vk, k≥2
‣ cycle/circuit simple: v2...vk distincts
‣ graphe non-orienté acyclique : sans cycle simple
‣ graphe orienté acyclique : sans circuit
Représentation des graphes
Par listes d'adjacence:
Pour chaque sommet v : la liste des sommets adjacents à u

2
1 2 3 4
1 4 2 4
3 4
4
3

2
1 2 3 4
1 4 2 1 4
3 1 4
4 1 2 3
3

En cas de poids associé aux arcs/arêtes : listes de couples (sommet, poids)


Représentation des graphes
Par matrice d'adjacence:
G=(S, A) est représenté comme une matrice |S| × |S| M(i, j) = 1 ssi (i, j) ∈ A

2
1 2 3 4
1 0 1 1 1
1 4 2 0 0 0 1
3 0 0 0 1
4 0 0 0 0
3

2
1 2 3 4
1 0 1 1 1
1 4 2 1 0 0 1
3 1 0 0 1
4 1 1 1 0
3
( on peut en stocker la moitié )
En cas de poids associé aux arcs/arêtes : M(i,j) stocke le poids de (i,j)
Représentation des graphes
Par listes d'adjacence:
‣ mémoire demandée : O(|S|+|A|)
‣ efficace pour les graphes peu denses ( |A| << |S|2 )
‣ recherche d’un arc/arête (u,v) : parcours de la liste d’adjacence de u
‣ parcours de tous les arcs/arêtes : O(|S|+|A|)

Par matrice d'adjacence:


‣ mémoire demandée : O(|S|2) indépendamment de A
‣ recherche d’un arc/arête (u, v) : O(1)
‣ parcours de tous les arcs/arêtes : O(|S|2)
Parcours de graphes
Parcours en largeur
‣ Objectif : “découvrir” tous les sommets du graphe accessibles depuis un sommet origine s
‣ “En largeur” ( intuitivement ) : tous le sommets à un certaine distance d sont rencontrés avant
le premier sommet à distance supérieure à d
‣ Idée : parcourir les sommets adjacents à s et les stocker dans une file. Pour chaque sommet
défilé, parcourir ses adjacents et les enfiler
‣ Pour parcourir chaque sommet une seule fois : marquage des sommets rencontrés
Parcours-en-largeur ( Graphe (S,A), Sommet s )
Tableau<Booléen> rencontré[ 1..|S| ]
File F;
for ( i=1 to |S| ) do rencontré[i] ← faux
rencontré[s] ← vrai
enfiler(F, s)
// F est la frontière des sommets rencontrés mais desquels le parcours n’a pas encore poursuivi
while ( not file-vide(F) ) do
u ← défiler(F)
pour tout (u, v)∈ A
if ( rencontré[v] = faux ) then rencontré[v] ← vrai ; enfiler(F, v) endif
endwhile ; return rencontré
Parcours en largeur
‣ Correction:
Lemme.
1) Parcours-en-largeur rencontre uniquement des sommets accessibles depuis l’origine s.
2) Parcours-en-largeur rencontre tous les sommets accessibles depuis s.
Preuve.
1) suit des invariants de boucle suivants (faciles à vérifier) : au début de chaque itération,
- la file F contient uniquement des sommets accessibles depuis s;
- rencontré[v] = vrai v accessible depuis s, pour tout sommet v

2. a) Quand la file est vide, tout sommet accessible depuis s a été rencontré :
Supposer qu’il existe un sommet accessible t non rencontré quand la file est vide. Sur le
chemin de s à t soit v le premier sommet non rencontré (à partir de s) et soit u son
prédécesseur (u rencontré)

s u v t

u rencontré u enfilé u défilé avant file vide v rencontré contradiction


Parcours en largeur
2. b) Terminaison (F devient vide) :
- Un sommet peut être rencontré une seule fois ( rencontré[v] passe de faux à vrai une
seule fois )
- Un sommet est enfilé seulement au moment où il est rencontré au plus |S| insertions

- Chaque itération défile un élément après au plus |S| itérations la file est vide

‣ Coût du parcours
‣ Au plus une itération par sommet. Une itération :
- défile le sommet en O(1) et
- parcourt ses sommets adjacents - O(1) sur chaque adjacent

coût :

‣ :
- graphe orienté : chaque arc est examiné au plus une fois : O(|A|)
- graphe non orienté : chaque arête est examiné au plus deux fois : O(|A|)
‣ coût O(|S|+|A|) en supposant représentation par liste d’adjacence
Parcours en largeur - distances
‣ Parcours augmenté avec n’importe quelle opération effectuée sur chaque sommet rencontré
‣ calcul de la distance des sommets du graphe d’un sommet origine
‣ calcul de l’arborescence de parcours en largeur
Distances:

Parcours-en-largeur( Graphe (S,A), Sommet s )


Tableau<Booléen> distance[ 1..|S| ]
File F;
for ( i=1 to |S| ) do distance[i] ← -1 // équivalent à i non rencontré
distance[s] ← 0
enfiler(F, s)
// F est la frontière des sommets rencontrés mais desquels le parcours n’a pas encore poursuivi
while ( not file-vide(F) ) do
u ← défiler(F)
pour tout (u, v)∈ A
if ( distance[v] = -1 ) then distance[v] ← distance[u]+1 ; enfiler(F, v) endif
endwhile ; return distance
Parcours en largeur - distances
‣ Correction: Soit δ(v) la distance du sommet v de l’origine s
Lemme. Au début de chaque itération de l’algorithme de calcul des distances:

1) Contenu de la file:
a) Si un sommet u est dans la file F u a été rencontré ( i.e. distance[u] ≠-1 ).
b) Les sommets dans la file sont triés par valeur de distance[u] (valeur minimale en tête).
c) Si t est la tête et q est la queue de F : distance[t] ≤ distance[q] ≤ distance[t] +1

2) Si t est la tête de la file, tous les sommets v à distance δ(v)≤ δ(t) ont été rencontrés ( i.e.
distance[v] ≠-1 )

3) Pour tout sommet v rencontré, distance[v] = δ(v)

Preuve. Exercice

‣ La correction suit du point 3) ci dessus et du fait que Parcours-en-largeur rencontre


exactement tous les sommets accessibles depuis s
(démontré comme en l’absence du calcul des distances)
Parcours en largeur - arborescence
Définition. Si G=(S,A) est un graphe, un sous-graphe G’=(S’, A’) S’⊆S A’⊆A avec un sommet
s ∈ A’ est une arborescence de parcours en largeur si

‣ S’ est l’ensemble des sommets accessibles depuis s


‣ pur tout v∈S’ il existe un unique chemin simple de s a v dans G’, qui est aussi un plus
court chemin de s à v dans G
Remarque: G’ est une un arbre enraciné en s
‣ L’arborescence de parcours en largeur peut être calculée pendant le parcours:
‣ mémoriser un champ π[v] pour chaque sommet v ; initialement π[v] =nil pour tout v
‣ quand un sommet v est rencontré pour la première fois depuis un arc (u,v) :
distance[v] ← distance[u]+1 ; π[v]← u
‣ u est le prédécesseur de v dans un plus court chemin de s à v (preuve similaire à celle
pour la distance)
‣ Lemme. Si π est le tableau des prédécesseurs calculé pendant le parcours en largeur de G
avec origine s, le graphe (Aπ, Sπ) avec le sommet s où
Sπ= {s} ∪ { v ∈S | π[v] ≠nil } Aπ= { (π(v), v) | v∈Sπ , v≠s}
est une arborescence de parcours en largeur pour G.
Preuve par induction sur la distance de s
Parcours en profondeur
‣ “en profondeur” ( intuitivement ) : les sommets accessibles depuis le sommet découvert le
plus récemment sont explorés avant de continuer le parcours
‣ Soit rencontré[ ] et π[ ] des variables globales;
initialement rencontré[i] = faux et π[i] = nil pour tout i
Parcours-en-profondeur( Graphe G, Sommet u )
//explore le graphe G=(S,A) “en profondeur” à partir de u
rencontré[u] ← vrai
//ici éventuelles opérations sur u (pre-visite de u)
pour tout (u, v)∈ A
if ( not rencontré[v] ) then π[v] ← u; Parcours-en-profondeur(G,v) endif
//ici éventuelles opérations sur u (post-visite de u)

‣ Coût : Au plus un appel Parcours-en-profondeur par sommet. Contribution de chaque appel :


O(1) : marquage et éventuellement pre-visite et post-visite
+ O( Adj(v) ) : parcours des sommets adjacents , O(1) sur chaque adjacent
coût : = O(|S|+|A|)

en supposant représentation par liste d’adjacence


Propriétés du parcours en profondeur
Graphe de parcours en profondeur d’un appel récursif Parcours-en-profondeur(G, u) :

Sous-graphe G’=( S’, A’) avec


S’ : ensemble de sommets rencontrés (i.e rencontré[v] affecté à vrai) pendant l’appel récursif
A’= { (π(v), v) | v∈S’ , v≠u}
où π est le tableau des prédécesseurs après l'exécution de l’appel récursif

Lemme.
Soit B l’ensemble des sommets v tels que rencontré[v]= faux au début d’un appel récursif
Parcours-en-profondeur( G, u ) avec u ∈B.
Soir GB le sous-graphe de G engendré par B.
Alors le graphe de parcours en profondeur de l’appel Parcours-en-profondeur( G, u ) est une
arborescence dont la racine est u et les sommets sont exactement les sommets accessibles
depuis u dans GB

Correction du parcours en profondeur : Par le lemme ci-dessus, le premier appel Parcours-en-


profondeur(G, s) avec B=S,
- rencontre exactement tous les sommets accessibles depuis s dans G
- son graphe de parcours en profondeur est une arborescence de racine s sur ces sommets.
Propriétés du parcours en profondeur
Preuve du lemme. Par induction sur |B| (B contient au moins u).
‣ Si |B|=1 alors B={u} graphe de parcours en profondeur : S’={u} A’ =∅.
(S’, A’) est une arborescence composé uniquement par la racine u
et u est le seul sommet de GB accessible depuis u
‣ Si |B| >1 :
➡ Soit v1, ..vn les adjacents de u tels que rencontré[vi]=faux quand l’arc (u, vi) est examiné par
Parcours-en-profondeur(G, u). Remarque : v1, ..vn ∈ B

➡ Le graphe G’ de Parcours-en-profondeur(G, u) est composé par :


- le sommet u avec un arc sortant (u,vi) pour tout i=1..n
- les sous-graphes Gi =( Si, Ai ) de parcours en profondeur des appels
Parcours-en-profondeur(G, vi), pour tout i=1..n
➡ Soit Bi l’ensemble des sommets v avec rencontré[v] = faux au début de l’appel
Parcours-en-profondeur(G, vi ).
➡ B1=B \ {u}, Bi =Bi-1 \ Si-1 pour i=2...n
➡ Bi ⊂ B hypothèse d’induction :
Gi est une arborescence de racine vi et Si = sommets accessibles depuis vi dans GBi
Propriétés du parcours en profondeur
➡ Bi =B \ ( {u} ∪ S1 ∪ ..∪Si-1 ) et Si⊆ Bi

‣ Si et Sj (les sommets de Gi et Gj) disjoints pour tout i≠j


‣ u∉ Si, pour tout i
➡ G’=(S’, A’) est une arborescence de racine u et sous-arbres fils G1, ..Gn

➡ Il reste à démontrer: S’ est l’ensemble des sommets accessibles depuis u dans GB


‣ S’= {u} ∪ S1 ∪ ..∪Sn , Si = sommets accessibles depuis vi dans GBi , u, vi ∈ B, (u, vi) ∈ A,
et Bi ⊆ B S’ accessible depuis u dans GB

‣ Soit v accessible depuis u dans GB par un chemin p ; on démontre v ∈ S’.


‣ Supposer v∉S’. u ∈ S’, v ∉ S’ il existe un arc (u’, v’) dans p tel que u’ ∈ S’ v’ ∉ S’

‣ v’ ∈ p v’ ∈ B rencontré[v’] =faux pendant Parcours-en-profondeur(G, u)

‣ u’ ∈S’ u’ rencontré par Parcours-en-profondeur(G, u) v’ analysé pendant Parcours-en-


profondeur(G, u)
‣ rencontré[v’] =faux quand v’ est analysé v’ rencontré par Parcours-en-profondeur(G, u)
v’ ∈S’ contradiction ☐
Dates de visite en profondeur

Parcours-en-profondeur( Graphe G, Sommet u )


//explore le graphe G=(S,A) “en profondeur” à partir de u
rencontré[u] ← vrai
//ici éventuelles opérations sur u (pre-visite de u)
pour tout (u, v)∈ A
if ( not rencontré[v] ) then π[v] ← u; Parcours-en-profondeur(G,v) endif
//ici éventuelles opérations sur u (post-visite de u)

Calcul des dates de découverte et de fin de traitement:

Soit date, d[ ] et f[ ] des variables globales; initialement date=0


‣ pre-visite de u : date ← date+1; d[u] ← date;
‣ post-visite de u : date ← date+1; f[u] ← date;

d[u], f[u] : début et fin de l’appel récursif Parcours-en-profondeur(G,u)


Arborescence de parcours en profondeur et dates de visite
‣ L’arborescence de parcours en profondeur correspond à l’arbre des appels récursifs
‣ à chaque sommet u de l’arborescence on associe l'intervalle [ d(u), f(u) ]
(début et fin de l’appel récursif)
‣ Un sommet x est descendant propre de y dans l'arborescence de parcours en profondeur ssi
l'intervalle [ d(x), f(x) ] est inclus dans l'intervalle [ d(y), f(y) ].
Dénoté :
[ d(y) [ d(x) f(x)] f(y) ]

‣ Intervalles bien parenthesés : si u et v sont deux sommets, leurs intervalles associés satisfont
une des deux propriétés suivantes :
‣ soit un des intervalles est inclut dans l’autre
‣ soit les deux intervalles sont disjoints (l’un commence après la fin de l’autre)
Recherche en profondeur (Depth First Search)
‣ Recherche en profondeur : parcours en profondeur réitéré jusqu’à ce que tous les sommets
aient été rencontrés.

Recherche-en-profondeur( Graphe G )
initialiser rencontré[ ] à faux π[ ] à nil et date à 0 , allouer d[ ] et f [ ]
Pour tout sommet v de G
si rencontré[v] = faux Parcours-en-profondeur (G, v)

‣ Forêt de parcours en profondeur :


Sous-graphe G’=(S, A’) avec
A’= { (π(v), v) | v∈S , π(v) ≠nil}

où π est le tableau des prédécesseurs après l'exécution de Recherche-en-profondeur(G)


‣ chaque arbre de la forêt est une arborescence de parcours en profondeur
Classifications des arcs
Soit G’ la forêt de parcours en profondeur d’un graphe orienté G
‣ Classification des arcs de G:
‣ arcs de liaison : arcs de G’
‣ arcs avants : arcs (u, v) t.q. v est un descendant propre de u dans G’ et (u,v) n’est pas un arc
de liaison
‣ arcs arrière : arcs (u, v) t.q. v est un ancêtre de u dans G’ (inclut les boucles)
‣ arcs transverses : tous les autres

Exemple 1

G 2 3 6 7

4 5
Classifications des arcs
Soit G’ la forêt de parcours en profondeur d’un graphe orienté G
‣ Classification des arcs de G:
‣ arcs de liaison : arcs de G’
‣ arcs avants : arcs (u, v) t.q. v est un descendant propre de u dans G’ et (u,v) n’est pas un arc
de liaison
‣ arcs arrière : arcs (u, v) t.q. v est un ancêtre de u dans G’ (inclut les boucles)
‣ arcs transverses : tous les autres

Exemple 1
arc avant
arc transverse
G 2 3 6 7

on
iais
arc arrière

l
de
4 5

arc
arc transverse

Forêt de parcours en profondeur G’ (deux arborescences)


Classifications des arcs
Lemme.
Soit (u,v) un arc d’un graphe orienté G. Soit d[ ] et f[ ] calculés par une recherche en profondeur
sur G. Alors (u,v) est un :
‣ arc de liaison ou un arc avant ssi [ d(u) [ d(v) f(v)] f(u) ]
‣ arc arrière ssi u=v ou [ d(v) [ d(u) f(u)] f(v) ]
‣ arc transverse ssi [ d(v) f(v) ] [ d(u) f(u)]
Preuve
‣ arcs de liaison, avant et arrière: immédiat par l’imbrication des intervalles
‣ Soit (u, v) un arc transverse. Alors u≠v (sinon arc arrière)
• Si d(u) < d(v) rencontré[v] = faux au début du parcours en profondeur de u (v ∈ B)
et v est accessible depuis u dans GB ( par l’arc (u, v) ) v descendant (propre) de u
(u,v) avant ou de liaison contradiction

• Alors d(v) < d(u). Deux cas : [ d(v) [ d(u) f(u)] f(v) ] ou [ d(v) f(v) ] [ d(u) f(u)]
• Si [ d(v) [ d(u) f(u)] f(v) ] v ancêtre de u dans l’arborescence en profondeur
(u,v) arc arrière contradiction ☐
Classifications des arêtes
Soit G’ la forêt de parcours en profondeur d’un graphe non-orienté G
‣ Classification des arêtes de G:
‣ arêtes de liaison : arêtes de G’
‣ arêtes arrière : arêtes {u, v} t.q. v est un ancêtre de u dans G’ et {u,v} n’est pas de liaison

Lemme.
Si e est une arête d’un graphe non orienté, e est soit une arête de liaison soit une arête arrière.
Preuve. Soit {u,v} une arête de G,
‣ Supposer sans perte de généralité d[u] < d[v]
‣ rencontré[v] = faux au début du parcours en profondeur de u (v ∈ B)
et v est accessible depuis u dans GB ( par l'arête (u, v) )
v est un sommet de l’arborescence de parcours en profondeur enraciné en u

u ancêtre de v dans G’ {u, v} arrière ou de liaison ☐


Applications de la recherche en profondeur

‣ Connexité et composantes connexes dans un graphe non-orienté

‣ Tri topologique d’un graphe orienté acyclique

‣ Composantes fortement connexes d’un graphe orienté


Composantes connexes d’un graphe non-orienté
‣ Un graphe non orienté G est connexe si pour toute paire de sommets u, v, le sommet v est
accessible depuis u
‣ Les composantes connexes de G sont les classes d'équivalence de la relation d’accessibilité
‣ Composante connexe de v : ensemble de sommets accessibles depuis v

‣ Le composantes connexes de G sont les sommets des arborescences en profondeur de


Recherche-en-profondeur (G)

‣ Connexité et affectation d’un numéro de composante connexe à chaque sommet:


- modifier Recherche en profondeur :
- initialiser un compteur cc à 0; allouer un tableau composante[ 1..|S| ]
- cc ← cc+1 à chaque nouveau appel à Parcours-en-profondeur
- Dans Parcours-en-profondeur :
- pre-visite de u : composante [u] ← cc
- G est connexe ssi cc =1
Tri topologique
‣ Soit G =(S,A) un graphe orienté.
‣ Un tri topologique de G est une permutation v1, ..vn des sommets S telles que
‣ Si (vi, vj) ∈ A alors i < j
v1 v2 v3 v4 v5

‣ Si un tri topologique existe, alors G est acyclique (i.e. sans circuit)


Lemme. Un graphe orienté G est acyclique ssi une recherche en profondeur de G ne génère
aucun arc arrière
‣ Par le lemme de classification des arcs : (u,v) est un arc arrière ssi f(u) ≤ f(v)
‣ G acyclique dans un parcours en profondeur de G, f(u) > f(v) pour tout arc (u,v) de G

‣ G acyclique un tri des sommets par f(u) décroissant est un tri topologique

‣ Algorithme pour le tri topologique d’un graphe acyclique G=(S,A) :


Recherche-en-profondeur(G) avec
post-visite de u : insérer u au début d’un liste chaînée
‣ Coût : Θ(|S|+|A|)
Tri topologique
Preuve du lemme.
‣ Si il existe un arc arrière entre un sommet u de G et son ancêtre v dans une arborescence de
parcours en profondeur u accessible depuis v (via l’arborescence) et v depuis u (arc (u,v))

‣ Si G a un circuit.
๏ Soit v le sommet de ce circuit qui est exploré en premier pendant le parcours en
profondeur ( le plus petit d[v] )
๏ Soit u le prédécesseur de v dans le circuit
๏ Si u=v l’arc (u,v) est un arc arrière par définition
๏ Si u≠v alors u n’a pas encore été rencontré à la date d[v]
๏ parcours-en-profondeur(G, v) retourne une arborescence de racine v ayant u comme
descendant propre
๏ (u,v) est un arc arrière
Composantes fortement connexes
‣ Les composantes fortement connexes d’un graphe orienté sont les classes d'équivalence de la
relation ≈ suivante :
u ≈ v ssi u est accessible depuis v et v est accessible depuis u

‣ Composantes fortement connexes et parcours en profondeur:

Lemme. Soit F une une forêt de parcours en profondeur d’un graphe orienté G. Le
sous-graphe de F engendré par chaque composante fortement connexe de G est un arbre
Preuve. Exercice

1 6
F

G 2 3 5 7

4 8
Composantes fortement connexes
‣ On dénote ri le sommet de G qui est racine de sa composante dans F
‣ On suppose r1...rn triés par date de fin f[ri] croissante
‣ On dénote Ci la composante de racine ri

C3
r3
1 6 C1

r2 r1
2 3 5 7

C2 4 8

Lemme. Si (u,v) est un arc de G, u ∈Ci et v∈Cj alors i ≥ j


(i.e. Cn...C1 est un tri topologique du graphe des composantes)
Preuve. Exercice

C3 C2 C1
Composantes fortement connexes
‣ Composantes fortement connexes et parcours en profondeur:
Lemme. Ci est l’ensemble de sommets qui sont descendants de ri en F et n’appartiennent
pas à C1, ..Ci-1.

Preuve.
C3 C1
๏ v∈Ci v descendant de ri et v∉C1, ..., v∉Ci-1. r3
1 6
๏ v descendant de ri
r2 r1
il existe un chemin de ri à v 2 3 5 7

๏ Si Cj est la composante de v, alors j≤ i


(par l’ordre topologique des composantes) 4 8
๏ j≤ i et v∉C1, ..., v∉Ci-1 v∈Ci C2

‣ Remarque :
Dans un parcours en profondeur de G, en fin de traitement de ri, les composantes C1...Ci-1
ont déjà été parcourues (composantes qui se terminent avant ri)
Si on suppose C1...Ci-1 déjà calculés, il suffit de détecter que ri est la racine de sa composante
pour calculer Ci
‣ C’est l’idée de l’algorithme de Tarjan pour calculer les composantes fortement connexes
Algorithme de Tarjan
Idée de l’algorithme:
‣ Faire un parcours en profondeur de G et stocker les sommets dans une pile dans l’ordre
dans lequel ils sont rencontrés pour la première fois
‣ Les composantes sont éliminées de la pile au fur et à mesure qu’elles se terminent (à la fin
du traitement de leur racine)
‣ Cela arrive dans l’ordre r1...rn (l’ordre de fin de traitement des racines dans le parcours)
‣ à la fin du traitement de chaque sommet u : détecter si u est une racine
‣ Si u est une racine ri, le sommet de la pile contient tous les descendants de ri sauf les
composantes déjà terminées, i.e les composantes C1, ...Ci-1
le sommet de la pile contient la composante Ci
‣ stocker Ci et éliminer Ci de la pile
‣ Coût : coût d’un parcours en profondeur O(|S|+|A|)
Problème à résoudre :
‣ Détecter, en fin de traitement, si un sommet est une racine d’une composante,
en supposant connues les composantes déjà terminées
Algorithme de Tarjan
Définition ( retour(u) ). Soit u ∈ Ci

retour(u) = min { d[u] } ∪ { d[w] | w ∈ W }


u
Où W est l’ensemble des sommets w tels que
1) il existe un arc arrière ou transverse (u’, w)
w
et u’ est un descendant de u dans F
u'
2) w ∉ C1, ..., w ∉ Ci-1

Lemme. Si w ∈ W, w∈ Ci (la composante de u) et donc w est descendant de ri dans F

Preuve. Par l’ordre topologique des composantes (il existe un chemin de u à w)


Algorithme de Tarjan
Lemme. Un sommet u est la racine de sa composante fortement connexe ssi
retour(u) = d[u]
Preuve. Soit Ci la composante de u.
a) u = ri d[u] ≤ d[v] pour tout v∈Ci d[u] ≤ d[w] pour tout w ∈W retour(u) =d[u]

b) u ≠ ri - u descendant propre de ri dans F


- Il existe un chemin p de u à ri dans G contenant au moins un arc
๏ Soit w le premier sommet de p qui n’est pas un descendant de u dans F ri
( w peut être ri )
๏ Soit u’ le prédécesseur de w dans p :
- u’ est un descendant de u dans F u

- (u’, w) est un arc arrière ou transverse p


w
๏ w ∈W. On démontre d[w] < d[u] ( et donc retour(u) < d[u] )
u'

๏ Supposer d[w]≥ d[u] d[w]> d[u] (par l’imbrication des intervalles) d[w] > f[u]

๏ d[w] > d[u’] contradiction ( parce que (u’, w) arc arrière ou transverse d[w] ≤ d[u’] )

Algorithme de Tarjan
‣ retour(u) peut être calculé pendant le parcours en profondeur, en fin de traitement de u,
grâce à la caractérisation suivante :
Lemme Soit u ∈ Ci.
retour(u) = min { d[u] } ∪
{ d[v] | (u, v) est un arc arrière ou transverse et v ∉ C1, ..v ∉ Ci-1} ∪
{ retour(v) | (u, v) est un arc de liaison }
Preuve. Exercice
‣ Composantes fortement connexes d’un graphe orienté G :

Tarjan( Graphe G )
initialiser rencontré[ ] à faux et date à 0 ,
allouer d[ ], f[ ] et ret[] // tableaux pour les dates de découverte et fin et les valeurs de retour( )
Pile P; // la pile des sommets rencontrés
Liste L; // la liste des composantes
Pour tout sommet v de G
si rencontré[v] = faux Parcours-Tarjan(G, v)
retourner L;
Algorithme de Tarjan
Parcours-Tarjan( Graphe G, Sommet u )
date ←date+1; d[u] ← date ; rencontré[u] ← vrai ; Empiler(P, u)
ret[u] ← d[u]
for all (u, v) ∈ A
if ( not rencontré[v] ) then // (u,v) arc de liaison
Parcours-Tarjan(G,v) ; ret[u] ← min{ ret[u], ret[v] }
elseif ( rencontré[v] and d[v] < d[u] ) // (u,v) arc arrière ou transverse
si v est dans la pile // i.e. v n’appartient pas au composantes déjà terminés
ret[u] ← min { ret[u], d[v] }
endif
endfor
date ← date+1 ; f[u] ← date
if ( ret[u] = d[u] ) then // u est la racine de sa composante
//composante de u : tous les descendants de u dans la pile
C←∅
repeat x ← sommet(P) ; Dépiler(P); C←C ∪ { x }; until ( x=u )
Inserer-en-tete(L, C)
endif
Validité de l’algorithme de Tarjan
‣ Pre et post-conditions :
๏ Si au début d’un appel récursif Parcours-Tarjan(G, u) la pile contient exactement tous les
sommets v déjà rencontrés ( i.e. d[v] < d[u] ) qui n’appartiennent pas aux composantes
fortement connexes déjà terminées ( i.e avec f[rj]< d[u] ),
à la fin de Parcours-Tarjan(G, u) :
๏ 1) La pile contient exactement tous les sommets v déjà rencontrés (i.e. d[v] < f[u] ) qui
n’appartiennent pas aux composantes fortement connexes déjà terminées (i.e avec f[rj]≤ f[u])
๏ 2) La liste des composantes contient exactement toutes les composantes fortement
connexes déjà terminées ( i.e avec f[rj]≤ f[u] )
๏ 3) ret[u] = retour(u)
Preuve. Exercice
‣ Validité
‣ Pre-condition vrai au début du premier appel Parcours-Tarjan(G, v) effectué par Tarjan(G)
(Pile vide et aucun sommet rencontré)
pre-condition vrai au début de chaque appel Parcours-Tarjan(G, v)

‣ à la fin de Tarjan(G), par 2) L est la liste des composantes fortement connexes de G


Plus courts chemins
Plus courts chemins
‣ Soit G=(S,A) un graphe orienté pondéré avec la fonction de poids w: A →R
‣ Poids (ou longueur) d’un chemin p= v1...vk :

‣ δ(u, v) -- longueur du plus court chemin de u à v :


‣ S’il n’existe pas de circuit de poids strictement négatif sur un chemin de u à v :
min { w(p) | p est un chemin de u à v } si il existe un chemin de u à v
δ(u, v) =
∞ sinon
‣ S’il existe un circuit de poids strictement négatif sur un chemin de u à v δ(u, v) = - ∞
‣ Plus court chemin entre u et v (uniquement défini si δ(u,v) est fini) : chemin p entre u et v tel
que w(p) = δ(u,v)
‣ Variantes déjà rencontrées :
‣ plus courts chemins dans un graphe acyclique
‣ distances (plus courts chemins avec poids unitaires)
Plus courts chemins
Propriétés des plus courts chemins
1) Si (u, v) ∈ A et s ∈ S δ(s, v) ≤ δ(s, u) + w(u, v)

2) Si δ(u, v) est fini, il existe un plus court chemin élémentaire de u a v

3) Si δ(u, v) est fini et p est un plus court chemin de u à v, chaque sous-chemin de p est un
plus court chemin entre son sommet de départ et son sommet d’arrivé
Algorithme de Bellman-Ford
‣ Input : un graphe orienté pondéré G=(S,A) avec fonction de poids w, un sommet de départ s
‣ Output : - un tableau d[ 1..|S| ] tel que d[u] = δ(s,u) pour tous u ∈ S, s’il n’existe pas de circuit
de poids strictement négatif accessible depuis s
- un booléen qui vaut faux ssi il existe un circuit de poids strictement négatif accessible depuis s
‣ Idée de l’algorithme :
‣ Pour tout v ∈S d[v] est initialisé à une “estimation” de δ(s, v) (borne supérieure)

‣ à chaque itération une meilleure estimation est obtenue pour chaque sommet sur la base
des estimations courantes :
‣ Si (u, v) ∈A d[u] +w(u, v) ≥ δ(s, u) +w(u, v) ≥ δ(s, v)

‣ d[u] +w(u, v) est une nouvelle estimation de δ(s, v)

‣ Si d[u] + w(u, v) ≤ d[v] la nouvelle estimation est meilleure et d[v] ← d[u] +w(u, v)
( relâchement de l’arc (u,v) )
‣ Si aucun circuit de poids strictement négatif n’est accessible depuis s, après |S|-1 itérations les
bornes ne peuvent plus être améliorées (point fixe)
‣ Si il y a un circuit de poids négatif accessible depuis s, le point fixe n’est pas atteint après |S|-1
itérations
Algorithme de Bellman-Ford
Bellman-Ford( Graphe (S,A,w), Sommet s )
//estimation initiale
Pour tout v∈ S d[v] ← ∞
d[s] ← 0

for ( i ← 1 to |S|-1 ) do
Coût O(|S||A|) //relâchement de tous les arcs
pour tout (u,v) ∈ A d[v] ← min{ d[v], d[u]+w(u,v) }
endfor

//détection des cycles de poids négatif


pour tout (u,v) ∈ A
if ( d[v] > d[u]+w(u,v) ) return false
return true

‣ Calcul des plus courts chemins : calculer un tableau des prédécesseurs π t.q. π[v] est le
prédécesseur de v dans un plus court chemin de s à v, s’il existe
‣ initialiser π[v]←nil pour tout v
‣ à chaque relâchement tel que d[v]← d[u]+w(u,v) affecter π[v]← u
Validité de l’algorithme de Bellman-Ford
Lemme. Dans l’algorithme de Bellmann-Ford, pour tout v ∈ S :

1) au début de chaque itération* d[v] ≥ δ(s,v);


2) Si au début d’une itération d[v] = δ(s,v), alors d[v] n’est plus modifié par la suite;
3) Si p=v1...vk est un chemin élémentaire entre s et vk alors au début de l'itération i
d[vi] ≤ w(v1...vi) pour tout i=1..k;
4) Quand la procédure se termine d[v] est fini ssi v est accessible depuis s;
5) Si δ(s, v) > -∞ alors d[v] =δ(s, v) quand la procédure se termine;
6) Si δ(s, v) = -∞ il existe un arc (u, u’) sur un chemin de s à v tel que d[u’] > d[u] +w(u, u’)
quand la procédure se termine.

Preuve Exercice

* début de l'itération i : test de la condition i ≤ |S|-1 ( i=1..|S| )


Validité de l’algorithme de Bellman-Ford
Corollaire. L’algorithme de Bellman-Ford calcule la longueur du plus court chemin de s a v pour
tout v tel que δ(s, v) > -∞
De plus l’algorithme renvoie faux ssi il existe un sommet v tel que δ(s, v) = -∞, i.e. ssi il existe un
circuit de longueur négative accessible depuis s

Preuve. Immédiat par 5) et 6)

‣ L’algorithme peut être modifié pour retourner d[v] =δ(s,v) pour tout v∈S (Exercice)
Algorithme de Dijkstra
Plus courts chemins dans un graphe orienté où le poids de tous les arcs est positif ou nul
‣ Entrée : un graphe orienté pondéré G=(S,A) avec une fonction de poids w t.q w(e) ≥ 0 ∀ e∈S,
un sommet de départ s
‣ Sortie : un tableau d[ 1..|S| ] tel que d[u] = δ(s,u) pour tous u ∈S
‣ Idée de l’algorithme (approche gloutonne)
‣ pour tout sommet v, d[v] contient toujours une “estimation” de δ(s, v) (borne supérieure)
‣ l’algorithme maintient un ensemble de sommets E t.q. d[v]=δ(s, v) pour tout v∈ E
‣ choix glouton : à chaque étape le sommet u ∈ S\E ayant d[u] minimale est ajouté à E
(d[u]=δ(s, u)) et ses arcs sortant sont relâchés.

Dijkstra( Graphe (S,A,w), Sommet s )


Pour tout v∈ S d[v] ← ∞; d[s] ← 0; //estimation initiale
E ←∅; F ← Creer-File-Priorité ( S ) // priorité de v : d[v]
while ( not Est-File-Vide(F) ) do
u ← Défiler-min(F); E ← E ∪ {u}
pour tout (u,v) ∈ A d[v] ← min{ d[v], d[u] + w(u,v) }
endwhile
Validité de l’algorithme de Dijkstra
Si Z ⊆S, on dénote
0 si v = s
δZ(s,v) = longueur du plus court chemin de s à v tel que le prédécesseur de v dans
le chemin est dans Z, si s≠v

Lemme. (Invariants) Dans l’algorithme de Dijkstra, au début de chaque itération :


1) Pour tout v d[v] ≥ δ(s,v)
2) F = S\E
3) Pour tout v ∈ E d[v] = δ(s,v)

4) Pour tout v ∈ S\E d[v] = δE(s,v)

Terminaison. Le nombre de sommets dans F décroît a chaque itération


Correction. Quand la procédure termine F=∅ et par 2) E=S

par 3), d[v] = δ(s, v) pour tout v ∈ S


Validité de l’algorithme de Dijkstra
Preuve du Lemme.
‣ Au début de la première itération :
- 1) d[s] = 0 = δ(s,s) , d[v] =∞ ≥ δ(s,v)
- 2) E = ∅, F = S

- 4) Pour tout v ∈ S, si v=s d[v] = 0 = δE(s,v), si v≠s, d[v] = ∞ = δE(s,v)

‣ Supposer 1) 2) 3) et 4) vrais au début de l’itération i


Soit u le sommet de F avec d[u] minimale au début de l'itération
➡ 1) Pour tout v si l'itération i change d[v]
d[v] = d[u] +w(u,v) ≥ δ(s,u)+w(u,v) ≥ δ(s,v) au début de l'itération i+1

➡ 2) dans l'itération i : F← (S\E) \{u} E←E∪{u}


au début de l'itération i+1 : F =S\E
Validité de l’algorithme de Dijkstra
➡ 3) On démontre que quand u est ajouté à E dans l'itération i, d[u] = δ(s, u) :
Vrai si i=1 (u =s et d[u] = 0 = δ(s,u) )
Si i>1, par contradiction :
- si d[u] ≠ δ(s, u) alors par 1) d[u] > δ(s, u) δ(s, u) est fini. Soit p le plus court chemin
de s à u. Soit y le premier sommet de S\E dans p et x son prédécesseur,
- s∈E y ≠s et x ∈E u

- w(ps↝y) ≥ δE(s, y) = d[y] (par 4) s

E x y
- poids positifs ou nuls w(p) ≥ w(ps↝y) ≥ d[y]

- d[u] > δ(s, u) = w(p) ≥ d[y] contradiction (u est le sommet de S\E avec d[u] minimal)

d[u] n’est pas modifié avant la fin de l'itération i ( min{ d[u], d[u] +w(u,u) } = d[u] )
au début de l'itération i+1 d[v] = δ(s,v) pour tout v ∈ E
Validité de l’algorithme de Dijkstra
➡ 4) Au début de l'itération i+1, pour tout v ∈ S\E :

‣ remarque : v ≠s
‣ si v n’est pas un successeur de u , d[v] n’a pas été modifié par l'itération i
- d[v] = δE\{u} (s, v) (par 4)
- δE\{u} (s, v) = δE(s, v) ( u n’est pas un prédécesseur de v )
‣ si v est un successeur de u d[v] = min{ δE\{u} (s, v), d[u] +w(u, v) }
- d[u] = δ(s, u) d[u] + w(u, v) = δ{u}(s, v)

- d[v] = min { δE\{u} (s, v), δ{u}(s, v) } d[v] = δE(s,v) ☐


Coût de l’algorithme de Dijkstra
Opérations :
‣ Création d’une file de priorité contenant |S| éléments
‣ |S| opérations d’extraction du minimum
‣ |A| opérations de réduction de la priorité

‣ File implementé par tableau linéaire : O( |S|2 )


‣ Création de la file : O(|S|) , extraction du min : O(|S|), réduction de la priorité : O(1)
‣ File implementé par tas binaire : O( (|S|+|A|) log|S| )
‣ Création de la file : O(|S|) , extraction du min : O(log|S|), réduction de la priorité : O(log|S|)
‣ File implementé par tas de Fibonacci : O(|S| log|S| + |A| )
‣ Création de la file : O(|S|) , extraction du min : O(log|S|), réduction de la priorité : O(1)
Plus court chemin pour tout couple de sommets
‣ Première approche: exécuter |S| fois un algorithme de plus court chemin à origine unique
‣ Arcs de poids positif ou nul : Dijkstra
‣ tableau : O(|S|3)
‣ tas binaire O( (|S|2+|S||A|) log|S| )
‣ tas de Fibonacci : O( |S|2 log|S| +|S||A| )
‣ Arcs de poids possiblement négatif : Bellman-Ford
‣ O( |S|2|A| )
‣ Approche directe - arcs de poids possiblement négatif
‣ Algorithme de Floyd-Warshall (programmation dynamique)
‣ Θ( |S|3 )
‣ Algorithme basé sur la multiplication de matrices (programmation dynamique)
‣ Θ( |S|3 log|S| )
‣ Algorithme de Johnson (repondération)
‣ O( |S|2 log|S| +|S||A| )
Algorithme de Floyd-Warshall
Soit G=(S,A) un graphe orienté pondéré avec une fonction de poids w et S={ 1,...,n }

Pour l’instant: il n’y a pas de cycles de poids strictement négatif en G


‣ Sous-structure optimale des plus courts chemins :
‣ Plus court chemin de i a j :
plus court chemin de i à j dont les sommets intermédiaires sont tous dans {1...n}
‣ Espace des solutions:
‣ Pour chaque chemin de i a j dont les sommets intermédiaires sont tous dans {1..n} au
moins un des deux cas suivants est possible :
‣ 1) n n’est pas un sommet intermédiaire
‣ 2) le chemin passe par n

‣ Plus court chemin dans le cas 1) :


plus court chemin de i à j dont les sommets intermédiaires sont tous dans {1..n-1}
Algorithme de Floyd-Warshall
‣ Plus court chemin dans le cas 2) :
‣ le chemin passe une seule fois par n (absence de cycles de poids négatif)
‣ il est composé par deux plus-courts sous-chemins

i n j
plus court chemin de n à j avec
plus court chemin de i à n avec
sommets intermédiaires
sommets intermédiaires
dans {1,...,n-1}
dans {1,...,n-1}
‣ Sous-problèmes :
Soit δk(i, j) la longueur du plus court chemin de i à j avec sommets intermédiaires dans {1,..,k}
δk(i, j) = min { δk-1(i, j), δk-1(i, k) + δk-1(k, j) }

w(i,j) si i≠j
δ0(i, j) =
0 si i=j

où w(i,j) est le poids de l’arc (i,j) et w(i,j)=∞ si (i,j) ∉A


Algorithme de Floyd-Warshall
Floyd-Warshall( Graphe (S,A,w) )
Pour tout i, j ∈ S d[i, j] ← δ0(i,j);
d j
for ( k←1 to n do ) do
//calcul de la matrice d’ = δk à partir de d= δk-1
for ( i←1 to n do ) do δk
for ( j←1 to n do ) do
d’[i, j] ← min{ d[i,j], d[i,k]+d[k, j] }
i
endfor δk-1
endfor
échanger d et d’
endfor; return d
‣ La matrice d’ n’est pas nécessaire : d[i, j] ← min{ d[i,j], d[i,k]+d[k, j] }
L’algorithme reste correct, i.e. à la fin de l'itération k de la boucle principale d = δk
‣ Par contradiction. Soit k, i, j les plus petits indices tels que à la fin de l'itération k de la
boucle principale d[i, j] ≠ δk(i, j)
‣ d[i, j] = min {δk-1(i,j), δh(i, k) + δq(k, j) } avec h, q ∈{ k, k-1}

‣ δk(i, k)= δk-1(i, k) δk(k, j) = δk-1(k, j) d[i, j] = δk(i, j)


Algorithme de Floyd-Warshall
Floyd-Warshall( Graphe (S,A,w) ) Coût : Θ(n3)
Pour tout i, j ∈ S d[i, j] ← δ0(i,j); indépendamment de la représentation
for ( k←1 to n do ) do du graphe
//calcul de la matrice d = δk à partir de d= δk-1
for ( i←1 to n do ) do
for ( j←1 to n do ) do
d[i, j] ← min{ d[i,j], d[i,k]+d[k, j] }
endfor
endfor
endfor; return d

‣ Calcul des plus court chemins : calculer un tableau des prédécesseurs π[1..n, 1..n],
à l'itération k, à π[i,j] est affecté le prédécesseur de j dans un plus court chemin de i à j dont
les sommets intermédiaires sont dans {1..k} :
‣ initialiser π[i,j] ← i si i≠j et w(i,j) <∞ et π[i,j] ← NIL, sinon
‣ à l'itération k, pour tout i, j, avant le calcul de d[i,j] :
if( d[i,j] > d[i,k]+d[k,j] ) π[i,j] ←π[k,j] endif
Algorithme de Floyd-Warshall - Exercice
En présence de cycles de poids strictement négatif :
w(i,j) si i≠j
‣ Définir Δ0(i, j) =
min{ 0, w(i,j) } si i=j

Pour k>0 Δk(i, j) = min { Δk-1(i, j), Δk-1(i, k) + Δk-1(k, j) }

‣ Remarque : L’algorithme de Floyd-Warshall qui utilise les tableaux d et d’ et initialise


d ← Δ0, calcule Δk à la fin de l'itération k
‣ δk(i,j) la longueur du plus court chemin entre i et j dont les sommets intermédiaires sont dans
{1..k} est maintenant défini comme:
- ∞ s’il n’y a pas de chemin de i à j dont les sommets intermédiaires sont dans {1..k}
- -∞ s’il existe un chemin de i à j dont les sommets intermédiaires sont dans {1..k} et qui
contient un cycle de poids strictement négatif avec sommets dans {1..k}
- min { w(p) | p est un chemin de i à j dont les sommet intermédiaires sont dans {1..k} }
sinon
‣ δk(i, j) ne coïncide plus forcement avec Δk(i, j)
Algorithme de Floyd-Warshall - Exercice
‣ Démontrer les propriétés suivantes de Δk sur un graphe pondéré arbitraire G=(S,A),
S={1..n}
‣ Pour tout i,j ∈ S et pour tout k = 0..n
Δk(i, j) > -∞ et
si δk(i,j) >-∞ alors Δk(i, j) = δk(i,j)

‣ Pour tout i, j ∈ S
Si p est un chemin simple de i à j dont les sommets intermédiaires sont tous dans {1..k} (p
est un cycle simple si i=j) Δk(i, j) ≤ w(p)
‣ Démontrer ensuite le lemme suivant
Lemme. Pour tout i, j ∈S δ(i,j) = -∞ ssi il existe un sommet k∈S tel que

Δn(k,k) < 0, Δn(i,k) <∞ et Δn(k,j) <∞

‣ En déduire un modification de l'algorithme de Floyd-Warshall qui calcule


d[i,j] = δ(i,j) pour tout i, j ∈S

(utiliser les deux tableaux d et d’ par simplicité)


Fermeture transitive
Soit G=(S,A) un graphe orienté, Fermeture transitive de G :
Graphe G* = (S, A*) où A* ={ (i,j) | i, j ∈ S et il existe un chemin de i à j dans G }

Algorithmes pour la fermeture transitive de G :


‣ Algorithme de Floyd-Warshall sur G avec w(i,j)=1 pour tout i,j
๏ d[i,j] = δ(i,j)< ∞ ssi il existe un chemin de i à j - Coût : Θ(n3)

‣ Approche directe de programmation dynamique ( Floyd-Warshall + → ∧, min →∨ )

τk(i,j) : vrai ssi il existe un chemin de i à j dont les sommets intermédiaires sont dans {1..k}

0 si i≠j et (i,j) ∉A
τ0(i, j) =
1 si i=j ou (i,j)∈A

τk(i, j) = τk-1(i, j) ∨ ( τk-1(i, k) ∧ τk-1(k, j) )


Fermeture transitive
‣ Approche directe de programmation dynamique

Fermeture-transitive( Graphe (S,A) )


Pour tout i, j ∈ S t[i, j] ← τ0(i,j);
for ( k←1 to n do ) do
// calcul de la matrice t = τk à partir de t= τk-1
for ( i←1 to n do ) do
for ( j←1 to n do ) do
t[i, j] ← t[i,j] ∨ ( t[i,k] ∧ t[k, j] )
endfor
endfor
endfor
return t

๏ Coût Θ(n3)
๏ Approche directe plus efficace (tableaux de bits, opérations booléennes plus efficaces)
Arbres couvrants minimaux
Arbre couvrant minimal
Soit G=(S, A) un graphe connexe non-orienté muni d’une fonction w de poids des arêtes.
‣ Arbre couvrant de G : sous-graphe T=(S, A’) avec A’ ⊆ A, tel que A est un arbre (i.e. un graphe
non-orienté connexe et acyclique).
‣ Remarque : |A’| =|S|-1
‣ Poids d’un sous-graphe T=(S,A’) : .

‣ Arbre couvrant minimal de G : arbre couvrant de G de poids minimum.


‣ Chaque graphe non-orienté connexe G a un arbre couvrant (minimal) :
Un graphe non-orienté avec un seul sommet est un arbre.
Soit G un graphe connexe avec plus d’un sommet :
T
- soit G est un arbre 3

- soit il existe une arête qui peut être 2 6


4
ôtée de G et G reste connexe
8 5

9 8
w(T) = 22
7
Arbres couvrants minimaux et algorithmes gloutons
Calcul d’un arbre couvrant minimal d’un graphe non-orienté pondéré connexe G=(S,A) :
‣ Réduction au calcul d’un ensemble optimal d’un matroïde pondéré
‣ instance de l’algorithme glouton pour les matroïdes pondérés : O(|A|2)
‣ Approches gloutonnes directes plus efficaces
‣ Algorithme de Kruskal :
‣ à l’aide d’une structure de données union-find : O(|A| log |A| )
‣ à l’aide d’une structure union-find optimisée + tri en temps linéaire : O( |A| log*|S| )
‣ Algorithme de Prim
‣ à l’aide d’un tas binaire : O( |A| log|S| )
‣ à l’aide d’un tas de Fibonacci : O( |S| log |S| + |A| )
Remarque dans un graphe non-orienté connexe |A| ≥ |S| -1
‣ Kruskal/union-find et Prim/tas binaire : même coût asymptotique
‣ Prim/tas de Fibonacci : plus performant
Arbres couvrants minimaux et algorithmes gloutons
‣ Idée de l’approche gloutonne :
----
Initialiser un ensemble E à ∅

tant que |E| < |S|-1


Invariant : E est un sous ensemble d'arêtes de G contenu dans un arbre couvrant minimal
- trouver une arête (u,v) ∉E tel que E∪{(u,v)} est encore contenu dans un arbre couvrant
minimal de G
- ajouter (u,v) à E
retourner E
----
‣ Invariant trivialement préservé
‣ À chaque itération il existe une nouvelle arête à ajouter
‣ Quand |E|=|S|-1 E coïncide avec l’arbre couvrant minimal qui le contient
Arbres couvrants minimaux et algorithmes gloutons
‣ Choix (glouton) de l'arête à ajouter
Théorème. Soit G=(S,A) un graphe non-orienté pondéré connexe.
Soit E ⊆ A contenu dans un arbre couvrant minimal de G.

Soit (P, S\P) une partition de S (appelé coupure) telle qu’aucune arête de E ne traverse la coupure
( i.e. pour toute arête (u,v) de E soit {u,v} ⊆ P soit {u,v} ⊆ S\P ).

Soit (u,v) l'arête de poids minimal qui traverse la coupure (P, S\P). Alors E ∪ {(u,v)} est contenu
dans un arbre couvrant minimal de G.


3
2 6
4 P
5
8
E
9 8
7

S\P
Arbres couvrants minimaux et algorithmes gloutons
Preuve du théorème.
‣ Soit T=(S,A’) l’arbre couvrant minimal de G qui contient E.
‣ Si T contient l'arête minimale (u,v) qui traverse la coupure terminé.

‣ Supposer que T ne contient pas (u,v) : (u,v) crée un cycle dans T avec l’unique chemin γ qui
relie u et v dans T
‣ u ∈P, v∈ S\P il existe une arête (x,y) de γ u v
qui traverse la coupure
‣ (x,y) ∉ E P S\P
γ
‣ T’ :=(S, A’’) avec A’’ = A’ \ {(x,y)} ∪ {(u,v)}
x y
‣ E ⊆ A’ et (x,y) ∉ E E⊆ A’’ E ∪{(u,v)} ⊆ A’’

‣ T’ est un arbre couvrant : T’ est connexe et |A’’|=|A’| =|S|-1


‣ T’ est un arbre couvrant minimal : w(T’) = w(T) -w(x,y) + w(u,v) ≤ w(T)
( (u,v) est l'arête de poids minimal qui traverse la coupure ) ☐
Arbres couvrants minimaux et algorithmes gloutons
‣ Algorithme glouton générique pour le calcul d’un arbre couvrant minimal d’un graphe non-
orienté pondéré connexe G=(S,A,w)

E←∅

while ( |E| < |S|-1 ) do


// Invariant : E est un sous ensemble d'arêtes de G contenu dans un arbre couvrant minimal
Choisir un ensemble P tel que aucune arête de E traverse la coupure (P, S\P)
Soit (u,v) l'arête minimale qui traverse la coupure (P, S\P)
E← E ∪ {(u,v)}

endwhile
return E

‣ Algorithmes de Prim et Kruskal : variantes de l’algorithmes générique


‣ ils diffèrent dans le choix de la coupure
‣ ils trouvent efficacement l'arête minimale à l’aide de structures de données adaptées
Algorithme de Kruskal
‣ Choix de la coupure : P = une composante connexe de (S,E)
Remarque : dans l’algorithme générique, tant que |E| <|S|-1, le graphe (S,E) n’est pas connexe
‣ E ne traverse pas la coupure
‣ Plus précisément :
๏ chaque étape choisit l'arête e de poids minimal qui relie deux composantes connexes de E
e

3 C
E

5
8
9 8
7
6
Algorithme de Kruskal
‣ Choix de la coupure : P = une composante connexe de (S,E)
Remarque : dans l’algorithme générique, tant que |E| <|S|-1, le graphe (S,E) n’est pas connexe
‣ E ne traverse pas la coupure
‣ Plus précisément :
๏ chaque étape choisit l'arête e de poids minimal qui relie deux composantes connexes de E
e

3 C
E

5
8
9 8
7
S\C 6

๏ e est aussi l'arête de poids minimal qui traverse la coupure (C, S\C)
Algorithme de Kruskal
‣ Pour mémoriser les composantes connexes de E et rechercher efficacement la composante
d’un sommet : structure union-find
----------
Kruskal ( Graphe (S,A, w) )
E←∅

Pour tout v ∈ S creer-ensemble (v) ; // les composantes connexes initiales de E (les singletons)

while ( |E| < |S|-1 ) do


// Invariant : E est un sous ensemble d'arêtes de G contenu dans un arbre couvrant minimal
trouver l'arête (u,v) de poids minimal telle que find(u) ≠ find(v)
E← E ∪ {(u,v)} ; union (u, v)

endwhile
return E
-----------
‣ La correction dérive de la correction de l’algorithme générique et du choix de l'arête minimale
‣ La séquence d'arêtes de E peut être trouvée en une seule passe sur A trié par poids croissant
Algorithme de Kruskal
Kruskal ( Graphe (S,A, w) )
E←∅
Pour tout v ∈ S creer-ensemble (v) ; // les composantes connexes initiales de (S,E) (les singletons)

trier A par poids croissant


soit e1...em les arêtes triées par poids croissant
for (i ←1 to m) do
// Invariant : E est un sous ensemble d'arêtes de G contenu dans un arbre couvrant minimal
// Invariant : toutes les arêtes e1...ei-1 relient deux sommets de la même composante de (S,E)
(u,v) ← ei
if ( find(u) ≠ find(v) ) // (u,v) est l'arête de poids minimal qui relie deux composantes de (S,E)
E← E ∪ {(u,v)} ; union (u, v)

endif
endfor
return E

‣ variantes de l’algorithme basés sur les matroïdes


Algorithme de Kruskal
‣ Coût : O( |A| log |A| )
‣ Tri de A : O( |A| log |A| )
‣ |S| opérations créer-ensemble(v) : O(|S|)
‣ O(|A|) opérations de union/find sur |S| éléments : O( |A| log |S| )

‣ Optimisation : O(|A| log* |S| )


‣ Si les poids ne sont pas trop grands → tri en temps linéaire O(|A|)
‣ O(|A|) opérations de union/find sur |S| éléments sur une structure union-find
optimisée : O(|A| log*|S|)
Algorithme de Prim
‣ Choix de la coupure : P = sommets incidents à E
( et si E est vide P={s}, où s est un sommet arbitraire )
‣ E ne traverse pas la coupure 3
S\P

5
E
8

‣ Recherche de l'arête minimale qui traverse la coupure P


‣ trouver le sommet v∈S\P à coût minimal

‣ coût d’un sommet v∈S\P : (∞ si l’ensemble est vide)

‣ prédécesseur d’un sommet v∈S\P : un sommet u∈P tel que w(u,v) = coût de v
‣ À chaque étape le sommet à coût minimal de S\P est inséré dans P (et les coûts mis à jour)
‣ E est représenté implicitement : E={ (u, pred(u)) | u ∈ P et pred(u) ≠ nil }

‣ P est également représenté implicitement : S\P est stocké dans une file de priorité
Algorithme de Prim
Prim ( Graphe (S,A,w) )
// initialement P est vide tous les coûts sont ∞
Pour tout v∈ S c[v] ← ∞; π[v] ← NIL

choisir un sommet s
c[s] ←0
// S\P est stocké dans une file de priorité F :
F ← Creer-File-Priorité ( S ) // priorité de v : c[v]
while ( not Est-File-Vide(F) ) do
u ← Défiler-min(F); // u est implicitement inséré dans P
pour tout (u,v) ∈ A

if ( v ∈ F and w(u,v) < c[v] ) c[v] ← w(u,v) ; π[v] ← u endif


endwhile
return π
‣ Même structure que l’algorithme de Dijkstra : seulement la mise à jour des coûts change
Algorithme de Prim
‣ Propriétés de l’algorithme de Prim
À la fin de chaque itération de la boucle while soit P =S\F et E={ (v, π[v]) | v∈ P et π[v] ≠ nil }

A) À la fin de la première itération de la boucle while : E=∅

B) (Invariants) À la fin de chaque itération de la boucle while :


1) P = sommets incidents à E, et si E est vide P={s}
2) pour tout v ∈ F c[v] = min { w(u,v) | u ∈ P } et π[v] =u t.q u∈P et w(u,v) =c[v]

3) |E| =|P| -1
Preuve : Exercice
‣ Correction de l’algorithme de Prim
On montre qu’il est une instance de l’algorithme générique :
‣ Par A) E est initialement vide
‣ Par l’invariant 1) à chaque itération (P, S\P) est une coupure qui n’est pas traversée par E
‣ Par l’invariant 2) chaque itération insère dans E l'arête minimale qui traverse (P, S\P)
‣ À la fin de la dernière itération F=∅ P=S (par 3.) |E|=|S|-1
Coût de l’algorithme de Prim
(Même analyse que pour l’algorithme de Dijkstra, mais sur un graphe connexe |S| =O(|A|) )
‣ Opérations :
‣ Création d’une file de priorité contenant |S| éléments
‣ |S| opérations d’extraction du minimum
‣ |A| opérations de réduction de la priorité
( test v∈F en temps constant : maintenir un tableau de booléens )

‣ File implementée par tableau linéaire : O( |S|2 )


‣ Création de la file : O(|S|) , extraction du min : O(|S|), réduction de la priorité : O(1)
‣ File implementée par tas binaire : O( |A| log|S| )
‣ Création de la file : O(|S|), extraction du min : O(log|S|), réduction de la priorité : O(log|S|)
‣ File implementée par tas de Fibonacci : O(|S| log|S| + |A| )
‣ Création de la file : O(|S|) , extraction du min : O(log|S|), réduction de la priorité : O(1)
Flot maximal
Le problème du flot maximal
‣ Les graphes peuvent modéliser des réseaux de transport :
‣ les arcs représentent des connexions avec une capacité limitée.
‣ La capacité représente la vitesse de transfert le long de la connexion
(transfert de matériau (ex. liquide) , courant électrique, information etc.)
‣ Flot : quantité transférée par unité de temps
‣ Le réseau possède en général une source qui produit le flot , et un puits qui le consomme
‣ Sur tous les autres noeuds du réseau le flot obéit à une loi de conservation
‣ Problème du flot maximal (informellement): étant donné la capacité limitée des connexions,
quel est le flot maximal que la source peut générer et envoyer sur le réseau vers le puits?

Définition. Un réseau de transport est un graphe orienté G=(S,A) sans boucle avec
deux sommets particuliers, la source s ∈ S et le puits t ∈ S, et une fonction de capacité
c : S×S → R t.q. :
1. Si (u,v)∈ A c(u,v) ≥0 et si (u,v) ∉ A c(u,v) =0

2. Chaque sommet v ∈S est sur chemin de s à t ( G connexe )


Le problème du flot maximal
Définition. Un flot sur un réseau de transport G=(S,A) de fonction de capacité capacité c,
source s et puits t, est une fonction f : S×S → R qui satisfait :
1. Contrainte de capacité : pour tout u,v ∈ S 0 ≤ f(u,v) ≤ c(u,v)

2. Conservation du flot : pour tout v ∈ S\{s,t}

Valeur du flot : 6
|f| = 6
2 6

2 8
(le flot net sortant de la source) 2
s 5 t
2 2 4
6 1
5 1
3
|f| = 7
3

Problème du flot maximal : donné un réseau de transport, trouver le flot de valeur maximale
Le problème du flot maximal
Sans perte de généralité, le réseau G n’a pas d’arcs “anti-parallèles” (u,v) et (v,u)
‣ Chaque réseau avec arcs anti-parallèles peut être transformé en un réseau sans arcs anti-
parallèles qui est équivalent pour le problème du flot maximal

G G’
c

c c' c'

Pour chaque flot f sur G il existe un flot f’ sur G’ avec |f’|=|f| et vice-versa
Réseau résiduel
Soit G=(S,A) un réseau de transport de fonction de capacité c. Soit f un flot sur G
Capacité résiduelle cf : S×S → R
‣ La capacité résiduelle de u à v représente les unités de flot qui peuvent encore être envoyées
sur la connexion de u à v :
8
‣ Si (u,v) ∈ A cf (u,v) = c(u,v) - f(u,v) u v cf (u,v) = 5
3

‣ Si (v,u) ∈ A cf (u,v) = f(v,u) 8 cf (u,v) = 3


u v
3
(dans ce cas envoyer une unité de flot de u à v veut dire réduire le flot de v à u )
‣ Sinon cf (u,v) = 0

‣ Remarque : la capacité résiduelle est bien définie parce que soit (u,v) ∈ A soit (v,u) ∈ A

Réseau résiduel pour G et f : Gf =(S, Af) Af={ (u,v) ∈ S×S | cf(u,v) >0 }, capacité cf

‣ Remarque : Af ⊆ A U A- où A- = { (v,u) | (u,v) ∈ A }


Réseau résiduel
5
G, f 6
2 5

2 8
3
|f| = 5 s 5 t
2 4
6 1
3
3
3

1
Gf
5
3
2 5
1
s 5 t
2 3
3 1
3
3
Chemin améliorants
‣ (u,v) dans Gf cf (u,v) unités supplémentaires de flot peuvent être envoyées de u à v sur G

‣ un chemin p de s à t dans Gf min {cf (u,v) | (u,v) ∈ p } unités supplémentaires de flot


peuvent être envoyées de s à t sur G :

1 5

5 6
3 2 5
2 2 8
5 3-1
1 5
5
2 3 2 1 4
s 3 1 t s 6 1 t
3 3+1 1
3 3
3
Gf G, f’ |f’|=|f|+1

‣ Un chemin de s à t dans Gf est appelé chemin améliorant


Chemin améliorants
Plus formellement : Si p est un chemin de s à t dans Gf et c = min{ cf (u,v) | (u,v) ∈ p } on dénote
fp : S×S → R la fonction
c si (u,v) ∈ A et (u,v) ∈ p
fp(u,v) := -c si (u,v) ∈ A et (v,u) ∈ p |fp| := c
0 sinon

Lemme. Si f est un flot sur G et p est un chemin de s à t dans Gf alors f+fp est un flot sur G de
valeur |f|+|fp| > |f|

Preuve . Pour chaque sommet v on dénote


SD(v) ={ u | (v,u) ∈A et (v,u) ∈ p } SI(v) ={ u | (v,u) ∈A et (u,v) ∈ p }
ED(v) ={ u | (u,v) ∈A et (u,v) ∈ p } EI(v) ={ u | (u,v) ∈A et (v,u) ∈ p }
➡ fp satisfait la conservation du flot : pour tout v ∈ S

si v ∈ S\ {s,t} (parce que p est un chemin et v un sommet interne de p)


➡ f+fp satisfait la conservation du flot
Chemin améliorants
➡ Contrainte de capacité : pour tout u, v ∈S

- Si (u,v) ∈ A et (u,v) ∈ p (f+fp)(u,v) = f(u,v) + fp(u,v) = f(u,v) +c


0 ≤ f(u,v) +c ≤ f(u,v) + cf (u,v) = f(u,v) + c(u,v) - f(u,v) = c(u,v)

- Si (u,v) ∈ A et (v,u) ∈ p (f+fp)(u,v) = f(u,v) + fp(u,v) = f(u,v) - c


f(u,v) - c ≤ f(u,v) ≤ c(u,v)
f(u,v) - c ≥ f(u,v) - cf (v,u) = f(u,v) - f(u,v) = 0

- Sinon (f+fp)(u,v) = f(u,v) et 0≤ f(u,v) ≤ c(u,v)


➡ Valeur du flot :

Corollaire. Si Gf a un chemin améliorant, f n’est pas un flot maximal sur G


Algorithme de Ford-Fulkerson
Ford-Fulkerson ( Graphe(S,A), Sommet s, Sommet t )
initialiser f(u,v) ← 0 pour tout u,v ∈ S

tant qu’il existe un chemin améliorant p dans le réseau résiduel Gf


c ← min { cf (u,v) | (u,v) ∈ p }

f(u,v) ← f(u,v) + c pour tout (u,v) ∈ A t.q (u,v) ∈ p

f(u,v) ← f(u,v) - c pour tout (u,v) ∈ A t.q (v,u) ∈ p

retourner f
Correction
‣ À chaque étape f est un flot sur G de valeur strictement croissante (par le lemme sur les
chemins améliorants)
‣ En supposant que l’algorithme termine, il faut démontrer que :

s’il n’existe pas de chemin améliorant dans Gf f est un flot maximal sur G

Cela se démontre à l’aide de la relation entre flots et coupes dans un réseau


Coupes dans un réseau de transport
‣ Une Coupe dans un réseau G=(S,A) de source s et puits t est
une partition (E,T) de S telle que s ∈ E et t ∈ T

‣ Capacité de la coupe (E,T) : Flot net à travers la coupe (E,T) :

5
6
2 5

2 8
3
c(E,T) = 9 s 5 t f(E,T) = 5
2 4
6 1
3
3
3

‣ Pour toute coupe (E, T) f(E,T) ≤ c(E,T)


Coupes dans un réseau de transport
‣ Si f est un flot sur G=(S, A) et X,Y ⊆ S on dénote :

Flot net de X à Y :
‣ Remarque :
‣ Conservation du flot : f ( {v}, S ) = 0 pour tout v ∈ S\{s,t}
‣ Valeur du flot : |f| = f ( {s}, S )
‣ Propriétés du flot net:
1) f (X,X) = 0
2) f (X,Y) = - F(Y,X)
3) Si X1 ∩ X2 =∅ f (X1 ∪ X2, Y) = f (X1,Y) + f (X2,Y)

4) Si Y1 ∩ Y2 =∅ f (X,Y1 ∪ Y2) = f (X,Y1) + f (X,Y2)

Lemme. Si f est un flot sur G=(S,A) et X⊆ S

Preuve f (X, S\X) = f (X,S) - f (X,X) = f (X,S) ( par 3) et 1) ). Conclusion par 3) ☐


Flot maximal - Coupe minimale
Corollaire. Soit (E,T) une coupe dans G=(S,A) de source s et puits t, et soit f un flot dans G
f(E,T) = f ( {s}, S ) = |f|
Par conséquent |f| ≤ c(E,T).

Théorème (flot maximal - coupe minimale)


Si f est un flot sur un réseau de transport G=(S,A) de source s et puits t, alors
1. f est un flot maximal dans G ssi
2. |f| = c(E,T) pour une certaine coupe (E,T) de G ssi
3. Gf n’a pas de chemins améliorants
Flot maximal - Coupe minimale
Preuve.
1 3 parce qu’un chemin améliorant permet d’augmenter la valeur du flot

3 2. Supposer que Gf n’a pas de chemin améliorants.

- Soit E = { v ∈ S | v est accessible depuis s dans Gf } et T =S\E

- Il n’existe pas d’arc de E à T dans Gf


si (u,v) ∈ A avec u ∈ E et v ∈ T alors f(u,v) = c(u,v)
( sinon (u,v) avec capacité cf (u,v) = c(u,v) - f(u,v) > 0 serait dans Gf )
si (u,v) ∈ A avec u ∈ T et v ∈ E alors f(u,v) = 0
( sinon (v,u) avec capacité cf (u,v) = f(u,v) > 0 serait dans Gf )
|f| = f(E,T) = c(E,T)

2 1. Pour tout flot f’ sur G |f’| ≤ c(E,T) = |f|. ☐

Corollaire. Quand l’algorithme de Ford-Fulkerson termine, il renvoie un flot maximal


Analyse de l’algorithme de Ford-Fulkerson
‣ Initialisation : O(|A|)
‣ Coût d’une itération sur le réseau G=(S,A) avec flot courant f : O( |A| )
‣ Trouver un chemin améliorant dans Gf s’il existe : O( |S| + |A| ) = O( |A| )
➡ Gf représenté implicitement :
๏ stocker G par une structure à listes d'adjacence
๏ dans la même structure représenter également les arcs { (v,u) | (u,v) ∈ A }
( en les marquant comme absents de G )
๏ stocker les capacités c(u,v) et le flot f(u,v) avec les arcs (u,v) de G
➡ Modifier le parcours de G à partir de s :
๏ un arc (u,v) dans la structure est parcouru si
- (u,v) est dans G et f(u,v) < c(u,v) ou
- (v,u) est dans G et f(u,v) > 0
๏ dans le parcours stocker pour chaque sommet visité v un prédécesseur u de v dans
un chemin depuis s, et cf (u,v)
‣ Calculer min {cf (u,v) | (u,v) ∈ p } et mettre à jour le flot f : O( |A| )
Analyse de l’algorithme de Ford-Fulkerson
‣ Capacités entières
Soit G=(S,A) un réseau de transport avec capacité des arcs entière.
‣ Soit f * le flot maximal (coupe minimale) sur G
‣ La valeur du flot maximal croît strictement à chaque itération au plus |f *| itérations

‣ Coût de l’algorithme : O(|f *| |A| )

‣ Capacités rationnelles : multiplier toutes les capacités par un facteur capacités entières

‣ Capacités arbitraires (réelles) : l’algorithme peut ne pas terminer si le choix du chemin


améliorant est arbitraire
‣ Si le chemin améliorant est choisi comme le plus court chemin (parcours en largeur)
l’algorithme termine, et a un coût O(|S| |A|2)
‣ Cette version est connue comme algorithme d’ Edmonds-Karp
Analyse de l’algorithme d’Edmonds-Karp
‣ On dénote δf (s,v) la longueur du plus court chemin de s à v dans Gf (poids des arcs unitaires)
‣ Un arc e d’un chemin améliorant p de Gf devient critique si p est choisi par une itération de
l’algorithme et cf (e) = min { cf (u,v) | (u,v) ∈ p }

‣ Propriétés de l’algorithme :
Soit f et f’ les flots avant et après une itération de l’algorithme d’Edmonds-Karp sur G=(S,A).
Soit p le chemin améliorant choisi par l'itération.
1) Si (u,v) est un arc critique de p, (u,v) est absent de Gf’
- si (u,v) ∈ A
cf (u,v) = c(u,v) - f(u,v) et f’(u,v) = f(u,v) + cf (u,v) = c(u,v) cf’ (u,v) = c(u,v) - f’(u,v) = 0

- si (v,u) ∈ A cf (u,v) = f(v,u) et f’(v,u) = f(v,u) - cf (u,v) = 0 cf’ (u,v) = f’(v,u) = 0

2) Si (u,v) est absent de Gf et (u,v) est présent dans Gf’ , alors p contient (v,u)
‣ Si (u,v) ∈ A cf(u,v) = 0 f(u,v) = c(u,v)
cf’(u,v) > 0 f’(u,v) < c(u,v) f’(u,v) < f(u, v) (v, u) ∈ p

‣ Si (v,u) ∈ A cf(u,v) = 0 f(v,u) = 0


cf’(u,v) > 0 f’(v,u) >0 f’(v,u) > f(v,u) (v,u) ∈ p
Analyse de l’algorithme d’Edmonds-Karp
3) Pour tout v ∈ S δf’ (s,v) ≥ δf (s,v)

Par l’absurde
➡ Soit v le sommet à δf’ (s,v) minimal tel que δf’ (s,v) < δf (s,v) ( v ≠ s et δf’ (s,v) doit être fini)
➡ Soit γ le plus court chemin de s à v dans Gf’, et soit u le prédécesseur de v dans γ
δf’ (s,u) = δf’ (s,v)-1
δf’ (s,u) ≥ δf (s,u)
(sinon v ne serait pas le sommet à δf’ (s,v) minimal dont la distance a été diminuée)
➡ (u,v) est absent de Gf :
( sinon δf (s,v) ≤ δf (s,u) +1 ≤ δf’ (s,u)+1 = δf’ (s,v) )

➡ (u,v) dans Gf’ et (u,v) absent de Gf (propriété 2) p contient (v,u)

➡ p = plus court chemin de s a t dans Gf il existe un plus court chemin s ↝↝v→ u dans Gf

➡ δf (s,v) = δf (s,u) -1 ≤ δf’ (s,u)-1 = δf’(s,v) - 2 contradiction


Analyse de l’algorithme d’Edmonds-Karp
Théorème
L’algorithme d’Edmonds-Karp sur un réseau de transport G=(S,A) exécute O(|S||A|) itérations.
Son coût est donc O(|S||A|2).

Preuve
‣ On démontre que pour tout (u,v) ∈ A∪A-, (u,v) peut devenir critique au plus |S|/2 fois

‣ À chaque itération au moins un arc (u,v) ∈ A∪A- devient critique

au plus 2|A|· |S| /2 = |A||S| itérations.

➡ Supposer que (u,v) devient critique à une itération donnée.


➡ Soit f le flot au début de cette itération.
δf (s,v) = δf (s,u)+1
(puisque le chemin améliorant qui contient (u,v) est un plus court chemin)
➡ Par la propriété 1) (u,v) disparait du réseau résiduel à la fin de cette itération
➡ avant de redevenir critique, (u,v) doit réapparaître dans le réseau résiduel.
Analyse de l’algorithme d’Edmonds-Karp
➡ Par la propriété 3) dans l'itération qui fait réapparaître (u,v),
l’arc (v,u) est dans le chemin améliorant choisi
➡ Soit f’ le flot au début de cette itération
δf’ (s,u) = δf’ (s,v)+1
(puisque le chemin améliorant qui contient (v,u) est un plus court chemin)
➡ Par la propriété 3) δf’ (s,v) ≥ δf (s,v)

➡ δf’ (s,u) = δf’ (s,v)+1 ≥ δf (s,v) +1 = δf (s,u) +2

➡ Entre deux itérations qui rendent un même arc (u,v) critique, la distance de u depuis
s dans le réseau résiduel augmente d’au moins 2

➡ De plus à chaque itération qui rend (u,v) critique δf (s,u) ≤ |S|-2


(puisque le plus court chemin de s à u ne passe pas par v)

➡ (u,v) peut devenir critique au plus (|S|-2)/2 +1 fois.


D’autres algorithmes pour le flot maximal
Algorithmes de préflot
‣ plusieurs implémentations :

‣ Algorithme de Goldberg O( |S|2 |A|)

‣ Algorithme de ré-étiquetage vers l’avant : O(|S|3)


Bibliographie
‣ T.H. Cormen, C. E. Leiserson, R. L. Rivest and C. Stein.
Introduction to Algorithms. 3e édition, The MIT Press 2009
‣ S. Dasgupta, C. Papadimitriou, U.Vazirani Algorithms McGraw Hill 2006
disponible en ligne http://www.cs.berkeley.edu/~vazirani/algorithms/
‣ Christine Froidevaux, Marie-Claude Gaudel, Michèle Soria
Types de données et algorithmes. Ediscience International, 1993
‣ Aho, Hopcroft, Ullman
The Design and Analysis of Computer Algorithms Addison-Wesley 1974
‣ J. Kleinberg, E. Tardos Algorithm Design Addison-Wesley 2005

Graphes comme type abstrait (statique et dynamique) :


‣ Christine Froidevaux, Marie-Claude Gaudel, Michèle Soria
Types de données et algorithmes. Ediscience International, 1993

Vous aimerez peut-être aussi