Differences

This shows you the differences between two versions of the page.

Link to this comparison view

sda-ab:laboratoare:10 [2020/12/24 13:04]
ruben_gilian.udroiu
sda-ab:laboratoare:10 [2021/04/26 09:58] (current)
ruben_gilian.udroiu [1. Obiectivele laboratorului]
Line 1: Line 1:
-===== Laboratorul ​10: Grafuri - Drumuri de cost minim =====+===== 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 **[[https://​github.com/​sda-ab/​lab-09-tasks|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 == 
 + 
 +<note tip>​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.</​note>​ 
 + 
 +====== 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. 
 +<note important>​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)**.</​note>​ 
 + 
 +{{ :​sda-ab:​laboratoare:​dijkstra_animation.gif?​400 |}} 
 + 
 +<note tip>​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**.</​note>​ 
 + 
 +Pasii: 
 + 
 +<code c> 
 +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) 
 +</​code>​ 
 +<note tip>​**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.</​note>​ 
 + 
 +=== 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) 
 +<note tip>​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) 
 +</​note>​ 
 +<code c> 
 +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. 
 +</​code>​ 
 + 
 +====== 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**. 
 +<code c> 
 +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 
 +</​code>​ 
 + 
 +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). 
 + 
 +<note tip> 
 +  ***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 
 +</​note>​ 
 + 
 +**OBS** - Pentru obţinerea nodurilor ce formează drumul de cost minim de la u la v, putem folosi următoarea secvenţă:​ 
 + 
 +<code c> 
 +funcţie afişareDrum(u,​ v) 
 +1. dacă u == v 
 + 1. print(v); STOP 
 +2. print(u); 
 +3. afişareDrum(next[u][v],​ v); 
 +</​code>​ 
 +<note tip>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</​note>​ 
 + 
 +====== 5. Observatii finale ====== 
 + 
 +<note important>​Cei trei algoritmi functioneaza bine atunci cand **toate muchiile** au **cost pozitiv**</​note>​ 
 + 
 +<note warning>​ 
 +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. 
 +</​note>​ 
 + 
 +<note important>​O muchie cu cost negativ este echivalentă cu 2 arce cu cost negativ şi implică existenţa unui ciclu cu cost negativ.</​note>​ 
 + 
 +<note warning>​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)</​note>​ 
 + 
 +====== 6. Exercitii propuse ====== 
 + 
 +  - Daţi un exemplu de graf orientat cu un singur arc cu cost negativ pentru care algoritmul lui Dijkstra dă rezultate greşite. 
 +  - 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. 
 +  - 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. 
 +  - Verificaţi dacă un graf conţine cicluri negative. 
 +  - 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.1608807866.txt.gz · Last modified: 2020/12/24 13:04 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