Differences

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

Link to this comparison view

pa:laboratoare:laborator-10 [2022/03/01 23:57]
darius.neatu created
pa:laboratoare:laborator-10 [2025/04/27 19:52] (current)
darius.neatu [Kruskal]
Line 1: Line 1:
-====== Laborator 10: Drumuri minime (2/2) ======+====== Laborator 10: Arbori minimi de acoperire ​======
  
-TODO Gigel+ 
 +{{:​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. 
 + 
 +===== 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 - 9 pe acest exemplu. 
 +    * Putem elimina în 3 moduri câte o muchie din prima componentă conexă - deci 3 arbori posibili. 
 +    * Putem elimina în 3 moduri câte o muchie din a doua componentă conexă - deci 3 arbori posibili. 
 +    * Numărul de păduri este $3 * 3 =9$ (combinăm oricare 2 arbori corespunzători componentelor conexe diferite). 
 + 
 +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 
 +// edges     = the list of all edges in G 
 +//             ​example:​ 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 = 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, adj)) { 
 +  // 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[node]) { 
 +      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, weight) in adj[node]) { 
 +      // Can we find a better / shorter link / edge from node to current MST? 
 +      if (!used[neigh] && weight < d[neigh]) { 
 +        d[neigh] = weight; ​                             // 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>​ 
 + 
 +==== Task-1: 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 **MST**. 
 + 
 +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>​ 
 + 
 + 
 +=== Task-2: Costul minim să conectăm n puncte 2D în plan === 
 +Rezolvați problema [[ https://​leetcode.com/​problems/​min-cost-to-connect-all-points/​description/​ | Min Cost to Connect All Points ]] pe LeetCode. 
 + 
 + 
 + 
 +==== 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.
  
pa/laboratoare/laborator-10.1646171828.txt.gz · Last modified: 2022/03/01 23:57 by darius.neatu
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