This shows you the differences between two versions of the page.
|
pa:laboratoare:laborator-11 [2022/05/17 14:25] darius.neatu [Laborator 11: Arbori minimi de acoperire] |
— (current) | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ====== Laborator 11: Arbori minimi de acoperire ====== | ||
| - | |||
| - | |||
| - | {{:pa:new_pa:partners:bitdefender-logo.png?160 |}} | ||
| - | |||
| - | 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 ===== | ||
| - | |||
| - | În acest laborator vom introduce contextul pentru **minimum spanning tree problem** și vom studia algoritmi care pot rezolva această problemă. | ||
| - | |||
| - | * Prezentarea problemei (diverse variante). | ||
| - | * Prezentarea algoritmilor pentru calcul a unui arbore minim de acoperire. | ||
| - | |||
| - | ===== Importanţă – aplicaţii practice ===== | ||
| - | |||
| - | Algoritmii pentru determinarea unor arbori (minimi) de acoperire au multiple aplicații practice. Câteva exemple de aplicații sunt: | ||
| - | |||
| - | * Designul circuitelor electronice: | ||
| - | * 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 ===== | ||
| - | |||
| - | 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. | ||
| - | |||
| - | 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. | ||
| - | |||
| - | |||
| - | |||
| - | > **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. | ||
| - | |||
| - | <spoiler Exemplu> | ||
| - | |||
| - | {{https://ocw.cs.pub.ro/courses/_media/pa/new_pa/lab11-spanning-tree-example.png?800| Arbore de acoperire }} | ||
| - | |||
| - | În exemplul atașat, avem un graf **neorientat** și **conex** cu următoare configurație: | ||
| - | |||
| - | * ''%%n = 5%%'', ''%%m = 5%%'' | ||
| - | * Pentru acest exemplu, avem **3** arbori de acoperire evidențiați în aceeași figură (rândul 2). | ||
| - | |||
| - | Observație: Un graf **conex** are cel puțin un arbore de acoperire! | ||
| - | |||
| - | </spoiler> \\ | ||
| - | |||
| - | |||
| - | |||
| - | |||
| - | > **Pădure de arbori de acoperire** / **spanning tree forest**: Se definește similar, dar pentru graf 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?800| 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> \\ | ||
| - | |||
| - | |||
| - | |||
| - | |||
| - | > **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). | ||
| - | |||
| - | <spoiler Exemplu> | ||
| - | |||
| - | {{https://ocw.cs.pub.ro/courses/_media/pa/new_pa/lab11-minimum-spanning-tree-example.png?900| Exemplu funcție de cost pentru graf orientat}} | ||
| - | |||
| - | În exemplul atașat, avem un graf **neorientat** și **conex** cu următoare configurație: | ||
| - | |||
| - | * ''%%n = 5%%'', ''%%m = 5%%'' | ||
| - | * În figura următoare evidențăm toți arborii de acoperire. Doar 2 dintre aceștia au cost minim **5**. | ||
| - | |||
| - | Observație: Un graf **conex** are cel puțin un arbore minim de acoperire! | ||
| - | |||
| - | </spoiler> \\ | ||
| - | |||
| - | |||
| - | ===== Algoritmi ===== | ||
| - | |||
| - | Pentru **minimum spanning tree problem** există mai mulți algoritmi, dintre care amintim: | ||
| - | |||
| - | * **Kruskal**: abordare greedy care se bazează pe sortarea muchiile după cost. Mai multe detalii în **Introduction to Algorithms** [0]. | ||
| - | * **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 Inverse Ackermann 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. | ||
| - | |||
| - | ===== Kruskal ===== | ||
| - | |||
| - | 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! | ||
| - | |||
| - | ==== Kruskal - Pseudocod ==== | ||
| - | |||
| - | |||
| - | <note> | ||
| - | |||
| - | Algoritmul lui Kruskal folosește o structură de date care suportă 2 operații: | ||
| - | |||
| - | * În ce mulțime este elementul **x**? | ||
| - | * 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. | ||
| - | |||
| - | |||
| - | </note> | ||
| - | |||
| - | <code cpp> | ||
| - | // 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 | ||
| - | // (a tree is created for each node) | ||
| - | disjointset = new DisjointSet(nodes); | ||
| - | |||
| - | // STEP 2: | ||
| - | sort(edges, compare=nondecreasing order by weight); | ||
| - | |||
| - | // 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. | ||
| - | cost += w; | ||
| - | mst += { (u, v) }; | ||
| - | } | ||
| - | } | ||
| - | |||
| - | return cost, mst; | ||
| - | } | ||
| - | |||
| - | // Usage example: | ||
| - | 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ăugarea acesteia, nu vom crea ciclu, ci vom crea un arbore mai mare (din cei 2 mici reuniți). | ||
| - | * Altfel, nu putem adăuga muchia, deoarece am crea un ciclu. Această muchie se ignoră. | ||
| - | |||
| - | ==== Exemple ==== | ||
| - | |||
| - | === Exemplu Kruskal === | ||
| - | |||
| - | {{https://ocw.cs.pub.ro/courses/_media/pa/new_pa/lab11-kruskal.png?900| Exemplu Kruskal}} | ||
| - | |||
| - | 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 adaugă la **MST**. Se reunesc 2 arbori. | ||
| - | * STEP 2: Muchia cu costul cel mai mic care e nefolosită este $(2, 4)$ de cost 1. Se adaugă la **MST**. Se reunesc 2 arbori. | ||
| - | * STEP 3: Muchia cu costul cel mai mic care e nefolosită este $(2, 3)$ de cost 2. Se adaugă 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 $(2, 5)$ nu va fi folosită deoarece are capetele în același arbore. | ||
| - | * Muchia cu costul cel mai mic care e nefolosită este $(1, 2)$ de cost 3. Se adaugă 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> \\ | ||
| - | |||
| - | |||
| - | ==== Complexitate ==== | ||
| - | |||
| - | * **complexitate temporală**: $T = O(m * log m)\ sau\ O(|E| * log |E|)$ | ||
| - | * **complexitate spațială** : $S = O(n)$ | ||
| - | |||
| - | <spoiler Detalii (analiză + optimizări)> | ||
| - | |||
| - | * **complexitate temporală**: | ||
| - | * 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**). | ||
| - | * ATENȚIE! Complexitatea conține funcția logaritm iterat, nu logaritm ([[https://en.wikipedia.org/wiki/Iterated_logarithm | Iterated logarithm]]), care pentru valorile uzuale din problemele de algoritmică poate fi aproximată cu o constantă foarte mică. | ||
| - | * **complexitate spațială** : Se ține o structură de date **DisjointSet** cu maximum **n** noduri. | ||
| - | |||
| - | </spoiler> \\ | ||
| - | |||
| - | |||
| - | ===== [Studiu de caz] Prim ===== | ||
| - | |||
| - | 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. | ||
| - | |||
| - | ==== Prim Pseudocod ==== | ||
| - | |||
| - | <spoiler Prim - Pseudocod> | ||
| - | |||
| - | <code cpp> | ||
| - | // apply Prim's algorithm for computing a Minimum Spanning Tree (MST). | ||
| - | // | ||
| - | // 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 | ||
| - | pq = {}; | ||
| - | |||
| - | // STEP 2: add the source(s) into pq | ||
| - | d[source] = 0; // distance from source to MST | ||
| - | p[source] = null; // source never has parent | ||
| - | pq.push( (source, d[source]) ); | ||
| - | |||
| - | // STEP 3: Build MST. | ||
| - | 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 | ||
| - | 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> \\ | ||
| - | |||
| - | |||
| - | ==== Complexitate ==== | ||
| - | |||
| - | * **complexitate temporală**: $T = O(m * log n)\ sau\ O(|E| * log |V|)$ | ||
| - | * **complexitate spațială** : $S = O(n)$ | ||
| - | |||
| - | <spoiler Detalii (analiză + optimizări)> | ||
| - | |||
| - | * **complexitate temporală**: Se încearcă relaxarea tuturor celor **m** muchii din graf, analog algoritmului Dijkstra. | ||
| - | * **complexitate spațială** : Se ține o coadă de priorități / un set 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> \\ | ||
| - | |||
| - | |||
| - | ===== TLDR ===== | ||
| - | |||
| - | * Pentru **minimum spanning tree problem**, am studiat 2 algoritmi (Kruskal și Prim) cu aceeași complexitate ($O(m log m)$). | ||
| - | * Algoritmul lui Kruskal este de preferat pentru simplitatea / intuitivitatea lui, dificultatea reducându-se la a implementa structura de date **DisjointSet**. | ||
| - | |||
| - | ===== Exerciții ===== | ||
| - | |||
| - | |||
| - | <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]]. | ||
| - | |||
| - | </note> | ||
| - | <note warning> | ||
| - | |||
| - | Înainte de a rezolva exercițiile, asigurați-vă că ați citit și înțeles toate precizările din secțiunea [[https://ocw.cs.pub.ro/courses/pa/skel_graph | Precizari laboratoare 07-12]]. | ||
| - | |||
| - | Prin citirea acestor precizări vă asigurați că: | ||
| - | |||
| - | * știți **convențiile** folosite | ||
| - | * evitați **buguri** | ||
| - | * evitați **depunctări** la lab/teme/test | ||
| - | |||
| - | |||
| - | </note> | ||
| - | |||
| - | ==== Kruskal ==== | ||
| - | |||
| - | Se dă un graf **neorientat** și **conex** cu **n** noduri și **m** muchii (cu costuri oarecare pe muchii). | ||
| - | |||
| - | Folosiți algoritmul lui **Kruskal** pentru a găsi un **APM**. | ||
| - | |||
| - | 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ă. | ||
| - | |||
| - | |||
| - | <note warning> | ||
| - | |||
| - | Restricții și precizări: | ||
| - | |||
| - | * $ n <= 2 * 10^5 $ | ||
| - | * $ m <= 4 * 10^5 $ | ||
| - | * $ -10^3 <= c <= 10^3$, unde c este costul unei muchii | ||
| - | * timp de execuție | ||
| - | * C++: ''%%1s%%'' | ||
| - | * Java: ''%%7s%%'' | ||
| - | * Vă recomandăm să parcurgeți [[https://infoarena.ro/problema/disjoint | DisjointSet]]. **ATENȚIE!** Scheletul de laborator oferă o astfel de implementare. | ||
| - | |||
| - | |||
| - | </note> | ||
| - | |||
| - | ==== BONUS ==== | ||
| - | |||
| - | La acest laborator, asistentul va alege 1-2 probleme din secțiunea extra. | ||
| - | |||
| - | ==== Extra ==== | ||
| - | |||
| - | * [[https://infoarena.ro/problema/desen|infoarena/desen]] | ||
| - | * [[https://infoarena.ro/problema/radiatie|infoarena/radiatie]] | ||
| - | * [[https://infoarena.ro/problema/bile|infoarena/bile]] | ||
| - | * [[https://codeforces.com/problemsets/acmsguru/problem/99999/323|codeforces/aviamachinations]] | ||
| - | |||
| - | ===== Referințe ===== | ||
| - | |||
| - | [0] Chapters **Minimum Spanning Trees**, “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. | ||
| - | |||
| - | [2] [[https://dl.acm.org/doi/pdf/10.1145/355541.355562 | A Minimum Spanning Tree Algorithm with InverseAckermann Type Complexity|]], Bernard, Chazelle. | ||