This shows you the differences between two versions of the page.
pa:laboratoare:shortest-paths-problem [2022/04/20 02:08] darius.neatu created |
pa:laboratoare:shortest-paths-problem [2023/05/08 22:20] (current) radu.iacob [[Studiu de caz] k surse / destinații] |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | TODO Darius | + | ====== Shortest-paths problem ====== |
+ | |||
+ | ===== Importanţă – aplicaţii practice ===== | ||
+ | |||
+ | Algoritmii pentru determinarea drumurilor minime au multiple aplicații practice si reprezintă clasa de algoritmi pe grafuri cel mai des utilizată. Câteva exemple de aplicații sunt: | ||
+ | |||
+ | * Rutare în cadrul unei rețele de calculatoare. | ||
+ | * Găsirea drumului minim dintre două locații (Google Maps, GPS etc.). | ||
+ | * Stabilirea unei agende de zbor în vederea asigurării unor conexiuni optime. | ||
+ | |||
+ | ===== Shortest-paths problem ===== | ||
+ | |||
+ | Puteți consulta capitolele **Single-Source Shortest Paths** și **All-Pairs Shortest Paths** din **Introduction to Algorithms** [0] pentru mai multe definiții formale. Această secțiune sumarizează principalele notații folosite în laboratoarele de PA.- [[#shortest-paths-problem|Shortest-paths problem]] | ||
+ | |||
+ | Cele mai uzuale notații din laboratoarele de grafuri sunt descrise în [[https://ocw.cs.pub.ro/courses/pa/skel_graph|Precizări laboratoare 07-12]] (ex. $n$, $m$, $adj$, $adj\_trans$, $(x, y)$, etc). | ||
+ | |||
+ | Vom adăuga alte notații în continuarea celor introduse anterior. | ||
+ | |||
+ | |||
+ | |||
+ | > **Costul unei muchii** / **edge cost**: $w[u][v]$ reprezintă costul muchiei de la nodul $u$ la nodul $v$. | ||
+ | |||
+ | <spoiler Exemplu> | ||
+ | |||
+ | |||
+ | |||
+ | > **Costul unei muchii** / **edge cost**: Fiind dat un graf orientat $G = (V, E)$, se consideră funcția $w: E -> W$, numită funcție de cost, care asociază fiecărei muchii o valoare numerică. | ||
+ | |||
+ | Domeniul funcției poate fi extins, pentru a include și perechile de noduri între care nu există muchie (directă), caz în care valoarea este $+∞$ . | ||
+ | |||
+ | {{https://ocw.cs.pub.ro/courses/_media/pa/new_pa/lab09-graph-cost-example.png?512| Exemplu funcție de cost pentru graf orientat}} | ||
+ | |||
+ | În exemplul atașat, avem un graf **orientat** cu următoare configurație: | ||
+ | |||
+ | * ''%%n = 5%%'', ''%%m = 6%%'' | ||
+ | * Funcția de cost ''%%w%%'' are următorele valori finite (restul valorilor fiind $+∞$, pentru perechile de noduri între care nu există muchie): | ||
+ | * $w[1][2] = 100$ | ||
+ | * $w[1][3] = 1$ | ||
+ | * $w[2][3] = -1$ | ||
+ | * $w[3][4] = 2$ | ||
+ | * $w[2][5] = 5$ | ||
+ | * $w[4][5] = 2$ | ||
+ | |||
+ | Observație: Costul pe o muchie poate să fie negativ! (sau chiar zero) </spoiler> \\ | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | > **Drum** / **Path**: $p = (v_1, v_2, ..., v_k)$ este un drum în graful $G = (V, E)$, dacă pentru oricare $i = 1:k - 1$, există muchia $(v_i, v_{i+1})$. | ||
+ | |||
+ | |||
+ | |||
+ | > **Costul unui drum** / **path cost**: $w(p) = \sum_{i=1:k-1} {w[v_i][v_{i+1}]}$ - costul unui drum $p$ este suma costurior muchiilor care îl compun. | ||
+ | |||
+ | <spoiler Exemplu> | ||
+ | |||
+ | {{https://ocw.cs.pub.ro/courses/_media/pa/new_pa/lab09-graph-cost-example.png?512| Exemplu drumuri cu cost pentru graf orientat}} | ||
+ | |||
+ | În exemplul atașat, avem un graf **orientat** cu următoare configurație: | ||
+ | |||
+ | * ''%%n = 5%%'', ''%%m = 6%%'' | ||
+ | * Funcția de cost ''%%w%%'' are valorile menționate pe muchii. | ||
+ | * Avem următoarele costuri asociate pentru drumurile din graf: | ||
+ | * $p = (1, 2)$, $w(p) = 100$ | ||
+ | * $p = (1, 2, 3)$, $w(p) = 100 - 1 = 99$ | ||
+ | * $p = (1, 2, 3, 4)$, $w(p) = 100 - 1 + 2 = 101$ | ||
+ | * $p = (1, 2, 3, 4, 5)$, $w(p) = 100 - 1 + 2 + 3= 104$ | ||
+ | * $p = (1, 2, 5)$, $w(p) = 100 + 5= 105$ | ||
+ | * $p = (1, 3)$, $w(p) = 1$ | ||
+ | * $p = (1, 3, 4)$, $w(p) = 1 + 2 = 3$ | ||
+ | * $p = (1, 3, 4, 5)$, $w(p) = 1 + 2 + 3 = 6$ | ||
+ | * și tot așa pentru drumurile care încep cu 2, 3, etc. | ||
+ | |||
+ | </spoiler> \\ | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | > **Shortest-paths problem** / **problema drumurilor minime**: Dat fiind un graf $G = (V, E)$, dorim să aflăm drumul de cost / lungime minimă între anumite noduri din graf. | ||
+ | |||
+ | <spoiler Exemplu> | ||
+ | |||
+ | {{https://ocw.cs.pub.ro/courses/_media/pa/new_pa/lab09-graph-cost-example.png?512| Exemplu drumuri cu cost pentru graf orientat}} | ||
+ | |||
+ | În exemplul atașat, avem un graf **orientat** cu următoare configurație: | ||
+ | |||
+ | * ''%%n = 5%%'', ''%%m = 6%%'' | ||
+ | * Funcția de cost ''%%w%%'' are valorile menționate pe muchii. | ||
+ | * Avem mai multe drumuri de cost diferite între diverse perechi de noduri din graf. | ||
+ | * Analizăm care dintre toate aceste drumuri au cost minim: | ||
+ | * Drumul minim de la $1$ la $2$ este $p = (1, 2)$ cu $w(p) = 100$. Este singurul drum posibil. | ||
+ | * Drumul minim de la $1$ la $3$ este $p = (1, 3)$ cu $w(p) = 1$. Mai există drumul $p = (1, 2, 3)$, dar de cost mai mare. | ||
+ | * Drumul minim de la $1$ la $4$ este $p = (1, 3, 4)$ cu $w(p) = 1 + 2 = 3$. Mai există drumul $p = (1, 2, 3, 4)$, dar de cost mai mare. | ||
+ | * Drumul minim de la $1$ la $5$ este $p = (1, 3, 4, 5)$ cu $w(p) = 1 + 2 + 3 = 6$. Mai există drumurile $p_1 = (1, 2, 3, 4, 5)$ și $p_2 = (1, 2, 5)$, dar de cost mai mare. | ||
+ | * Analog și pentru drumurile care pornesc din 2, 3, etc. | ||
+ | |||
+ | </spoiler> \\ | ||
+ | |||
+ | |||
+ | ===== Shortest-paths: variants ===== | ||
+ | |||
+ | |||
+ | |||
+ | > **Sursă unică** / **single-source shortest-paths problem**: Se dă un graf $G = (V, E)$ și un nod special $source$, considerat sursă. Se cere calculul distanței de la sursa **source** la toate nodurile din graf. Formal: $d[v] = distanța\ de\ la\ sursa\ \textbf{source}\ la\ nodul\ \textbf{v}$. | ||
+ | |||
+ | <spoiler Exemplu> | ||
+ | |||
+ | {{https://ocw.cs.pub.ro/courses/_media/pa/new_pa/lab09-graph-distances-example.png?512| Exemplu drumuri de cost minim - sursă unică}} | ||
+ | |||
+ | În exemplul atașat, avem un graf **neorientat** cu următoare configurație: | ||
+ | |||
+ | * ''%%n = 5%%'', ''%%m = 6%%'' | ||
+ | * Funcția de cost ''%%w%%'' are valorile menționate pe muchii. | ||
+ | * Avem mai multe drumuri de cost diferite între diverse perechi de noduri din graf. | ||
+ | * Pentru exemplificare, alegem $ source = 1 $ și obținem vectorul de distanțe : | ||
+ | |||
+ | |node|1|2|3|4|5|6| \\ | ||
+ | |d[node]|0|0|1|4|7|$+∞$| \\ | ||
+ | |||
+ | |||
+ | * Explicație: | ||
+ | * $d[1] = 0$, pentru că 1 este sursa | ||
+ | * $d[2] = 0$, pentru că druml minim de la 1 la 2 este $p = (1, 3, 2)$ de cost $w(p) = 1 - 1= 0$. | ||
+ | * $d[3] = 1$, pentru că druml minim de la 1 la 3 este $p = (1, 3)$ de cost $w(p) = 1$. | ||
+ | * $d[4] = 0$, pentru că druml minim de la 1 la 4 este $p = (1, 3, 4)$ de cost $w(p) = 1 + 3= 4$. | ||
+ | * $d[5] = 5$, pentru că druml minim de la 1 la 5 este $p = (1, 3, 2, 5)$ de cost $w(p) = 1 - 1 + 5 = 5$. | ||
+ | * $d[6] = +∞$, pentru că nodul 6 nu este accesibil din sursa 1. | ||
+ | |||
+ | </spoiler> \\ | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | > **Destinație unică** / **single-source shortest-paths problem**: Se dă un graf $G = (V, E)$ și un nod special $destination$, considerat destinație. Se cere calculul distanței de la fiecare nod la nodul **destination**. Putem reduce această problema la cea de **sursă unică** (pentru graf orientat, trebuie să folosim graful transpus). | ||
+ | |||
+ | \\ | ||
+ | |||
+ | |||
+ | |||
+ | > **Pereche unică** / **single-pair shortest-path problem**: Se dă un graf $G = (V, E)$. Se cere calculul drumului minim între 2 noduri **fixate** $u$ și $v$. Observăm că din nou putem reduce problem la **sursă unică**, alegând pe $u$ sau $v$ drept sursă. | ||
+ | |||
+ | \\ | ||
+ | |||
+ | |||
+ | |||
+ | > **Surse / destinații multiple** / **all-pairs shortest-paths problem:**: Se dă un graf $G = (V, E)$. Se cere calculul drumului minim între **oricare** 2 noduri $u$ și $v$. Formal: $d[u][v] = distanța\ de\ la\ nodul\ \textbf{u}\ la\ nodul\ \textbf{v}$. | ||
+ | |||
+ | <spoiler Exemplu> | ||
+ | |||
+ | {{https://ocw.cs.pub.ro/courses/_media/pa/new_pa/lab09-graph-distances-example.png?512| Exemplu drumuri de cost minim - surse și destinații multiple}} | ||
+ | |||
+ | În exemplul atașat, avem un graf **orientat** cu următoare configurație: | ||
+ | |||
+ | * ''%%n = 5%%'', ''%%m = 6%%'' | ||
+ | * Funcția de cost ''%%w%%'' are valorile menționate pe muchii. | ||
+ | * Avem mai multe drumuri de cost diferite între diverse perechi de noduri din graf. | ||
+ | * Tabloul d este de această dată o matrice de distanțe | ||
+ | |||
+ | |-|1|2|3|4|5|6| \\ | ||
+ | |1|0|100|1|4|5|$+∞$| \\ | ||
+ | |2|$+∞$|0|$+∞$|$+∞$|5|$+∞$| \\ | ||
+ | |3|$+∞$|-1|0|3|4|$+∞$| \\ | ||
+ | |4|$+∞$|$+∞$|$+∞$|0|3|$+∞$| \\ | ||
+ | |5|$+∞$|$+∞$|$+∞$|$+∞$|0|$+∞$| \\ | ||
+ | |6|$+∞$|$+∞$|$+∞$|$+∞$|$+∞$|0| \\ | ||
+ | |||
+ | |||
+ | * Explicație: | ||
+ | * $d[u][u] = 0$ (convenție) | ||
+ | * $d[u][v] = +∞$, dacă nu există muchia $(u, v)$ (convenție) | ||
+ | * Exemple | ||
+ | * $d[1][5] = 5$, pentru că drumul minim de la 1 la 5 este $p = (1, 3, 2, 5)$ de cost $w(p) = 1 - 1 + 5 = 5$. | ||
+ | * $d[3][5] = 4$, pentru că drumul minim de la 3 la 5 este $p = (3, 2, 5)$ de cost $w(p) = - 1 + 5 = 4$. | ||
+ | |||
+ | </spoiler> \\ | ||
+ | |||
+ | |||
+ | ===== Edge relaxation ===== | ||
+ | |||
+ | |||
+ | |||
+ | > **Relaxarea unei muchii** / **edge relaxation**: A relaxa o muchie $(u, v)$ constă în a testa dacă se poate **reduce distanța / costul drumului** de la sursa **source** până la nodul **v**, trecând prin nodul intermediar $u$ și apoi circuland pe muchia $(u, v)$. | ||
+ | |||
+ | **Toți** algoritmii pentru topologii generale prezentați în laboratoarele 09 și 10 se bazează pe relaxare pentru a determina drumul minim. | ||
+ | |||
+ | <spoiler Detalii și exemple> | ||
+ | |||
+ | Presupunem următoarele notații: | ||
+ | |||
+ | * $w[u][v]$ costul muchiei $(u, v)$ | ||
+ | * $d[source][u]$ costul **drumului** de la sursa **source** la nodul **u** | ||
+ | * $d[source][v]$ costul **drumului** de la sursa **source** la nodul **v** | ||
+ | |||
+ | Dacă $d[source][v] > d[source][u] + w[u][v]$ muchia $(u, v)$ este **relaxată** și drumul anterior $(source, ..., v)$ (care **nu** trece prin $u$) este înlocuit cu drumul $(source, ..., u, v)$ (care trece prin u și care are cost mai mic!). | ||
+ | |||
+ | {{https://ocw.cs.pub.ro/courses/_media/pa/new_pa/lab09-graph-distances-example.png?512| Exemplu relaxare muchii}} | ||
+ | |||
+ | În exemplul atașat, avem un graf **orientat** cu următoare configurație: | ||
+ | |||
+ | * ''%%n = 5%%'', ''%%m = 6%%'' | ||
+ | * Funcția de cost ''%%w%%'' are valorile menționate pe muchii. | ||
+ | * Avem mai multe drumuri de cost diferite între diverse perechi de noduri din graf. | ||
+ | * Alegem $source = 1$. | ||
+ | * Pentru că inițial cunoaștem doar drumurile directe (muchiile), singurele distanțe față de sursă pe care le cunoaștem sunt $d[1][1] = 0$, $d[1][2] = 100$, $d[1][3] = 1$. Restul distanțelor se consideră inițial ca fiind egale cu infinit. | ||
+ | * Vom relaxa muchia $(3, 2)$ de cost $w[3][2] = -1$. Aceasta ne permite să reactualizăm distanța de la sursa **1** la nodul **2** cu valoarea **0** deoarece $d[1][3] + w[3][2] = -1 + 1 = 0 < d[1][2]$. | ||
+ | |||
+ | </spoiler> \\ | ||
+ | |||
+ | |||
+ | ===== RebuildPath ===== | ||
+ | |||
+ | O să observăm că toți algoritmii studiați produc 2 rezultate: $d$ (distanțele față de sursă) și $p$ (vectorul de părinți, folosit pentru reconstituirea drumurilor). | ||
+ | |||
+ | În această secțiune prezentăm procedura de reconstruire a drumului, pornind de la destinație către sursă, apoi inversând la final șirul de noduri obținut. Această metoda este independentă de algoritmul cu care vectorul de părinți **p** a fost calculat! | ||
+ | |||
+ | ===== RebuildPath - Pseudocod ===== | ||
+ | |||
+ | <code cpp> | ||
+ | // rebuild path from source to destination using parents vector p | ||
+ | // (previously computed with a shortest-paths algorithm) | ||
+ | // | ||
+ | // source = the source for the computing distances | ||
+ | // destination = the destination node | ||
+ | // p[node] = parent of node on the shortest path from source to node | ||
+ | RebuildPath(source, destination, p) { | ||
+ | // STEP 0: Create an empty path. | ||
+ | path = []; | ||
+ | |||
+ | // STEP 1: Add nodes one by one, from destination to source going up on parents! | ||
+ | // (source, ..., p[destination], destination) | ||
+ | while (source != destination) { | ||
+ | path.push_back(destination); | ||
+ | destination = p[destination]; | ||
+ | } | ||
+ | // STEP 1.1: Also add the source. | ||
+ | path.push_back(source); | ||
+ | |||
+ | // STEP 2: Reverse to actually get path (source, ..., destination). | ||
+ | reverse(path); | ||
+ | |||
+ | return path; | ||
+ | } | ||
+ | |||
+ | // Usage example: | ||
+ | // * run algorithm for computing shortests-paths | ||
+ | d, p = RunShortestPathsAlgorithm(source, G=(nodes, edges)) | ||
+ | // * call rebuikd | ||
+ | path = RebuildPath(source, destination, p) | ||
+ | </code> | ||
+ | Analog se reconstitue drumul și dacă suntem pe cazul cu surse multiple, destinații multiple, unde tabloul $p$ este o matrice, iar atunci când cautăm un drum care începe cu nodul $source$, folosim doar elementele de pe linia $p[source]$. | ||
+ | |||
+ | ===== [Studiu de caz] Shortest-paths: particular topologies ===== | ||
+ | |||
+ | Vom menționa câteva cazuri particulare de topologii de grafuri unde putem obține soluție optimă pentru **shortest-paths problem** cu noțiunile învățate anterior, nefiind necesar să folosim algoritmi mai complecși, pe care îi vom studia în continuare pentru topologii generale. | ||
+ | |||
+ | ==== Shortest-paths: no costs - BFS ==== | ||
+ | |||
+ | Dacă avem un graf **fără costuri** sau **cu toate costurile egale**, o soluție optimă va folosi **BFS**. | ||
+ | |||
+ | <spoiler Detalii> | ||
+ | |||
+ | Dacă avem un graf **fără costuri** sau **cu toate costurile egale**, putem afla distanța minimă de la un nod $source$ la orice alt nod printr-o parcurgere BFS, considerând lungimea unui drum ca fiind numărul de muchii de pe drum (ulterior se înmulțește cu costul comun a muchiilor, dacă acesta există). | ||
+ | |||
+ | * **complexitate temporală**: $T = O(n + m)\ sau\ O(|V| + |E|)$ | ||
+ | * Complexitatea dată de parcurgere. | ||
+ | * **complexitate spațială** : $S = O(1)$ | ||
+ | * Nu avem memorie spațială auxialiară. ATENȚIE! Vom aloca tabloul $d$, însă acesta nu este specific algoritmului. | ||
+ | |||
+ | </spoiler> \\ | ||
+ | |||
+ | |||
+ | ==== Shortest-paths: Tree - DFS ==== | ||
+ | |||
+ | Dacă avem un **arbore** (graf neorientat acicllic), o soluție optimă va folosi **DFS**. | ||
+ | |||
+ | <spoiler Detalii> | ||
+ | |||
+ | Dacă avem un **arbore** (graf neorientat acicllic), există un singur drum între oricare două noduri, care poate fi aflat printr-o simplă parcurgere **DFS** / **BFS**. | ||
+ | |||
+ | * **complexitate temporală**: $T = O(n + m)\ sau\ O(|V| + |E|)$ | ||
+ | * Complexitatea dată de parcurgere. | ||
+ | * **complexitate spațială** : $S = O(1)$ | ||
+ | * Nu avem memorie spațială auxialiară. ATENȚIE! Vom aloca tabloul $d$, însă acesta nu este specific algoritmului. | ||
+ | |||
+ | </spoiler> \\ | ||
+ | |||
+ | |||
+ | Folosind diferite preprocesări, putem calcula distanța între oricare două noduri în $O(1)$. | ||
+ | |||
+ | ==== Shortest-paths: DAG - Topological Sort ==== | ||
+ | |||
+ | Daca avem un **DAG** (graf orientat aciclic), o soluție optimă va folosi **Topological Sort**. | ||
+ | |||
+ | <spoiler Detalii> | ||
+ | |||
+ | Daca avem un **DAG** (graf orientat aciclic), putem să relaxăm muchiile nodurilor, parcurgându-le pe acestea în ordinea dată de sortarea topologică. | ||
+ | |||
+ | * **complexitate temporală**: $T = O(n + m)\ sau\ O(|V| + |E|)$ | ||
+ | * Complexitatea dată de parcurgere. | ||
+ | * **complexitate spațială** : $S = O(n)$ | ||
+ | * Stocăm vectorul cu sortarea topologică. | ||
+ | |||
+ | </spoiler> \\ | ||
+ | |||
+ | |||
+ | ===== [Studiu de caz] k surse / destinații ===== | ||
+ | |||
+ | Mai există o varianta a problemei pe care dorim să o menționăm. Avem **k** surse (sau destinații), unde **k << n ** (k mult mai mic decât n - de exemplu, $k = 10$ constant) și ne interesează să găsim distanța pentru fiecare nod din graf către cea mai apropiată sursă. | ||
+ | |||
+ | O abordare naivă ar rula de **k** ori un algorthm de drumuri minime (de exemplu, Dijkstra) și pentru fiecare nod ar reține minimul dintre cele k distanțe calculate pentru un nod. Complexitatea este $O(k * complexitate\_algoritm$). | ||
+ | |||
+ | O optimizare pe care o putem facem, este să adaugăm un nod fictiv $S$ și să îl unim cu fiecare dintre cele **k** noduri cu muchie de cost **0**. În noul graf format, dacă vom rula un algoritm să găsim toate distanțele de la nodul **S** la celelalte noduri din graf, rezolvăm și problema inițială. | ||
+ | |||
+ | Dacă alegem să folosim Dijkstra, putem să nu modificăm graful. Dacă băgăm cele **k** surse în heap în etapa de inițializare, efectul este ca și cum s-ar fi scos nodul **S** și s-au relaxat toate muchiile care pornesc din acesta. | ||
+ | |||
+ | ===== Referințe ===== | ||
+ | |||
+ | [0] Chapters **Single-Source Shortest Paths** / **All-Pairs Shortest Paths**, “Introduction to Algorithms”, Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest and Clifford Stein. |