Laboratorul 9: Grafuri - Drumuri de cost minim

1. Obiectivele laboratorului

  • Înțelegerea ideii de cost și de drum minim într-un graf
  • Prezentarea algoritmilor care calculează drumul de cost minim
  • Înțelegerea aplicațiilor practice ( de exemplu: gasirea drumului minim intre doua locatii, rutarea in cazul retelelor de calculatoare)

Structura laboratorului se gaseste in acest link.

2. Notiuni teoretice

2.1. Costul unei muchii

La fel ca la arbori de acoperire, presupunem că fiecare muchie are un cost de parcurgere.

2.2. Costul unei drum (drumul de cost minim)

Într-un graf, orice drum este definit de o succesiune de muchii (cu proprietatea că, pentru oricare două muchii consecutive din succesiune, nodul destinaţie/de sosire al primei muchii este acelaşi cu nodul sursa/de plecare al celei de-a doua muchii).

Costul unui drum va fi definit ca suma costurilor muchiilor ce compun acel drum.

Fie un nod sursă(S) şi un nod destinaţie(D). Pot exista mai multe drumuri de la S la D(drumuri care au S = primul nod, D = ultimul nod), iar drumul de cost minim de la S la D va fi cel mai ieftin(cu costul cel mai mic) dintre acestea. (OBS - pot exista mai multe drumuri de cost minim de la S la D).

2.3. Legatura muchii-arce

Orice graf neorientat este echivalent cu un graf orientat dacă înlocuim fiecare muchie cu două arce(de acelaşi cost, câte un arc pentru fiecare sens). Algoritmii următori pot fi folosiţi(în limita observaţiilor finale) pe grafuri orientate la fel ca pe grafuri neorientate.

3. Drumuri de cost minim cu sursa unica

Următorii algoritmi caută drumurile de cost minim de la un singur nod(sursă) la toate celelalte noduri. Rezultatul acestor algoritmi este un arbore cu drumuri de cost minim, unde:

  • nodul sursă(S) este rădăcina arborelui;
  • toate nodurile din graf sunt în arbore;
  • pentru orice nod destinaţie(D), costul drumului din arbore de la rădăcina S la D este drum de cost minim(de la S la D) în graf.

Algoritmi:

  • Algoritmul lui Dijkstra
  • Algoritmul Bellman-Ford

3.1 Algoritmul lui Dijkstra

