This shows you the differences between two versions of the page.
pa:laboratoare:laborator-11 [2022/05/16 19:35] darius.neatu |
pa:laboratoare:laborator-11 [2025/02/24 21:59] (current) darius.neatu [Laborator 11: Flux maxim în rețele de transport] |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Laborator 11: Arbori minimi de acoperire ====== | + | ====== Tutorial: Flux maxim în rețele de transport ====== |
- | {{:pa:new_pa:partners:bitdefender-logo.png?155 |}} | + | {{:pa:new_pa:partners:bitdefender-logo.png?190 |}} Bitdefender provides cybersecurity solutions with leading security efficacy, performance and ease of use to small and medium businesses, mid-market enterprises and consumers. Guided by a vision to be the world’s most trusted cybersecurity solutions provider, Bitdefender is committed to defending organizations and individuals around the globe against cyberattacks to transform and improve their digital experience. |
- | Bitdefender provides cybersecurity solutions with leading security efficacy, performance and ease of use to small and medium businesses, mid-market enterprises and consumers. Guided by a vision to be the world’s most trusted cybersecurity solutions provider, Bitdefender is committed to defending organizations and individuals around the globe against cyberattacks to transform and improve their digital experience. | + | |
===== Obiective laborator ===== | ===== Obiective laborator ===== | ||
- | În acest laborator vom introduce contextul pentru **minimum spanning tree problemm** și vom studia algoritmi care pot rezolva această problemă. | + | În acest laborator vom introduce contextul pentru **maximum flow problem** și vom studia algoritmi care pot rezolva această problemă. |
- | * Prezentarea problemei drumului de cost minim (diverse variante). | + | * Prezentarea noțiunii de rețea de transport și a termenilor asociați. |
- | * Prezentarea algoritmilor pentru calcul a unui arbore minim de acoperire. | + | * Prezentarea problemei (diverse variante). |
+ | * Prezentarea algoritmilor pentru calcul unui flux maxim într-o rețea de transport. | ||
- | ===== Importanţă – aplicaţii practice ===== | + | ===== Maximum flow problem ===== |
- | Algoritmii pentru determinarea unor arbori (minimi) de acoperire au multiple aplicații practice. Câteva exemple de aplicații sunt: | + | Vă rugăm să parcugeți [[https://ocw.cs.pub.ro/courses/pa/laboratoare/maximum-flow-problem|Maximum flow problem]] pentru a vă familiariza cu contextul, problema și notațiile folosite. |
- | * Designul circuitelor electronice: | + | Concepte necesare: |
- | * Maparea pinilor pe un circuit astfel încât să se foloseacă o lungime minimă de traseu.) | + | |
- | * Rețele de calculatoare: | + | |
- | * Interconectarea mai multor stații în rețea, cu un cost / latență redus(ă). | + | |
- | * STP Protocol: protocol care previne apariția buclelor într-un LAN. | + | |
- | * Segmentarea imaginilor: | + | |
- | * Împărțirea unei imagini în regiuni cu proprietăți asemănătoare. | + | |
- | * [[https://en.wikipedia.org/wiki/Minimum_spanning_tree-based_segmentation | Minimum spanning tree-based segmentation]]). | + | |
- | * Clustering: | + | |
- | * [[https://en.wikipedia.org/wiki/Single-linkage_clustering|Single-linkage clustering]]). | + | |
- | ===== Minimum spanning tree problem ===== | + | * **rețea de transport** / **flow network** |
+ | * **flux** / **flow** | ||
+ | * **flux maxim** / **maximum flow** | ||
+ | * **capacitate reziduală** / **residual capacity** | ||
+ | * **rețea reziduală** / **residual network** | ||
+ | * **drum de ameliorare** / **augmenting path** | ||
+ | * **capacitate reziduală a unui drum de ameliorare** / **residual capacity of augmenting path** | ||
- | Puteți consulta capitolul **Minimum Spanning Trees** din **Introduction to Algorithms** [0] pentru mai multe definiții formale. Această secțiune sumarizează principalele notații folosite în laboratoarele de PA. | + | ===== Algoritmi ===== |
- | 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). | + | Pentru **maximum flow problem** există mai mulți algoritmi, dintre care amintim: |
- | Vom adăuga alte notații în continuarea celor introduse anterior. | + | * **Ford–Fulkerson**: Este o metodă (nu un algorithm concret), a cărui complexitate este direct proporțională cu valoarea fluxului maxim - $O(m * f_{max})$. [1] |
+ | * **Edmonds-Karp**: Este o implementare concretă / optimizare a metodei anterioare - $O(n * m^2)$. [2] | ||
+ | * **Dinic**: O optimizare bazată pe construirea progresivă a unui graf folosind BFS. În fiecare etapă se poate afla fluxul în $O(n * m)$, iar în total sunt $n -1$ etape. Complexitate finală $O(n^2 * m)$. [3] | ||
+ | * **Preflow-push / push-relabel**: Un algoritm care poate fi optimizat pentru a obține complexitate $O(n^3)$ sau $O(n^2 * m)$. | ||
+ | Puteți consulta capitolul **Maximum Flow** din **Introduction to Algorithms** [0] pentru mai multe detalii despre acești algoritmi. | ||
+ | În acest laborator vom studia și analiza **metoda Ford-Fulkerson**, apoi vom implementa **algoritmul Edmonds-Karp** (care este o optimizare peste Ford-Fulkerson). | ||
- | > **Arbore de acoperire** / **spanning tree**: Într-un graf neorientat **conex** $G = (V, E)$, cu funcția de cost$w: E -> W$, numim **un arbore de acoperire** un subgraf al lui **G** cu număr minim de muchii și care interconectează toate nodurile din graf. | + | ===== Ford-Fulkerson ===== |
- | <spoiler Exemplu> | + | Algoritmul lui [[https://en.wikipedia.org/wiki/L._R._Ford_Jr.|Lester Randolph **Ford** Jr. ]] și [[https://en.wikipedia.org/wiki/D._R._Fulkerson|Delbert Ray **Fulkerson**]], cunoscut ca și algoritmul / metoda [[https://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm|Ford-Fulkerson]] este un algoritm **greedy** pentru calcularea fluxului maxim într-o rețea de transport. |
- | {{https://ocw.cs.pub.ro/courses/_media/pa/new_pa/lab11-spanning-tree-example.png?850| Arbore de acoperire }} | + | Atât timp cât în graf încă există drumuri de ameliorare (augmenting paths), înseamnă că mai putem adauga flux in rețea prin acele drumuri. La fiecare pas alegem un drum de ameliorare, prin care adăugăm cantitatea maximă posibilă de flux. |
- | În exemplul atașat, avem un graf **neorientat** și **conex** cu următoare configurație: | + | <spoiler Ford-Fulkerson - Pseudocod> |
- | * ''%%n = 5%%'', ''%%m = 5%%'' | + | <code cpp> |
- | * Pentru acest exemplu, avem **3** arbori de acoperire evidențiați în aceeași figură (rândul 2). | + | // apply Ford-Fulkerson's algorithm for computing maximum flow in network G |
+ | // | ||
+ | // nodes = list of all nodes from G | ||
+ | // edges = the list of all edges in G | ||
+ | // example: edge (node, neigh, capacity) | ||
+ | // S = source in network G | ||
+ | // T = sink in network G | ||
+ | // | ||
+ | // returns: maxFlow (maximum flow in G from S to T) | ||
+ | // | ||
+ | Ford-Fulkerson(G=(nodes, edges), S, T) { | ||
+ | // STEP 0: initialize | ||
+ | // * flow in network | ||
+ | foreach ((u, v) in edges) { | ||
+ | f[u][v] = 0; | ||
+ | } | ||
+ | // * maximum total flow | ||
+ | totalFlow = 0; | ||
- | Observație: Un graf **conex** are cel puțin un arbore de acoperire! | + | while (exists augmenting path p in G) { |
+ | augment flow f along p; | ||
+ | update totalFlow; | ||
+ | } | ||
- | </spoiler> \\ | + | return totalFlow; |
+ | } | ||
+ | // Usage example: | ||
+ | maxFlow = Ford-Fulkerson(G=(nodes, edges), S, T); | ||
+ | // Use maxFlow. | ||
+ | </code> | ||
+ | După cum se observă, metoda Ford-Fulkerson descrie care este abordarea generală, însă nu ne spune concret cum să găsim drumurile de ameliorare și cum să creștem fluxul pe ele. | ||
+ | Putem să alegem drumurile la întâmplare. Vom obține un rezultat corect (nu vom demonstra). | ||
- | + | Din punct de vedere al performanței, în cel mai rău caz, pornind o parcurgere oarecare vom găsi în $O(n + m)$ un drum de ameliorare pe care vom putea crește cu exact o unitate cantitatea de flux. Această abordare duce la o complexitate de $O((n + m) * |f_{max}|)$ - adică un timp de execuție proporțional cu cantitea de flux pompată. Abordarea este ineficientă. | |
- | > **Pădure de arbori de acoperire** / **spanning tree forese**: Se definește similar, dar pentru grad neorientat **neconex**. Fiecare componentă conexă are un **arbore de acoperire** / **spanning tree**. Spunem că acești arbori formează împreună o **pădure** (**forest**). | + | |
- | + | ||
- | <spoiler Exemplu> | + | |
- | + | ||
- | {{https://ocw.cs.pub.ro/courses/_media/pa/new_pa/lab11-spanning-forest-example.png?850| Pădure de arbori de acoperire }} | + | |
- | + | ||
- | În exemplul atașat, avem un graf **neorientat** și **neconex** cu următoare configurație: | + | |
- | + | ||
- | * ''%%n = 6%%'', ''%%m = 6%%'' | + | |
- | * În figura următoare evidențăm toate pădurile de arbori de acoperire (6 pe acest exemplu). | + | |
- | + | ||
- | Observație: Un graf **neconex** are cel puțin o pădure de arbori de acoperire! | + | |
</spoiler> \\ | </spoiler> \\ | ||
+ | În practică **NU** vom folosi direct metoda Ford-Fulkerson, ci **vom folosi algoritmul Edmonds-Karp**, pe care îl vom analiza în secțiunea următoare. | ||
+ | ===== Edmonds-Karp ===== | ||
- | > **Arbore minim de acoperire** (**AMA**)/ **minimum spanning tree** (**MST**): Un arbore de acoperire este **ȘI** arbore minim de acoperire dacă costul total al muchiilor din arbore este minim posibil. Analog se poate defini și noțiunea de **pădure de arbori minimi de acoperire** (pornind de la cea de pădure de arbori de acoperire). | + | Algoritmul lui [[https://en.wikipedia.org/wiki/Jack_Edmonds|Jack **Edmonds**]] și [[https://en.wikipedia.org/wiki/Richard_M._Karp|Richard M. **Karp**]], cunoscut ca și algoritmul [[https://en.wikipedia.org/wiki/Edmonds%E2%80%93Karp_algorithm|Edmonds-Karp]] reprezintă o implementare concretă a metodei Ford-Fulkerson. Acest algoritm rezolvă problema drumurilor de ameliorare folosind **BFS** - adică drumul cel mai scurt (ca și număr de arce), pe care se poate merge ținând cont de restricția de capacitate ($f(u, v) \lt c(u, v)$). |
- | <spoiler Exemplu> | + | ==== Edmonds-Karp - pseudocod ==== |
- | {{https://ocw.cs.pub.ro/courses/_media/pa/new_pa/lab11-minimum-spanning-tree-example.png?850| Exemplu funcție de cost pentru graf orientat}} | + | <code cpp> |
+ | // apply Edmonds-Karp's algorithm for computing maximum flow in network G | ||
+ | // | ||
+ | // nodes = list of all nodes from G | ||
+ | // adj[node] = the adjacency list of node | ||
+ | // example: adj[node] = {..., neigh, ...} => edge (node, neigh) | ||
+ | // S = source in network G | ||
+ | // T = sink in network G | ||
+ | // f = flow in network G | ||
+ | // c = capacity in network G | ||
+ | // | ||
+ | // returns: maxFlow (maximum flow in G from S to T) | ||
+ | // | ||
+ | Edmonds-Karp(G=(nodes, adj), S, T) { | ||
+ | // STEP 0: initialize | ||
+ | // * flow in network | ||
+ | foreach (u in nodes) { | ||
+ | foreach(v in nodes) { | ||
+ | f[u][v] = 0; | ||
+ | } | ||
+ | } | ||
+ | // * maximum total flow | ||
+ | totalFlow = 0; | ||
- | În exemplul atașat, avem un graf **neorientat** și **conex** cu următoare configurație: | + | // STEP 1: Start BFS for finding augmenting path(s). |
+ | while (BFS finds at least one augmenting path in G) { | ||
+ | // STEP 2.1: Compute residual capacity of augmenting path. | ||
+ | rc = +oo; // residual capacity | ||
+ | foreach ((u, v) in path) { | ||
+ | rc = min(rc, c[ u ][ v ] - f[ u ][ v ]); | ||
+ | } | ||
- | * ''%%n = 5%%'', ''%%m = 5%%'' | + | // STEP 2.2: Update flow on path with rc. |
- | * În figura următoare evidențăm toți arborii de acoperire. Doar 2 dintre aceștia au cost minim **5**. | + | foreach ((u, v) in path) { |
+ | f[ u ][ v ] += rc; // Increase on edge (u, v). | ||
+ | f[ v ][ u ] -= rc; // Decrease on edge (v, u). | ||
+ | } | ||
- | Observație: Un graf **conex** are cel puțin un arbore minim de acoperire! | + | // STEP 2.3: Update total flow. |
+ | totalFlow += rc; | ||
+ | } | ||
- | </spoiler> \\ | + | return totalFlow; |
+ | } | ||
+ | // Usage example: | ||
+ | maxFlow = Edmonds-Karp(G=(nodes, adj), S, T); | ||
+ | // Use maxFlow. | ||
+ | </code> | ||
+ | Algoritmul Edmons-Karp pornește o parcurgere BFS la fiecare pas. Pentru un drum de ameliorare găsit, calculează capacitatea reziduală a acestuia, apoi crește corespunzător fluxul pe acesta. | ||
- | ===== Algoritmi ===== | + | La **fiecare** pas, atunci când se crește fluxul pe un arc, se scade fluxul pe arcul invers (vom demonstra într-o subsecțiune viitoare necesitatea acestui pas pentru corectitudinea algoritmului). |
- | Pentru **minimum spanning tree problem** există mai mulți algoritmi, dintre care amintim: | + | Algoritmul se oprește când nu mai există drumuri de ameliorare. Fluxul pompat până atunci în rețea este maxim. |
- | * **Kruskal**: abordare greedy care se bazează pe sortarea muchiile după cost. Mai multe detalii în **Introduction to Algorithms** [0]. | + | ==== Exemple ==== |
- | * **Prim**: abordare greedy care extinde arborele căutat pornind de la o rădăcină (nod ales oarecare). Mai multe detalii în **Introduction to Algorithms** [0]. | + | |
- | * **Karger, Klein & Tarjan**: algoritm randomizat cu complexitate liniară (mai bună decât Kruskal și Prim). Mai multe detalii în **A Randomized Linear-Time Algorithm to Find Minimum Spanning Trees** [1]. | + | |
- | * **Bernard Chazelle**: algoritm determinist cu o complexitate liniară (cu complexitate mai bună decât soluția anterioară). Mai multe detalii în **A Minimum Spanning Tree Algorithm with InverseAckermann Type Complexity** [2]. | + | |
- | În acest laborator vom studia și vom implementa **algoritmul lui Kruskal**. De asemenea, oferim ca studiu de caz pentru acasă, un material pentru algoritmul lui Prim. | + | === Edmonds-Karp: exemplu rezultat === |
- | ===== Kruskal ===== | + | {{https://ocw.cs.pub.ro/courses/_media/pa/new_pa/lab12-edmonds-karp-example.png?512| Exemplu Edmonds-Karp}} |
- | Algoritmul lui [[https://en.wikipedia.org/wiki/Joseph_Kruskal|Joseph Bernard **Kruskal**]] (**Kruskal’s algorithm**) rezolvă **minimum spanning tree problem** în grafuri neorientate **G = (V, E)** cu costurile muchiilor **oarecare** aplicând o strategie greedy foarte simplă: muchiile de cost minim probabil fac parte din MST! | + | Fluxul maxim calculat cu Edmonds-Karp pentru rețeaua atașată este: **2**. |
- | ==== Kruskal - Pseudocod ==== | + | Drumurile de ameliorare folosite sunt: |
+ | * $1 - 2 - 4 - 6$ (capacitate reziduală **+1**) | ||
+ | * $1 - 3 - 5 - 6$ (capacitate reziduală **+1**) | ||
- | <note> | + | === Exemplu Edmonds-Karp: exemplu simplu pas cu pas === |
- | Algoritmul lui Kruskal folosește o structură de date care suportă 2 operații: | + | În această secțiune vom exemplifica de ce doar simpla incrementare a fluxului pe arcele directe nu este suficientă pentru a găsi fluxul maxim în rețea. Următoarea secțiunea conține soluția. |
- | * În ce mulțime este elementul **x**? | + | <spoiler Exemplu> |
- | * Să se reunească mulțimile din care fac parte elementele **x** și **y**. | + | |
- | Se pot folosi mai multe structuri de date. Pentru o implementare eficientă, alegem **DisjointSet**. Vă recomandăm să parcurgeți [[https://infoarena.ro/problema/disjoint | DisjointSet]]. **ATENȚIE|** Scheletul de laborator oferă o astfel de implementare. | + | Pornim de la rețeaua din figura următoare: |
+ | {{https://ocw.cs.pub.ro/courses/_media/pa/new_pa/lab12-edmonds-karp-example.png?512| Exemplu Edmonds-Karp}} | ||
- | </note> | + | * ''%%n = 6%%'', ''%%m = 7%%'' |
+ | * Sursa este ''%%S = 1%%'', iar terminalul este ''%%T = 6%%''. | ||
+ | * Funcția de capacitate ''%%c%%'' are valorile din figură. | ||
- | <code cpp> | + | Dacă rulăm Edmonds-Karp, o posibilă succesiune de stări pentru rețea este cea din figura următoare: |
- | // apply Kruskal's algorithm for computing a Minimum Spanning Tree (MST). | + | |
- | // | + | |
- | // nodes = list of all nodes from G | + | |
- | // adj[node] = the adjacency list of node | + | |
- | // example: adj[node] = {..., (neigh, weight) ...} => edge (node, neigh, weight) | + | |
- | // | + | |
- | // returns: cost, mst | + | |
- | // cost = the cost of the MST | + | |
- | // mst = the actual set of edges in MST | + | |
- | // | + | |
- | Kruskal(G=(nodes, edges)) { | + | |
- | // STEP 0: initialize results | + | |
- | cost = 0; // cost of the built MST | + | |
- | mst = {}; // the actual set of edges (u, v) in the built MST | + | |
- | // STEP 1: initialize disjoint set data structure | + | {{https://ocw.cs.pub.ro/courses/_media/pa/new_pa/lab12-edmonds-karp-basic-example-01.png?512| Exemplu Edmonds-Karp}} |
- | // (a tree is created for each node) | + | |
- | disjointset = new DisjointSet(nodes); | + | |
- | // STEP 2: | + | * Inițial fluxul în rețea este zero. |
- | sort(edges, compare=nondecreasing order by weight); | + | * Se vor găsi 2 drumuri de ameliorare $1 - 2 - 4 - 6$ și $ 1 - 3 - 5 - 6$. Pe fiecare drum se pompează câte o unitate de flux. |
+ | * În rețeaua reziduală obținută se observă că nu se mai poate ajunge de la S la T. | ||
+ | * Prin urmare fluxul obținut este maxim și are valoare $1 + 1 = 2$. | ||
- | // STEP 3: Add edge by edge to MST if no cycles are created. | ||
- | foreach ( (u, v, w) in edges ) { | ||
- | // STEP 3.1: Check if u anv v are in different trees. | ||
- | if (disjointset.setOf(u) != disjointset.setOf(v)) { | ||
- | // STEP 3.2: Merge these 2 trees (no cycles created). | ||
- | disjointset.union(u, v); | ||
- | // STEP 3.3: Extend MST with the current edge. | + | <note warning> |
- | cost += w; | + | |
- | mst += { (u, v) }; | + | |
- | } | + | |
- | } | + | |
- | return cost, mst; | + | NU avem control asupra drumurilor găsite de BFS, adică ordinea în care acestea vor fi alese, ci știm doar că se va alege cele mai scurte drumuri. |
- | } | + | |
- | // Usage example: | + | </note> |
- | cost, mst = Kruskal(G=(nodes, edges)); | + | |
- | // 1. Use cost of MST. | + | |
- | // 2. Use edges of MST (e.g. actually build the tree). | + | |
- | </code> | + | |
- | Algoritmul lui Kruskal pornește cu structura de date Păduri de mulțimi disjuncte inițializată cu n noduri izolate. Muchiile sunt sortate crescător după cost și se încearcă adăugarea lor, rând pe rând la pădurea de arbori. | + | |
- | * Dacă o muchie $(u, v)$ are capetele în 2 arbori diferiți, atunci prin adăgarea acesteia, nu vom crea ciclu, ci vom crea un arbore mai mare (din cei 2 mici reuniți). | + | O altă succesiune posibilă și corectă de stări pentru rețea se găsește în figura următoare: |
- | * Altfel, nu putem adăuga muchia, deoarece am crea un ciclu. Această muchie se ignoră. | + | |
- | ==== Exemple ==== | + | {{https://ocw.cs.pub.ro/courses/_media/pa/new_pa/lab12-edmonds-karp-basic-example-02.png?512| Exemplu Edmonds-Karp}} |
- | === Exemplu Kruskal === | + | * Inițial fluxul în rețea este zero. |
+ | * Se găsește drumul de ameliorare $1 - 2 - 5 - 6$ (tot de lungime 3 arce) cu capacitatea reziduală 1. Se pompează această unitate de flux. | ||
+ | * În rețeaua reziduală obținută se observă că nu se mai poate ajunge de la S la T. | ||
+ | * Fluxul total obținut este **1**, care **NU** este maxim. | ||
- | {{https://ocw.cs.pub.ro/courses/_media/pa/new_pa/lab11-kruskal.png?850| Exemplu Kruskal}} | + | Prin urmare ajungem într-un punct în care algoritmul a greșit și nu mai poate pompa flux în plus. Parcugeți următorul exemplu pentru a vedea concret cum se rezolvă această problemă. |
- | + | ||
- | Un **MST** găsit cu algoritmul lui Kruskal este: ${ (1, 2); (2, 4); (2, 3); (3, 5)}$ de cost **5**. | + | |
- | + | ||
- | <spoiler Explicație pas cu pas> | + | |
- | + | ||
- | În exemplul atașat, avem un graf **neorientat** cu următoare configurație: | + | |
- | + | ||
- | * ''%%n = 5%%'', ''%%m = 5%%'' | + | |
- | * Funcția de cost ''%%w%%'' are valorile menționate pe muchii. | + | |
- | * Muchiile sortate după cost sunt: | + | |
- | * $(3, 5)$ de cost $-1$ | + | |
- | * $(2, 4)$ de cost $1$ | + | |
- | * $(2, 3)$ de cost $2$ | + | |
- | * $(2, 5)$ de cost $2$ | + | |
- | * $(1, 2)$ de cost $3$ | + | |
- | * Explicație pas cu pas | + | |
- | * STEP 0: Inițializăm pădurile de mulțimi disjuncte - pornind cu n arbori (noduri izolate). | + | |
- | * STEP 1: Muchia cu costul cel mai mic care e nefolosită este $(3, 5)$ de cost -1. Se adăgă la **MST**. Se reunesc 2 arbori. | + | |
- | * STEP 2: Muchia cu costul cel mai mic care e nefolosită este $(2, 4)$ de cost 1. Se adăgă la **MST**. Se reunesc 2 arbori. | + | |
- | * STEP 3: Muchia cu costul cel mai mic care e nefolosită este $(2, 3)$ de cost 2. Se adăgă la **MST**. Se reunesc 2 arbori. | + | |
- | * Observație: În acest pas am fi putut alege muchia $(2, 5)$ cu același cost. | + | |
- | * STEP 4: Muchia cu costul cel mai mic care e nefolosită este $(1, 2)$ de cost 3. Se adăgă la **MST**. Se reunesc 2 arbori. | + | |
- | * Avem un arbore. STOP. | + | |
- | + | ||
- | Observație: În STEP 3 pas am fi putut alege muchia $(2, 5)$ cu același cost. Obțineam un MST care diferea printr-o singură muchie (${ (1, 2); (2, 5); (2, 3); (3, 5)}$, dar cu același cost **5**. | + | |
</spoiler> \\ | </spoiler> \\ | ||
- | ==== Complexitate ==== | + | === Exemplu Edmonds-Karp: exemplu detaliat cu flux pe arce inverse === |
- | * **complexitate temporală**: $T = O(m * log m)\ sau\ O(|E| * log |E|)$ | + | În această secțiune vom arăta ce face algoritmul pas cu pas și vom observa necesitatea și corectitudinea scăderii fluxului pe arcele inverse. |
- | * **complexitate spațială** : $S = O(n)$ | + | |
- | <spoiler Detalii (analiză + optimizări)> | + | Pornim de la rețeaua din figura următoare: |
- | * **complexitate temporală**: | + | {{https://ocw.cs.pub.ro/courses/_media/pa/new_pa/lab12-edmonds-karp-negative-flow-01.png?512| Exemplu Edmonds-Karp}} |
- | * Se sortează cele **m** muchii în ordine crescătoare după cost - $O(m log m)$. | + | |
- | * Se folosesc **n** operații pe **DisjointSet** - $O(n log*n)$, care poate fi aproximat cu $O(n)$ (demonstrație pe pagina **DisjointSet**). | + | |
- | * **complexitate spațială** : Se ține o structură de date **DisjointSet** cu maximum **n** noduri. | + | |
- | * **optimizare**: Analog Dijkstra, putem să obținem: | + | |
- | * **Prim cu heap binar** - $O(m log n)$. | + | |
- | * **Prim cu heap Fibonacci** - $O(n logn + m)$. | + | |
- | </spoiler> \\ | + | * ''%%n = 6%%'', ''%%m = 7%%'' |
+ | * Sursa este ''%%S = 1%%'', iar terminalul este ''%%T = 6%%''. | ||
+ | * Funcția de capacitate ''%%c%%'' are valorile din figură. | ||
+ | * Observăm că pentru fiecare arc $(u, v)$ am dus un arc imaginar $(v, u)$ de capacitate **0**. Aceste arce fictive reprezintă cheia funcționării algoritmului Edmonds-Karp: de fiecare dată când vom pompa (adăuga) flux pe un arc $(u, v)$, vom scădea cu aceeași valoare fluxul pe arcul $(v, u)$. | ||
+ | Să vedem în continuare la ce ne ajută dacă alegem din nou, ghinionist, drumul de amelioarare $1 - 2 - 5 - 6$ și dacă de data aceasta putem corecta. Vom face acțiunile marcate în figura următoare: | ||
- | ===== [Studiu de caz] Prim ===== | + | {{https://ocw.cs.pub.ro/courses/_media/pa/new_pa/lab12-edmonds-karp-negative-flow-02.png?512| Exemplu Edmonds-Karp}} |
- | Algoritmul lui [[https://en.wikipedia.org/wiki/Robert_C._Prim|Robert C **Prim**]] (**Prim’s algorithm**) rezolvă **minimum spanning tree problem** în grafuri neorientate **G = (V, E)** cu costurile muchiilor **oarecare** aplicând o strategie constructivă greedy, asemănătoare cu algoritmul lui Dijkstra - merge pe muchiile de cost minim, din aproape în aproape, considerând o sursă inițial aleasă aleator și extinzând MST-ul curent cu muchia de cost minim neadăugată încă la arbore. | + | * Se pompează o unitate de flux pe drumul $1 - 2 - 5 - 6$. Prin urmare pe arcele marcate cu verde creștem fluxul cu **+1** unități de flux, iar pe arcele marcate cu roșu scădem (**-1** unități de flux). |
+ | * La pasul următor, se pornește BFS din 1 și se înaintează folosind aceeași condiție - se poate merge pe o muchie $(u, v)$ dacă $f(u, v) \lt c(u, v)$. De data aceasta, această condiție se îndeplinește și pentru unele dintre muchiile inverse - cele cu **-1/0**. | ||
- | ==== Prim Pseudocod ==== | + | Vom face acțiunile marcate în figura următoare: |
- | <spoiler Prim - Pseudocod> | + | {{https://ocw.cs.pub.ro/courses/_media/pa/new_pa/lab12-edmonds-karp-negative-flow-03.png?512| Exemplu Edmonds-Karp}} |
- | <code cpp> | + | * Drumul de ameliorare găsit este $1 - 3 - 5 - 2 - 4 - 6$, care are capacitatea reziduală **1**. Se pompează **+1** pe acest drum (arcele cu verde), se scade în sens invers (arcele cu roșu). |
- | // apply Prim's algorithm for computing a Minimum Spanning Tree (MST). | + | * La următoarea incercare de apelare BFS, nu se mai poate ajunge de la S la T, prin urmare algoritmul se oprește! |
- | // | + | * Fluxul găsit este **1 + 1 = 2**, care este maxim! |
- | // source = source / starting node for computing / expading MST | + | |
- | // (if not specified, node 1 is defaulted) | + | |
- | // nodes = list of all nodes from G | + | |
- | // adj[node] = the adjacency list of node | + | |
- | // example: adj[node] = {..., (neigh, weight) ...} => edge (node, neigh, weight) | + | |
- | // | + | |
- | // returns: cost, mst | + | |
- | // cost = the cost of the MST | + | |
- | // mst = the actual set of edges in MST | + | |
- | // | + | |
- | Prim(source=1, G=(nodes, adges)) { | + | |
- | // STEP 0: initialize results | + | |
- | // d[node] = distance from MST (any node in current/partial MST) to node | + | |
- | // p[node] = parent of node: node is connected to MST with edge p[node] - node | + | |
- | foreach (node in nodes) { | + | |
- | d[node] = +oo; // distance not yet computed | + | |
- | p[node] = null; // parent not yet found | + | |
- | } | + | |
- | // STEP 0: initialize results | ||
- | cost = 0; // cost of the built MST | ||
- | mst = {}; // the actual set of edges (u, v) in the built MST | ||
- | // STEP 1: initialize a priority queue | + | <note warning> |
- | pq = {}; | + | |
- | // STEP 2: add the source(s) into pq | + | Să observăm că starea finală a rețelei conține 2 unități de flux. Dacă ne uităm cu atenție, observăm că avem o unitate de flux pe drumul $1 - 2 - 4 - 6$ și alta pe $1 - 3 - 5 - 6$. |
- | d[source] = 0; // distance from source to MST | + | |
- | p[source] = null; // source never has parent | + | |
- | pq.push( (source, d[source]) ); | + | |
- | // STEP 3: Build MST. | + | Deși Edmonds-Karp nu a ales inițial aceste 2 drumuri, s-a corectat pe parcurs, iar în final, dacă fluxul maxim trebuia să treacă pe aici, rețeaua reziduală a fost actualizată / corectată până s-a întâmplat acest lucru! |
- | while ( !pq.empty() ) { | + | |
- | // STEP 3.1: Pop next possible node to connect with MST. | + | |
- | node, _ = pq.pop() | + | |
- | if (used[noode]) { | + | |
- | continue; | + | |
- | } | + | |
- | // STEP 3.2: Extend MST with edge p[node] - node | + | Semnificația înaintării pe un arc fictiv este că defapt cautăm să deviem fluxul de pe un drum ales anterior (de exemplu, $1 - 2 - 5 - 6$), pe o altă cale (de exemplu, porțiunea $1 - 2$ să se continue cu $2 - 4 - 6$), astfel încât să putem folosi partea eliberată (de exemplu, $5 - 6$), în construcția unui nou drum la pasul curent. |
- | used[node] = true; | + | |
- | if (p[node] != null) { | + | |
- | cost += d[node]; | + | |
- | mst += { (node, p[node]) }; | + | |
- | } | + | |
- | + | ||
- | // STEP 3.3: relax all edges (node, neigh) | + | |
- | foreach ( (neigh, w) in adj[node]) { | + | |
- | // Can we find a better / shorter link / edge from node to current MST? | + | |
- | if (!used[neigh] && w < d[neigh]) { | + | |
- | d[neigh] = d[node] + w[node][neigh]; // update the new distance from neigh to MST | + | |
- | p[neigh] = node; // save parent | + | |
- | + | ||
- | pq.push( (neigh, d[neigh]) ); // replace distance for neigh in pq | + | |
- | } | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | return cost, mst; | + | |
- | } | + | |
- | + | ||
- | // Usage example: | + | |
- | cost, mst = Prim(1, G=(nodes, edges)); // e.g. source = 1, can be any node in G | + | |
- | // 1. Use cost of MST. | + | |
- | // 2. Use edges of MST (e.g. actually build the tree). | + | |
- | </code> | + | |
- | </spoiler> \\ | + | |
+ | </note> | ||
==== Complexitate ==== | ==== Complexitate ==== | ||
- | * **complexitate temporală**: $T = O(m * log n)\ sau\ O(|E| * log |V|)$ | + | * **complexitate temporală**: $T = O(n * m^2)\ sau\ O(|V| * |E|^2)$ |
- | * **complexitate spațială** : $S = O(n)$ | + | * **complexitate spațială** : $S = O(n)\ sau \ O(|V|)$ |
<spoiler Detalii (analiză + optimizări)> | <spoiler Detalii (analiză + optimizări)> | ||
- | * **complexitate temporală**: Se încearcă relaxarea tuturor celor **m** muchii din graf, analog algoritmului Dijkstra. | + | * **complexitate temporală**: Demonstrația se găsește în **Introduction to Algorithms**. Ideea de bază este că de fiecare dată când creștem fluxul pe un drum de ameliorare, o muchie devine saturată. Dacă vreodată această muchie va mai fi folosită în alt drum de ameliorare (în cazul unei corecții), aceasta va fi folosită într-un drum mai lung. Respectivele distanțe sunt monotone și limitate de $O(|V|)$. |
- | * **complexitate spațială** : Se ține o coadă de priorități / un set cum maximum **n** noduri. | + | * **complexitate spațială** : Stocăm o coadă pentru algoritmul BFS. De asemenea, în implementare vom stoca și un vector de părinți pentru a putea parcurge un drum de ameliorare de la T la S (sens invers). |
- | * **optimizare**: Analog Dijkstra, putem să obținem: | + | |
- | * **Prim cu heap binar** - $O(m log n)$. | + | |
- | * **Prim cu heap Fibonacci** - $O(n logn + m)$. | + | |
</spoiler> \\ | </spoiler> \\ | ||
Line 318: | Line 262: | ||
===== TLDR ===== | ===== TLDR ===== | ||
- | * Pentru **minimum spanning tree problem**, am studiat 2 algoritmi (Kruskal și Prim) cu aceeași complexitate ($O(m log m)$). | + | * Ford-Fulkerson este un tipar general, **în practică** vom folosi doar algoritmul Edmonds-Karp. |
- | * Algoritmul lui Kruskal este de preferat pentru simplitatea / intuitivitatea lui, dificultatea reducându-se la a implementa structura de date **DisjointSet**. | + | * Algoritmul de bazează pe pomparea fluxului pe arcele directe și scăderea pe arcele inverse. |
+ | * Ordinea în care drumurile de amelioare sunt găsite nu contează. Este un algoritm greedy de tip constructiv care își corectează deciziile pe parcurs. În final mereu găsește fluxul maxim! | ||
+ | * Dacă în plus avem și costuri pe arce, putem determina flux maxim de cost minim cu Edmonds-Karp înlocuind BFS cu Dijkstra pentru găsirea drumurilor de ameliorare. | ||
===== Exerciții ===== | ===== Exerciții ===== | ||
Line 326: | Line 272: | ||
<note> | <note> | ||
- | Scheletul de laborator se găsește pe pagina [[https://github.com/acs-pa/pa-lab/tree/main/skel/lab11|pa-lab::skel/lab11]]. | + | Scheletul de laborator se găsește pe pagina [[https://github.com/acs-pa/pa-lab/tree/main/skel/lab12|pa-lab::skel/lab12]]. |
</note> | </note> | ||
Line 342: | Line 288: | ||
</note> | </note> | ||
- | ==== Kruskal ==== | + | ==== Edmonds-Karp ==== |
- | Se dă un graf **neorientat** și **conex** cu **n** noduri și **m** muchii (cu costuri oarecare pe muchii). | + | Se dă un graf orientat cu n noduri și m arce. Graful are pe arce **capacitati pozitive**. |
- | Folosiți algoritmul lui **Kruskal** pentru a găsi un **APM**. | + | Folosiși Edmonds-Karp pentru a găsi fluxul maxim între nodul sursă 1 și nodul destinație n în graful dat. |
- | + | ||
- | Task-uri: | + | |
- | + | ||
- | - Găsiți **costul** MST. | + | |
- | - Găsiți care sunt **muchiile** din MST. **ATENȚIE!** Se poate găsi orice **MST** (în caz că există mai mulți). Ordinea muchiilor din vectorul rezultat **NU** este relevantă. | + | |
Line 358: | Line 299: | ||
Restricții și precizări: | Restricții și precizări: | ||
- | * $ n <= 2 * 10^5 $ | + | * $ n <= 10^3 $ |
- | * $ m <= 4 * 10^5 $ | + | * $ m <= 5 * 10^3 $ |
- | * $ -10^3 <= c <= 10^3$, unde c este costul unei muchii | + | * $ 1 <= c <= 110.000$, unde c este capacitatea unui arc |
+ | * Nu există arce de la n la 1. | ||
* timp de execuție | * timp de execuție | ||
* C++: ''%%1s%%'' | * C++: ''%%1s%%'' | ||
- | * Java: ''%%7s%%'' | + | * Java: ''%%2s%%'' |
- | * Vă recomandăm să parcurgeți [[https://infoarena.ro/problema/disjoint | DisjointSet]]. **ATENȚIE|** Scheletul de laborator oferă o astfel de implementare. | + | |
+ | |||
+ | </note> | ||
+ | <note> | ||
+ | Rezultatul se va returna sub forma unui singur număr, reprezentând fluxul maxim ce poate fi trimis prin rețea. | ||
</note> | </note> | ||
Line 375: | Line 321: | ||
==== Extra ==== | ==== Extra ==== | ||
- | * [[https://infoarena.ro/problema/desen|infoarena/desen]] | + | * [[https://infoarena.ro/problema/cuplaj|infoarena/cuplaj]] |
- | * [[https://infoarena.ro/problema/radiatie|infoarena/radiatie]] | + | * [[https://infoarena.ro/problema/fmcm|infoarena/fcmm]] |
- | * [[https://infoarena.ro/problema/bile|infoarena/bile]] | + | * [[https://infoarena.ro/problema/drumuri2|infoarena/drumuri2]] |
- | * [[https://codeforces.com/problemsets/acmsguru/problem/99999/323|codeforces/aviamachinations]] | + | * [[https://infoarena.ro/problema/joc4|inforena/joc4]] |
+ | * [[https://acm.timus.ru/problem.aspx?space=1&num=1533|acm/fat-hobbits]] | ||
===== Referințe ===== | ===== Referințe ===== | ||
- | [0] Chapters **Minimum Spanning Trees**, “Introduction to Algorithms”, Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest and Clifford Stein. | + | [0] Chapter **Maximum Flow**, “Introduction to Algorithms”, Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest and Clifford Stein. |
- | [1] [[http://cs.brown.edu/research/pubs/pdfs/1995/Karger-1995-RLT.pdf | A Randomized Linear-Time Algorithm to Find Minimum Spanning Trees]], David R. Karger, Philip N. Klein, Robert E. Tarjan. | + | [1] [[http://www.cs.yale.edu/homes/lans/readings/routing/ford-max_flow-1956.pdf|Maximal flow through a network]], L.R. Ford, D.R. Fulkerson |
- | [2] [[https://dl.acm.org/doi/pdf/10.1145/355541.355562 | A Minimum Spanning Tree Algorithm with InverseAckermann Type Complexity|]], Bernard, Chazelle. | + | [2] [[http://www.eecs.umich.edu/~pettie/matching/Edmonds-Karp-network-flow.pdf|Theoretical improvements in algorithmic efficiency for network flow problems]], Edmonds, Jack; Karp, Richard M. |
+ | [3] [[https://www.cs.bgu.ac.il/~dinitz/Papers/Dinitz_alg.pdf|Dinitz Algorithm]], Yefim Dinitz | ||