Differences

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

Link to this comparison view

sda-ab:laboratoare:10 [2021/02/24 18:03]
ruben_gilian.udroiu
sda-ab:laboratoare:10 [2021/04/26 09:58] (current)
ruben_gilian.udroiu [1. Obiectivele laboratorului]
Line 1: Line 1:
 ===== Laboratorul 9: 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.1614182588.txt.gz · Last modified: 2021/02/24 18:03 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