Un algoritm pentru determinarea celei mai scurte cai de la un varf la oricare alt varf e algoritmul lui Dijkstra. (OBS - acesta nu functioneaza pentru grafuri cu circuite negative(in cazul in care exista circuite de cost negativ, costum minim pentru orice varf care apartine circuitului, ar putea fi considerat -INF, iar algoritmul nu detecteaza aceste circuite)

Algoritm:

  • iniţial, toate nodurile sunt neexplorate şi vom construi arborele, începând de la nodul S;

atribuim un posibil cost(o estimare a distanţei) pentru fiecare nod. (iniţial, S are costul 0, toate celelalte noduri au costul infinit);

  • la fiecare pas, alegem cel mai bun candidat dintre nodurile neexplorate, urmând să îl explorăm(să îi evaluăm vecinii), iar acel candidat va rămâne în arbore;
  • la fiecare explorare(evaluare a vecinilor), dacă găsim o nouă estimare de cost mai bună decât cea precedentă, folosim, mai departe, noua estimare. Dacă dorim să ţinem evidenţa muchiilor folosite, actualizăm şi nodul părinte al vecinului respectiv.

OBS: atunci cand evaluam vecinii unui nod - pentru fiecare nod vecin(V) al lui C, noul cost posibil va fi costul drumului S-V(de la S la V) care trece prin C, mai exact - suma dintre costul drumului S-C şi costul muchiei (C,V).

Construind astfel algoritmul, este garantat că, în momentul în care explorăm un nod(C), estimarea pentru costul drumului S-C este chiar costul minim.

Pasii:

1. Declarăm două mulţimi:
	mulţimea nodurilor neexplorate(MN), iniţial MN conţine toate nodurile;
	mulţimea nodurilor explorate(ME) ce compun arborele, iniţial ME = vidă;
2. Atribuim fiecărui nod o estimare iniţială a costului:
	0 pentru nodul sursă(S);
	infinit pentru toate celelalte;
3. Cât timp există noduri în MN
	1. Alegem, din MN(nodurile neexplorate), nodul cu cel mai mic cost estimat
	 îl numim C(nodul curent)
	2. pentru fiecare din vecinii lui C care se află în MN 
	3. calculăm noua estimare de cost = cost(drumul S-C) + cost(muchia (C,V));
	4. comparăm noua estimare cu vechiul cost(drumul S-V):
	 dacă noul cost e mai bun
		1. actualizăm cost(drumul S-V) = noul cost;
		2. actualizăm parinte(V) = C; (pentru păstrarea muchiei folosite)
	 altfel păstrăm vechiul cost
	5. Marcăm nodul C ca explorat: îl eliminăm din MN şi îl adăugăm în ME.
	 (Nu va mai fi verificat)

OBS - algoritmul nu salveaza si calea de la nodul sursa spre celalalte varfuri. Pentru a putea face acest lucru, se poate introduce un vector pred (predecesor) care stocheaza predecesorul pentru fiecare varf adaugat in ME.

3.2 Algoritmul Bellman-Ford

Algoritmul Bellman-Ford determina drumurile de cost minim de la un varf sursa la fiecare dintre celalalte varfuri, identificand eventualele circuite de cost negativ.

Pasii sunt similari ca cei ai algoritmului Dijkstra:

  • vom construi arborele, începând de la nodul S;
  • atribuim un posibil cost(o estimare a distanţei) pentru fiecare nod. (iniţial, S are costul 0, toate celelalte noduri au costul infinit);
  • la fiecare evaluare, dacă găsim o nouă estimare de cost mai bună decât cea precedentă, folosim, mai departe, noua estimare. Dacă dorim să ţinem evidenţa muchiilor folosite, actualizăm şi nodul părinte.
  • funcţia de estimare a costului este definită la fel ca la algoritmul lui Dijkstra(costul drumului de la S la nodul respectiv)

Diferenta care apare este cea la alegerea nodurilor pentru care facem evaluarea :

  • Algoritmul nu are preferinţe pentru anumite noduri şi nu extrage, la fiecare pas, cel mai bun candidat.
  • În schimb, acest algoritm evaluează toate muchiile la un pas. (presupunand ca nu se repeta muchii lungimea oricărui drum de la S la alt nod va fi maxim N-1, unde N = numarul de noduri din graf)

1. Atribuim fiecărui nod o estimare iniţială a costului:
	0 pentru nodul sursă(S);
	infinit pentru toate celelalte;
2. Executăm de N-1 ori:
	1. Pentru fiecare pereche (u, v) a.i. există muchie de la u la v
		1. calculăm noua estimare de cost = cost(drumul S-u) + cost(muchia (u,v));
		2. comparăm noua estimare cu vechiul cost(drumul S-v):
		 dacă noul cost e mai bun
			1. actualizăm cost(drumul S-v) = noul cost;
			2. actualizăm parinte(v) = u; (pentru păstrarea muchiei folosite)
		 altfel păstrăm vechiul cost
3. Dupa executare, pentru identificarea circuitelor de cost negativ, se verifica fiecare pereche (u,v) a.i exista muchie de la u la v cu conditia ca noul cost sa fie mai bun.  In cazul in care exista, se va returna valoarea 0 si 1, in caz contrar.

4. Drumul de cost minim intre oricare 2 noduri

Algoritmul Floyd-Warshall, caută cel mai scurt drum de la orice nod sursă(S) la fiecare nod destinaţie(D).

Rezultatul algoritmului este o matrice N x N(unde N = |V|, numărul de noduri), iar valorea din matrice de la poziţia [i][j] va fi costul minim pentru drumul i-j. Fie această matrice numită dist.

1. Declarăm matricele:
	dist, matrice N x N şi o iniţializăm dist[i][j] = infinit, pentru orice i şi j
	next, matrice N x N în care vom salva prima muchie din drumul i-j de cost minim
	//next este necesar doar în cazul în care ne interesează muchiile folosite
//pasul k = 0
2. Pentru fiecare nod v
	1. dist[v][v] = 0;
3. Pentru fiecare pereche (u,v) a.i. există muchie de la u la v
	1. dist[u][v] = costMuchie(u,v);
	2. next[u][v] = v; //pentru urmărirea muchiilor ce compun drumul
//am terminat pasul k = 0
4. Pentru fiecare pas k (de la 1 la N)
	1. Pentru fiecare nod i (de la 1 la N)
		1. Pentru fiecare nod j (de la 1 la N)
			1. calculăm costul nou = dist[i][k] + dist[k][j];
			2. comparăm costul nou cu costul vechi = dist[i][j]
			 dacă e mai bun costul nou:
				1. dist[i][j] = costul nou;
				2. next[i][j] = next[i][k]; //pentru urmărirea muchiilor
			 altfel păstrăm costul vechi

Pentru simplitate, algoritmul numerotează nodurile de la 1 la N şi foloseşte următoarea construcţie: costMinim(i,j,k) = cel mai ieftin drum care pleaca din i, ajunge in j, iar nodurile parcurse intermediar fac parte din {1,2,3…k} (primele k noduri).

  • costMin(i,j,0) = costMuchie(i,j) (dacă există muchie de la i la j) sau infinit(altfel);
  • costMin(i,j,N) = drumul de cost minim de la i la j

OBS - Pentru obţinerea nodurilor ce formează drumul de cost minim de la u la v, putem folosi următoarea secvenţă:

funcţie afişareDrum(u, v)
1. dacă u == v
	1. print(v); STOP
2. print(u);
3. afişareDrum(next[u][v], v);

OBS: Algoritmul Floyd-Warshall poate detecta cicluri cu cost negativ: putem verifica, la fiecare pas, dacă avem o valoare negativă pe diagonala matricei dist. Dacă găsim cel puţin o valoare negativă, graful conţine măcar un ciclu cu cost negativ

5. Observatii finale

Cei trei algoritmi functioneaza bine atunci cand toate muchiile au cost pozitiv

Drumul de cost minim NU este bine definit atunci când există cicluri cu cost negativ.

  • putem ajunge de la un nod la acelaşi nod, folosind acelaşi ciclu de oricâte ori ⇒ (costMinim = -infinit);
  • în aceste cazuri, nu putem pune problema găsirii drumurilor de cost minim.

O muchie cu cost negativ este echivalentă cu 2 arce cu cost negativ şi implică existenţa unui ciclu cu cost negativ.

Algoritmii Bellman-Ford şi Floyd-Warshall funcţionează pe grafurile cu arce cu cost negativ, atâta timp cât drumurile de cost minim sunt bine definite(fără cicluri cu cost negativ)

6. Exercitii propuse

  1. Daţi un exemplu de graf orientat cu un singur arc cu cost negativ pentru care algoritmul lui Dijkstra dă rezultate greşite.
  2. Cum putem folosi algoritmul lui Dijkstra sau algoritmul Bellman-Ford pentru a obţine aceleaşi rezultate ca algoritmul Floyd-Warshall(drumul de cost minim pentru toate perechile de noduri)? (OBS -Ne limităm la grafurile pe care merg toţi cei trei algoritmi.
  3. Implementaţi unul din algoritmi pentru a calcula drumurile de cost minim de la un nod sursă la toate celelalte noduri într-un graf cu toate muchiile/arcele cu cost pozitiv.
  4. Verificaţi dacă un graf conţine cicluri negative.
  5. Cum găsiţi(mai rapid decât cu cei 3 algoritmi prezentaţi) drumul de cost minim de la S la D într-un graf în care toate muchiile au acelaşi cost(1)? Cum adaptaţi soluţia în cazul în care toate muchiile au costul 1 sau 2?
sda-ab/laboratoare/10.txt · Last modified: 2021/04/26 09:58 by ruben_gilian.udroiu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0