Le problème Algorithme de Bellman-Ford
Transcription
Le problème Algorithme de Bellman-Ford
20052006 Algorithmique Avancée et Complexité Master 1 d'Informatique S.Tison UFR IEEA Graphes: Chemins Minimaux Le problème On se place dans le cas des graphes pondérés: un graphe pondéré est un triplet et P (S, A, P ) où (S, A) est un graphe est une fonction qui associe à chaque arc un poids réel, pas forcément positif. c = v0 v1 , v1 v2 , ....vk−1 vk dans un graphe pondéré est juste la somme des poids des P P (c) = k−1 i=0 P (vi vi+1 ). Si le poids d'un arc est 1, on retrouve la notion de longueur dans Le poids d'un chemin arcs le composant: un graphe non pondéré. Le problème général est donc: étant donnés deux noeuds a et b, si il existe. On notera δ(s, b) s et b, trouver le chemin de poids minimal reliant le poids minimal d'un chemin reliant a et b. On parlera de: . problème à source et destination uniques quand on veut juste calculer pour un seul couple d'états . problème à source unique (resp. destination unique) si, pour un chemin correspondant) pour tout b (resp. ( et un chemin correspondant) s (resp. b) donné, on veut calculer δ(s, b) ( et s), .de problème toutes sources, toutes destinations si on on veut calculer pour tous les couples δ(s, b) (s, b), δ(s, b) ( et un chemin correspondant) (s, b). Remarques: . Si il n'y a pas de chemin de s à b, on prendra comme convention δ(s, b) = +∞ . Il se peut qu'il n'y ait pas de poids minimal, si le graphe contient des circuits négatifs ou absorbants, i.e. dont la somme des poids est strictement négative. On aura alors δ(s, b) = −∞ pour certains sommets. Algorithme de Bellman-Ford On se place dans le cas du problème à source unique. G = (S, A, P ) est un graphe orienté pondéré, s la source. L'algorithme est basé sur quelques observations très simples: .pour tout arc u, v , δ(s, v) ≤ δ(s, u) + p(u, v) .Si il n'y a pas de circuit négatif dans le graphe, on a: δ(s, v) = 0 si s = v δ(s, v) = min{δ(s, u) + p(u, v)/(u, v) ∈ A} si s diérent de v . .Tout chemin minimal -si il existe- de s à v comporte au plus |S| − 1 arcs. Le cas général //G=(S,A,P) orienté pondéré //s source fixée Algorithme de Bellman-Ford{ //init pour tout v D[v]=+ ∞ ; D[s]=0; //fin init pour i de 1 à |S|-1 //ETAPE //invariant: D(v) supérieur ou égal à δ(s, v) //invariant: D(v) inférieur ou égal au poids de tout chemin de s à v // d'au plus i-1 arcs } pour tout (u,v) dans A si (D[v]> D[u]+p(u,v)) alors D[v]= D[u]+p(u,v); //relaxation de l'arc (u,v) fin pour; //fIN ETAPE fin pour; //détection des circuits négatifs pour tout (u,v) dans A si (D[v]> D[u]+p(u,v)) alors raise ``CIRCUIT Négatif''; fin pour; // G est sans circuit négatif // pour tout v D[v]=δ(s, v); La complexité de l'algorithme sera donc en O(|S|∗|A|), en supposant qu'on peut énumérer les arcs en O(|A|) Q?: qu'est-ce que cela présuppose sur l'implémentation choisie? Remarque: on pourrait aussi répéter Etape jusqu'à obtenir un point xe. On arrête dès qu'une étape n'a rien modié. Pour certains graphes, le nombre d'étapes eectuées pourra être nettement inférieur à |S| − 1. Q?: pour lesquels? Le cas acyclique Dans le cas où le graphe est acyclique, on peut simplier l'algorithme. Une version naïve serait: //G orienté sans circuit D(s,but){ si s=but alors 0 sinon min(chemin(s,noeud) + D(noeud, but)) pour tout arc (noeud,but) de A } Si le graphe n'a pas de circuit l'arrêt et la correction sont donc assurés. sauf dans des cas très particuliers (lesquels?), plusieurs appels à D(s, noeud) On peut alors remarquer que seront eectués avec les mêmes paramètres. On peut donc appliquer le principe de la programmation dynamique, ce qui conduità: //DejaCalcule table de booléens indexée par S //TD table de poids indexée par S //si DejaCalcule[v]=True, TD[v]=δ(s, v) Drecdyn(b){ //retourne δ(s, b) si non DejaCalcule[but] alors //on calcule! {DejaCalcule[but]=True; si but=s alors TD[but]=0; sinon TD[but]=min(Drecdyn(noeud) + p(noeud, but)) return TD[but]} Dans ce cas la complexité sera de O(|A| + |S|), pour tout arc (noeud,but) de A}; si on suppose par exemple que le graphe est implémenté par listes d'adjacence (donc l'accès aux arcs arrivant dans un sommet se fait en temps proportionnel au nombre d'arcs). Exercice: comment programmer cet algorithme de façon itérative? Algorithme de Dijkstra On se place toujours dans le cas d'un graphe pondéré orienté; On suppose de plus que les poids des arcs sont positifs ou nuls. L'algorithme de Dijkstra est de type glouton; à chaque étape, il ajoute un sommet à l'ensemble des noeuds pour lesquels δ(s, n) est déjà calculé, en le sélectionnant de façon gloutonne. 2 //G=(S,A,p) graphe pondéré avec poids >=0 Dijkstra{ pour chaque noeud D(noeud)=+∞; D(source)=0; pour chaque noeud Marquer noeud NonDécouvert; Marquer source Adévelopper; tant que Adévelopper non vide // //choix glouton prendre n dans Adévelopper tel que D(n) soit minimum. pour tout arc (n,b) dans A { si b NonDécouvert alors {Marquer b Adévelopper; D(b)=D(n)+p(n,b);Pred(b)=n;} sinon si b Adévelopper et D(b)>D(n)+p(n,b) alors {D(b)=D(n)+p(n,b);Pred(b)=n;} fin pour; marquer n Exploré; fin tant que; //pour tout noeud b D(b)=δ(s, b) } Correction: On peut montrer la correction en utilisant un raisonnement de type glouton. Soit l'énumération des sommets δ(source, b) b δ(source, b) minimal. Si l'algorithme D(b) soit diérent de δ(source, b). Il est dans l'ordre de minimal tel que n'était pas correct, soit le sommet b de assez facile de montrer qu'il existerait n de poids inférieur sur lequel l'algorithme se tromperait, d'où la contradiction. On peut aussi montrer que les propriétés suivantes constituent l'invariant de la boucle principale: .si b est Exploré D(b) = δ(source, b). .si b est Adévelopper D(b) est le poids minimal d'un chemin de source à b ne passant que par des noeuds Exploré. .Tout successeur d'un noeud Exploré est Exploré ou Adévelopper. Exercice: Compléter la preuve... Complexité: La complexité dépend de la structure choisie pour l'ensemble des noeuds Adévelopper. Les opérations à réaliser sont: Ajouter un élément, Extraire un élément de poids minimum, Modier le poids d'un élément. Un élément est ajouté et extrait une seule fois, mais par contre il peut être modié plusieurs fois. Si on implémente la structure via un tableau indexé par les numéros de sommets, Ajouter, Modier seront en O(1) et Extraire_Min sera en O(|S|). On aura donc un coût total en O(|S|2 +|A|) soit O(|S|2 ) (en supposant que l'accès aux arcs arrivant dans un sommet se fait en temps proportionnel au nombre d'arcs). On peut aussi implémenter la structure via un minimier(tas) binaire. Ajouter, Modier et Extraire_Min seront en O(log(|S|)). On aura donc un coût total en O(|S|log(|S|) + |A|log(|S|), soit O(|A|log(|S|)) si le graphe est connexe. Cette implémentation sera donc intéressante par rapport à la première, si le graphe est peu dense. Q?: Comment implémenter un minimier? On peut encore améliorer et obtenir une complexité en O(|A| + |S|log(|S|)) noeuds Adévelopper comme un tas de Fibonacci qui permet un coût amorti en en implémentant l'ensemble des O(1) pour Modier. Remarque: Cet algorithme qui est de type glouton - à chaque étape, on choisit le sommet qui respecte le critère glouton (n tel que D(n) minimum)- peut être vu comme un parcours générique de graphe où la gestion des noeuds Adévelopper se fait via une le de priorité. Le cas toutes sources, toutes destinations Le graphe considéré est orienté et pondéré. On supposera que le graphe ne contient pas de circuit négatif. Les sommets seront numérotés de d'adjacence p. p(i, j) sera +∞ 1 |S|. (i, j) à si On supposera de plus que le graphe est ici représenté par une matrice n'est pas un arc, 0 si 3 i = j, le poids de l'arc (i, j) sinon. Un premier algorithme L'idée est simplement de calculer le poids minimal des chemins de longueur 1, puis 2... Soit dl (i, j) le poids minimal d'un chemin de i à j de longueur au plus l . On a donc: d0 (i, i) = 0, d0 (i, j) = +∞ si i 6= j . dl (i, j) = min(dl−1 (i, j), min(k,j)∈A (dl−1 (i, k) + p(k, j))) δ(i, j) = d|S|−1 (i, j): chemin de poids minimal ne contient pas de circuit et donc est de longueur au plus |S| − 1. Calcul basique: On peut représenter les valeurs dl (i, j) dans une matrice carrée Dl . Le calcul de Dl 3 4 fonction de Dl−1 sera eectué en O(|S| ). Le calcul total est donc en O(|S| . Enn, on peut remarquer que si le graphe ne contient pas de circuits négatifs, alors Calcul amélioré: un en on peut remarquer que ce calcul peut être vu comme un calcul de multiplication de min, la multiplication l'addition. La matrice Dl peut donc être vue l comme P , avec P la matrice des poids. On peut alors utiliser une technique type exponentation indienne |S|−1 en O(log(|S|) multiplication de matrices, soit un coût total en O(|S|3 log(|S|). pour calculer P matrices, l'addition usuelle étant ici le Exercice: écrire l'algorithme amélioré, rééchir à pourquoi ça marche... L'algorithme de Floyd-Warshall L'idée est un peu similaire. Mais au lieu de faire varier la longueur des chemins, on va faire varier les som- mets intermédiaires autorisés; on s'intéresse d'abord aux chemins sans sommet intermédiaire, puis de sommet intermédiaire éventuel ak (i, j) sera part i et j ). 1, puis 1 et 2... le poids minimal d'un chemin de i à j n'utilisant que des sommets de numéro au plus k (mis à On aura donc: a0 (i, j) = p(i, j) ak (i, j) = min(ak−1 (i, j), ak−1 (i, k) + ak−1 (k, j)) Cette récurrence est basée sur l'hypothèse que le circuit ne contient pas de circuit négatif: un chemin de poids minimal ne passe pas deux fois par le même sommet; donc, si k est utilisé dans le chemin, il est utilisé une seule fois. Les valeurs qui nous intéressent sont les En remarquant que ak (i, k) = ak−1 (i, k) a|S| (i, j): a|S| (i, j) = δ(i, j). et ak (k, j) = ak−1 (k, j), on aboutit à l'algo suivant: //G graphe orienté pondéré sans circuit négatif //P matrice des poids de G Algo Floyd-Warshall{ //A_0=P pour i de 1 à |S| pour j de 1 à |S| a[i][j]= P[i][j]; //fin init pour k de 1 à |S| //calcul de a_k pour i de 1 à |S| pour j de 1 à |S| si a[i][j]>a[i][k]+a[k][j] alors a[i][j]=a[i][k]+a[k][j]; //pour tout i et j, a[i][j]=δ(i, j) } Le coût total est donc en Exercice: O(|S|3 ). ) on ne s'est intéressé dans cette section qu'au calcul des poids minimaux, et non des chemins correspondants. Enrichir les deux algorithmes précedents pour pouvoir aussi calculer une représentation ecace de l'arbre des chemins de poids minimal. 4