Differences

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

Link to this comparison view

pa:laboratoare:laborator-10 [2022/05/03 14:07]
darius.neatu
pa:laboratoare:laborator-10 [2023/03/15 16:55] (current)
radu.nichita
Line 1: Line 1:
-====== Laborator 10: Drumuri minime în grafuri: surse / destinații multiple. (2/2) ======+====== Laborator 10: Arbori minimi de acoperire ​======
  
  
-{{:​pa:​new_pa:​partners:​adobe-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 businessesmid-market enterprises ​and consumers. Guided by a vision to be the world’s most trusted cybersecurity solutions providerBitdefender is committed to defending organizations ​and individuals around the globe against cyberattacks to transform ​and improve their digital experience.
-\\ \\ \\ Changing the world through digital experiences is what Adobe’s all about. We give everyone - from emerging artists to global brands - everything they need to design ​and deliver exceptional digital experiences! We’re passionate about empowering people ​to create beautiful ​and powerful images, videos, and apps, and transform ​how companies interact with customers across every screen.+
  
 ===== Obiective laborator ===== ===== Obiective laborator =====
  
-În laboratorul anterior am introdus ​contextul pentru **Shortest-paths ​problem** și **Single-source shortest-paths problem**. În laboratorul 10 vom continua cu **All-pairs shortest-paths ​problem**.+În acest laborator vom introduce ​contextul pentru **minimum spanning tree problem** și vom studia algoritmi care pot rezolva această ​problemă.
  
-  ​* Înțelegerea conceptelor de cost asociat unei muchii, relaxare a unei muchii. +  * Prezentarea problemei (diverse variante). 
-  ​* Prezentarea problemei ​drumului de cost minim (diverse variante). +  * Prezentarea algoritmilor pentru ​calcul a unui arbore minim de acoperire.
-  * Prezentarea algoritmilor pentru ​calculul drumurilor minime.+
  
-===== Shortest-paths problem: all-pairs ​=====+===== Importanţă – aplicaţii practice ​=====
  
-Vă rugăm să parcugeți [[https://​ocw.cs.pub.ro/​courses/​pa/​laboratoare/​shortest-paths-problem|Shortest-paths problem]] pentru a vă familiariza cu contextul, problema și notațiile folosite.+Algoritmii pentru determinarea unor arbori (minimi) de acoperire au multiple aplicații practiceCâteva exemple de aplicații sunt:
  
-Concepte necesare:+  * 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]].
  
-  * **cost muchie** / **edge cost** +===== Minimum spanning tree problem ​=====
-  * **cost drum** / **path cost** +
-  * **problema drumurilor minime: surse / destinații multiple** / **all-pairs shortest-paths ​problem** +
-  * **relaxarea unei muchii** / **edge relaxation** +
-  * **reconstruirea unui drum** / **RebuildPath**+
  
-===== Algoritmi =====+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.
  
-În acest laborator vom studia **all-pairs shortest-paths problem**Pentru această problemă, vom prezenta 2 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).
  
-  * **Roy-Floyd**:​ eficient pentru grafuri **dense** ($m >> n$ sau $|E| >> |V|$ - a.k.a. număr de muchii mult mai decât număr de noduri). +Vom adăuga alte notații în continuarea celor introduse anterior.
-  * **Johnson**:​ eficient pentru grafuri **rare** ($n >> m$ sau $|V| >> |E|$ - a.k.a. număr de noduri mult mai mare decât număr de muchii).+
  
-Vom prezenta fiecare algoritm, îl vom analiza, iar la final vom vedea când îl vom folosi pe fiecare. 
  
-Puteți consulta capitolul **All-Pairs Shortest Paths** din **Introduction to Algorithms** [0] pentru mai multe detalii despre acești algoritmi. 
  
-===== Roy-Floyd =====+> **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.
  
-Algoritmul [[https://​en.wikipedia.org/​wiki/​Floyd%E2%80%93Warshall_algorithm|Roy-Floyd]] (**Roy-Floyd** / **Floyd–Warshall** algorithm) rezolvă **shortest-paths problem** în grafuri **G = (V, E)** care sunt **dense**.+<spoiler Exemplu>
  
-==== Roy-Floyd Pseudocod ====+{{https://​ocw.cs.pub.ro/​courses/​_media/​pa/​new_pa/​lab11-spanning-tree-example.png?​800| Arbore de acoperire }}
  
-<code cpp> +În exemplul atașatavem un graf **neorientat** ș**conex** cu următoare configurație:
-// apply Roy-Floyd'​s algorithm for all-pairs shortest-paths problem +
-// +
-// nodes     = list of all nodes from G +
-// adj[node] = the adjacency list of node +
-//             ​example:​ adj[node] = {...neigh, ...} => edge (node, neigh) of cost w[node][neigh] +
-// +
-// +
-// returns: d, p +
-//          d = distance matrix +
-//          p = parent matrix +
-// +
-Roy-Floyd(G=(nodes,​ adj)) { +
-  // STEP 1: initialize results +
-  // d[i][j] = minimum distance from i to j +
-  // p[i][j] = parent of node j, on shortest path from i to j +
-  for (i in nodes) { +
-    for (j in nodes) { +
-      d[i][j] = w[i][j]; ​                         // edge cost (or infinity if missing) +
-      p[i][j] = (w[i][j] != infinity ? i : null); // parent (or null if missing) +
-    } +
-  }+
  
-  ​// STEP 2: For each intermediar node k+  ​* ''​%%n = 5%%''​''​%%m = 5%%''​ 
-  ​// try to update shortest path from to j. +  ​* Pentru acest exemplu, avem **3** arbori de acoperire evidențiațîn aceeașfigură ​(rândul 2).
-  for (k in nodes) { +
-    for (in nodes) { +
-      for (j in nodes+
-        // Is (i -> ... -> k) reunion with (k -> ... -> j) shorter than (i -> ... -> j)? +
-        if (d[i][k] + d[k][j] < d[i][j]) { +
-          d[i][j] = d[i][k] + d[k][j]; +
-          p[i][j] = p[k][j]; +
-        } +
-      } +
-    } +
-  }+
  
-  return d, p; +Observație:​ Un graf **conex** are cel puțin un arbore de acoperire!
-}+
  
-// Usage example: +</spoiler\\
-d, p = Roy-Floyd(G=(nodes,​ adj)); +
-// 1. Use distances from d +
-// 2. Rebuild path from node to source using parents (p) +
-RebuildPath(source,​ destination,​ p); +
-</code> +
-==== Exemple ====+
  
-=== Exemplu Roy-Floyd === 
  
-{{https://​ocw.cs.pub.ro/​courses/​_media/​pa/​new_pa/​lab10-graph-royfloyd-example.png?​512| Exemplu Roy-Floyd}} 
  
-Drumurile minime calculate de algoritmul Roy-Floyd sunt: 
  
-|-|1|2|3|4| \\ +> **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**).
-|1|0|-1|-2|0| \\ +
-|2|4|0|2|4| \\ +
-|3|5|1|0|2| \\ +
-|4|3|-1|1|0| \\+
  
 +<spoiler Exemplu>
  
-<spoiler Explicație pas cu pas> În exemplul atașat, avem un graf **orientat** cu următoare configurație:+{{https://​ocw.cs.pub.ro/​courses/​_media/​pa/​new_pa/​lab11-spanning-forest-example.png?​800| Pădure de arbori de acoperire }}
  
-  * ''​%%n = 4%%''​''​%%m = 5%%''​ +În exemplul atașatavem un graf **neorientat** și **neconex** cu următoare configurație:
-  * 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+
-  ​Construim tabloul ​**d** al distanțelor minime: +
-    ​* **d[u][v] = w[u][v]**, dacă există muchia $(u, v)$. +
-    * **d[u][v] = +∞, dacă **NU%%**%% există muchia $(u, v)$. +
-    * **d[u][u] = 0**, convenție+
-    * Obținem matricea **d_0**:+
  
-|-|1|2|3|4| \\ +  * ''​%%n = 6%%'',​ ''​%%m = 6%%''​ 
-|1|0|+∞|-2|+∞| \\ +  * În figura următoare evidențăm toate pădurile de arbori de acoperire ​9 pe acest exemplu. 
-|2|4|0|3|+∞| \\ +    * Putem elimina în 3 moduri câte o muchie din prima componentă conexă ​deci 3 arbori posibili. 
-|3|+∞|+∞|0|2| \\ +    * Putem elimina în moduri câte o muchie din a doua componentă conexă - deci 3 arbori posibili. 
-|4|+∞|-1|+∞|0| \\+    * Numărul de păduri este $* 3 =9$ (combinăm oricare ​arbori corespunzători componentelor conexe diferite).
  
 +Observație:​ Un graf **neconex** are cel puțin o pădure de arbori de acoperire!
  
-  * Rulăm algoritmul Roy-Floyd, vom scrie matricea pentru fiecare $ k = 1, 2, 3, 4$. +</​spoiler>​ \\
-    * $d_1$ (matricea dupa primul pas din algoritm; se modifică doar **d[2][3]**)+
  
-|-|1|2|3|4| \\ 
-|1|0|+∞|-2|+∞| \\ 
-|2|4|0|**2**|+∞| \\ 
-|3|+∞|+∞|0|2| \\ 
-|4|+∞|-1|+∞|0| \\ 
  
  
-  * $d_2$ (matricea dupa primul pas din algoritm; se modifică doar **d[4][3]**) 
  
-|-|1|2|3|4| \\ +**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).
-|1|0|+∞|-2|+∞| \\ +
-|2|4|0|2|+∞| \\ +
-|3|+∞|+∞|0|2| \\ +
-|4|+∞|-1|**1**|0| v+
  
-  * $d_3$ (matricea dupa primul pas din algoritm; se modifică doar **d[1][4]** și **d[2][4]**)+<spoiler Exemplu>
  
-|-|1|2|3|4| \\ +{{https://​ocw.cs.pub.ro/​courses/​_media/​pa/​new_pa/​lab11-minimum-spanning-tree-example.png?​900Exemplu funcție de cost pentru graf orientat}}
-|1|0|+∞|-2|**0**| \\ +
-|2|4|0|2|**4**| \\ +
-|3|+∞|+∞|0|2| \\ +
-|4|+∞|-1|1|0\\+
  
 +În exemplul atașat, avem un graf **neorientat** și **conex** cu următoare configurație:​
  
-  * $d_4$ (matricea dupa primul pas din algoritm; se modifică doar **d[1][2]**, **d[3][1]** ​ș**d[3][2]**)+  * ''​%%n = 5%%'',​ ''​%%m = 5%%''​ 
 +  * În figura următoare evidențăm toți arborii de acoperire. Doar dintre aceștia au cost minim **5**.
  
-|-|1|2|3|4| \\ +Observație:​ Un graf **conex** are cel puțin un arbore minim de acoperire!
-|1|0|**-1**|-2|0| \\ +
-|2|4|0|2|4| \\ +
-|3|**5**|**1**|0|2| \\ +
-|4|+∞|-1|1|0| \\+
  
 +</​spoiler>​ \\
  
-  * Drumurile minime sunt finale (cele menționate anterior - $d_4$). 
  
-</​spoiler>​ \\+===== Algoritmi =====
  
 +Pentru **minimum spanning tree problem** există mai mulți algoritmi, dintre care amintim:
  
-==== Complexitate ====+  * **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].
  
-  ​* **complexitate temporală**: $T = O(n^3)\ sau\ O(|E|^3)$ +Î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.
-  * **complexitate spațială** : $S = O(1)$+
  
-<spoiler Detalii (analiză + optimizări)>​+===== Kruskal =====
  
-  ​* **complexitate temporală**: Se aplică recurența discutată anterior care pentru fiecare nod intermediar ​**k**, încearcă să actualizeze drumul minim de la **i** la **j**. Cele 3 foruri dau complexitatea temporară. +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!
-  ​* **complexitate spațială** Nu stocăm tablouri auxilare.+
  
-</​spoiler>​ \\+==== Kruskal - Pseudocod ====
  
  
-===== Johnson =====+<​note>​
  
-Algoritmul lui [[https://​en.wikipedia.org/​wiki/​Johnson%27s_algorithm|Johnson]] (**Johnson’s algorithm**) rezolvă **shortest-paths problem** în grafuri **G = (V, E)** care sunt **rare**.+Algoritmul lui Kruskal folosește o structură de date care suportă 2 operații:
  
-Ideea de la care pornește acest algoritm ​este de a rula cel mai rapid algorithm pentru ​**shortest-paths single source**, adică algoritmul lui Dijkstra, pentru fiecare sursă (nod) din graf. Dacă toate costurile sunt pozitive, putem face direct acest lucru. Dacă însă există costuri negative, nu putem aplica Dijkstra pe acest graf. Algoritmul lui Johnson face o preprocesare (în **ComputeH**și calculează un graf echivalent, în care toate costurile sunt pozitive. Pe acest graf se poate aplica Dijkstra și să se afle toate distanțele. Ulterior se face translatarea inversă și se obțin ​**distanțele în graful inițial**.+  * În ce mulțime ​este elementul ​**x**
 +  * Să se reunească mulțimile ​din care fac parte elementele ​**x** și **y**.
  
-Observație:​ Dacă se știe că toate costurile din graf sunt pozitive (nenegative)algoritmul lui Johnson se poate înlocui cu rularea directă a algoritmului Dijkstra pentru toate nodurile din graf.+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.
  
-==== Johnson - Pseudocod ====+ 
 +</​note>​
  
 <code cpp> <code cpp>
-// apply Johnson's algorithm for all-pairs shortest-paths problem+// apply Kruskal's algorithm for computing a Minimum Spanning Tree (MST).
 // //
 // nodes     = list of all nodes from G // nodes     = list of all nodes from G
-// adj[node] ​= the adjacency ​list of node +// edges     = the list of all edges in G 
-//             ​example: ​adj[node] = {..., neigh, ...} => edge (node, neigh) ​of cost w[node][neigh]+//             ​example:​ edge (node, neigh, weight)
 // //
-// returns: ​has_cycled, p +// returns: ​costmst 
-//          ​has_cycle ​negative cycle detection flag (true if found) +//          ​cost the cost of the MST 
-//          ​d = distance matrix (defined only if has_cycle == false) +//          ​mst the actual set of edges in MST
-//          p = parent matrix (defined only if has_cycle =false)+
 // //
-Johnson(G=(nodes, ​adj)) { +Kruskal(G=(nodes, ​edges)) { 
-  // STEP 1Compute adjustment distances h (using Bellmand-Ford). +  // STEP 0initialize results 
-  ​has_cycle, h ComputerH(G)+  ​cost 0// cost of the built MST 
-  ​if (has_cycle+  ​mst = {}; // the actual set of edges (u, vin the built MST
-    return true, null, null; +
-  }+
  
-  // STEP 2Update all costs in G to obtain all costs nonnegative. +  // STEP 1initialize disjoint set data structure 
-  ​foreach ​((u, vin edges) { +  ​// (a tree is created for each node
-    if (w[u][v] !infinity) { +  ​disjointset ​DisjointSet(nodes);
-      w[u][v] = w[u][v] + (h[u] - h[v]); +
-    } +
-  }+
  
-  // STEP 3Now all costs are nonnegative,​ so we can apply Dijsktra. +  // STEP 2
-  ​// Start Dijkstra for each source u, saving just distances. +  ​sort(edgescompare=nondecreasing order by weight);
-  foreach ​(u in nodes) { +
-    d_dijkstrap_dijkstra ​Dijkstra(u, G);+
  
-    ​// STEP 4Compute distance ​(u, v) on initial graph+  ​// STEP 3Add edge by edge to MST if no cycles are created. 
-    ​foreach ​(v in nodes) { +  foreach ( (u, v, win edges ) { 
-      ​d[u][v] = d_dijkstra[v] + (h[v] - h[u]); +    // STEP 3.1: Check if u anv v are in different trees
-      ​p[u][v] = p_dijkstra[v];+    ​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 += { (uv) };
     }     }
   }   }
  
-  return ​falsed, p // no negative cycles detected+  return ​costmst;
 } }
- 
-ComputeH(G=(nodes,​ adj)){ 
-  // STEP 0: Create a new **temporary** graph 
-  // * add a new node 
-  source = new node; 
-  new_nodes = nodes + { source }; 
-  new_adj = adj; 
-  // * add a new edge (source, node) with cost 0 for all existing nodes 
-  for (node in nodes) { 
-    new_adj[source].push_back(node);​ 
-    w[source][node] = 0; 
-  } 
- 
-  // STEP 1: Run Bellman-Ford on the new graph. Save just flag and distances. 
-  has_cycle, h, _ = Bellmann-Ford(source,​ new_G=(new_nodes,​ new_adj))) 
-  if (has_cycle) { 
-    return true, null; // negative cycle detected 
-  } 
- 
-  return false, d; 
-} 
- 
  
 // Usage example: // Usage example:
-has_cycled, p Johnson(source, ​G=(nodes, ​adj)); +costmst Kruskal(G=(nodes, ​edges)); 
-if (has_cycle) { +// 1. Use cost of MST. 
-  print "Has Cycle!"​ +// 2. Use edges of MST (e.g. actually build the tree).
-  STOP. +
-} else { +
-  ​// 1. Use distances from d +
-  // (e.g. d[node] = distance from source to node) +
-  // +
-  // 3Rebuild path from node to source using parents (p) +
-  RebuildPath(source,​ destination,​ p); +
-}+
 </​code>​ </​code>​
-==== Exemple ====+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.
  
-=== Exemplu Johnson ===+  * 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ă.
  
-În această secțiune exemplificăm grafic cum rulează algoritmul lui Johnson pe un graf dat.+==== Exemple ====
  
-<spoiler Explicație pas cu pas> {{https://​ocw.cs.pub.ro/​courses/​_media/​pa/​new_pa/​lab10-graph-johnson-example01.png?​512| ​Exemplu ​Johnson 1/3}}+=== Exemplu ​Kruskal ===
  
-În exemplul atașat, avem un graf **orientat** cu următoare configurație:+{{https://​ocw.cs.pub.ro/​courses/​_media/​pa/​new_pa/​lab11-kruskal.png?​900| Exemplu Kruskal}}
  
-  ​''​%%n = 5%%'',​ ''​%%m = 8%%''​ +Un **MST** găsit cu algoritmul lui Kruskal este: ${ (12)(2, 4)(2, 3); (3, 5)}$ de cost **5**.
-  ​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. (Observăm că nu avem ciclu de cost negativdeci are sens să rulăm un algoritm de drumuri minime)+
-  * ''​%%STEP 1%%'':​ Adăugăm un nod fictiv ​(exemplu nodul ''​%%6%%''​). Îl vom uni de fiecare nod din graful inițial ​(1, 2, 3, 4, 5) cu muchie ​de cost 0Obținem graful din figura următoare:+
  
-{{https://​ocw.cs.pub.ro/​courses/​_media/​pa/​new_pa/​lab10-graph-johnson-example02.png?​512| Exemplu Johnson - 2/3}}+<spoiler Explicație pas cu pas>
  
-  ​Pe acest graf putem rula o dată algoritmul Bellman-Ford,​ considerând sursă noul nod adăugat. +În exemplul atașat, avem un graf **neorientat** cu următoare configurație:
-  * Obținem vectorul de distanțe $h[node] = distanța\ de\ la\ nodul\ fictiv\ (6)\ la\ nodul\ node$+
  
-|node|1|2|3|4|5|6| \\ +  * ''​%%n = 5%%'',​ ''​%%m = 5%%''​ 
-|h[node]|-1|-7|-4|0|-2|0| \\+  * Funcția de cost ''​%%w%%''​ are valorile menționate pe muchii. 
 +  * Muchiile sortate după cost sunt: 
 +    * $(3, 5)$ de cost $-1
 +    * $(24)$ 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 ​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**.
  
-  * ''​%%STEP 2%%'':​ Revenim la graful inițial (cel cu 5 noduri, 8 muchii) și îi alterăm costurile:​ +</​spoiler>​ \\
-    * $w[1][4] = w[1][4] + (h[1] - h[4]) = 2 + [ (-1) - (0) ] = 1$ +
-    * $w[2][1] = w[2][1] + (h[2] - h[1]) = 6 + [ (-7) - (-1) ] = 0$ +
-    * $w[2][3] = w[2][3] + (h[2] - h[3]) = 3 + [ (-7) - (-4) ] = 0$ +
-    * $w[3][1] = w[3][1] + (h[3] - h[1]) = 4 + [ (-4) - (-1) ] = 1$ +
-    * $w[3][4] = w[3][4] + (h[3] - h[4]) = 5 + [ (-4) - (0) ] = 1$ +
-    * $w[4][2] = w[4][2] + (h[4] - h[2]) = -7 + [ (0) - (-7) ] = 0$ +
-    * $w[4][5] = w[4][2] + (h[4] - h[2]) = -2 + [ (0) - (-2) ] = 0$ +
-    * $w[5][4] = w[5][4] + (h[5] - h[4]) = -1 + [ (-2) - (-4) ] = 1$ +
-  * Obținem graful din următoarea figură:+
  
-{{https://​ocw.cs.pub.ro/​courses/​_media/​pa/​new_pa/​lab10-graph-johnson-example03.png?​512| Exemplu Johnson - 2/3}} 
  
-  * ''​%%STEP 3%%'':​ Deoarece toate costurile sunt pozitive, putem rula Dijkstra pe rând, pentru fiecare sursă ''​%%source ​1, 2, 3, 4, 5%%''​ din graf. +==== Complexitate ​====
-  * ''​%%STEP 3%%'':​ $ source ​1 $ +
-    * Vectorul de distanțe este ''​%%d_dijkstra%%''​ atașat mai jos. +
-    * Distanțele față de nodul ''​%%1%%''​ pe graful inițial sunt: +
-      * $d[1][1] ​0$ +
-      * $d[1][2] ​d_{dijkstra}[2] + (h[2] - h[1]) 1 + [ (-7) - (-1) ] -5$ +
-      * $d[1][3] ​d_{dijkstra}[3] + (h[3] - h[1]) 1 + [ (-4) - (-1) ] = -2$ +
-      * $d[1][4] = d_{dijkstra}[4] + (h[4] - h[1]) = 1 + [ (0) - (-1) ] = 2$ +
-      * $d[1][5] = d_{dijkstra}[5] + (h[5] - h[1]) = 1 + [ (-2) - (-1) ] = 0$+
  
-|node|1|2|3|4|5| ​\\ +  * **complexitate temporală**:​ $T = O(m * log m)sauO(|E* log |E|)$ 
-|d_dijkstra[node]|0|1|1|1|1| \\+  * **complexitate spațială** : $S = O(n)$
  
 +<spoiler Detalii (analiză + optimizări)>​
  
-|node|1|2|3|4|5| \\ +  * **complexitate temporală**:​ 
-|d[1][node]|0|-5|-2|2|0| \\+    * 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>​ \\
  
-  * ''​%%STEP 3%%'':​ $ source = 2 $ 
-    * Vectorul de distanțe este ''​%%d_dijkstra%%''​ atașat mai jos. 
-    * Distanțele față de nodul ''​%%2%%''​ pe graful inițial sunt: 
-      * $d[2][1] = d_{dijkstra}[1] + (h[1] - h[2]) = 0 + [ (-1) - (-7) ] = 6$ 
-      * $d[2][2] = 0$ 
-      * $d[2][3] = d_{dijkstra}[3] + (h[3] - h[2]) = 0 + [ (-4) - (-7) ] = 3$ 
-      * $d[2][4] = d_{dijkstra}[4] + (h[4] - h[2]) = 1 + [ (0) - (-7) ] = 8$ 
-      * $d[2][5] = d_{dijkstra}[5] + (h[5] - h[2]) = 1 + [ (-2) - (-7) ] = 6$ 
  
-|node|1|2|3|4|5| \\ +===== [Studiu de cazPrim =====
-|d_dijkstra[node]|0|0|0|1|1| \\+
  
 +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.
  
-|node|1|2|3|4|5| \\ +==== Prim Pseudocod ====
-|d[2][node]|6|0|3|8|6| \\+
  
 +<spoiler Prim - Pseudocod>​
  
-  * ''​%%STEP 3%%'':​ $ source = 3 $ +<code cpp> 
-    * Vectorul de distanțe este ''​%%d_dijkstra%%''​ atașat mai jos+// apply Prim's algorithm for computing a Minimum Spanning Tree (MST)
-    ​* Distanțele față de nodul ''​%%3%%''​ pe graful inițial sunt: +// 
-      * $d[3][1] = d_{dijkstra}[1] + (h[1] - h[3]) = 1 + (-1) - (-4) ] = 4$ +// source ​   = source / starting node for computing / expading MST 
-      * $d[3][2] = d_{dijkstra}[2] + (h[2] - h[3]) = 1 + [ (-7- (-4] = -2$ +//             (if not specified, node is defaulted) 
-      * $d[3][3] = 0$ +// nodes     list of all nodes from G 
-      * $d[3][4] = d_{dijkstra}[4] + (h[4- h[3]) = 1 + [ (0) - (-4] = 5$ +// adj[node] = the adjacency list of node 
-      ​* $d[3][5] = d_{dijkstra}[5] ​(h[5- h[3]) 1 + [ (-2) - (-4) ] = 3$+//             ​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 
 +  }
  
-|node|1|2|3|4|5| \\ +  // STEP 0: initialize results 
-|d_dijkstra[node]|1|1|0|1|1| \\+  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 = {};
  
-|node|1|2|3|4|5| \\ +  // STEP 2: add the source(s) into pq 
-|d[3][node]|4|-2|0|5|3| \\+  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%%'':​ $ source = 4 $ +    // STEP 3.2Extend MST with edge p[node] - node 
-    * Vectorul de distanțe este ''​%%d_dijkstra%%''​ atașat mai jos. +    used[node] = true; 
-    * Distanțele față de nodul ''​%%4%%''​ pe graful inițial sunt: +    ​if ​(p[node!null{ 
-      * $d[4][1] = d_{dijkstra}[1] + (h[1] - h[4]0 + [ (-1) - (0) ] = -1$ +      ​cost += d[node]; 
-      * $d[4][2] = d_{dijkstra}[2] + (h[2- h[4]) 0 + [ (-7- (0) ] = -7$ +      ​mst += { (node, p[node]) }; 
-      ​* $d[4][3] = d_{dijkstra}[4] ​(h[3] - h[4]) 0 + [ (-4) - (0) ] = -4$ +    }
-      * $d[4][4] = 0$ +
-      ​* $d[4][5] ​d_{dijkstra}[5] + (h[5] - h[4]) = 0 + [ (-2) - (0) ] = 2$+
  
-|node|1|2|3|4|5| \\ +    // STEP 3.3: relax all edges (node, neigh) 
-|d_dijkstra[node]|0|0|0|0|0| \\+    ​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
 +      }
 +    }
 +  }
  
-|node|1|2|3|4|5| \\ +  return cost, mst; 
-|d[4][node]|-1|-7|-4|0|2| \\ +}
- +
- +
-  * ''​%%STEP 3%%'':​ $ source = 5 $ +
-    * Vectorul de distanțe este ''​%%d_dijkstra%%''​ atașat mai jos. +
-    * Distanțele față de nodul ''​%%4%%''​ pe graful inițial sunt: +
-      * $d[5][1] = d_{dijkstra}[1] + (h[1] - h[5]) = 2 + [ (-1) - (-2) ] = 3$ +
-      * $d[5][2] = d_{dijkstra}[2] + (h[2] - h[5]) = 2 + [ (-7) - (-2) ] = -3$ +
-      * $d[5][3] = d_{dijkstra}[4] + (h[3] - h[5]) = 1 + [ (-4) - (-2) ] = -1$ +
-      * $d[5][4] = d_{dijkstra}[5] + (h[4] - h[5]) = 2 + [ (0) - (-2) ] = 4$ +
-      * $d[5][5] = 0$ +
- +
-|node|1|2|3|4|5| \\ +
-|d_dijkstra[node]|2|2|1|2|0| \\ +
- +
- +
-|node|1|2|3|4|5| \\ +
-|d[5][node]|3|-3|-1|4|0| \\ +
- +
- +
-  * STOP! Am obținut toate distanțele $d[u][v]$ cerute!+
  
 +// 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>​ \\ </​spoiler>​ \\
  
Line 391: Line 304:
 ==== Complexitate ==== ==== Complexitate ====
  
-  * **complexitate temporală**:​ $T = O(n * m * log (n))\ sau\ O(|V| * |E| * log (|V|))$ +  * **complexitate temporală**:​ $T = O(m * log n)\ sau\ O(|E| * log |V|)$ 
-  * **complexitate spațială** : $S = O(n + m)\ sau \ O(|V| + |E|)$+  * **complexitate spațială** : $S = O(n)$
  
 <spoiler Detalii (analiză + optimizări)>​ <spoiler Detalii (analiză + optimizări)>​
  
-  * **complexitate temporală**:​ +  * **complexitate temporală**: ​Se încearcă relaxarea tuturor celor **m** muchii ​din graf, analog algoritmului ​Dijkstra. 
-    ​* **ComputeH**:​ +  * **complexitate spațială** : Se ține o coadă de priorități / un set cu maximum ​**n** noduri
-      * Construire graf nou - $O(n + m))$. +  * **optimizare**: ​Analog Dijkstra, putem să obținem: 
-      ​Aplicare Bellman-Ford pe noul graf - $O(n m)$. +    * **Prim cu heap binar** - $O(log n)$. 
-    * **Update edges** - pasul se face în $O(m)$. +    * **Prim cu heap Fibonacci** - $O(n logn + m)$.
-    * Rularea Dijkstra pentru fiecare nod din graf - este complexitatea de la rulare ​Dijkstra ​pentru un singur nod sursă (a.k.a. $O(m log n)$), multiplicată cu numărul de noduri. +
-    * În final ajungem la un total de $O(n * m * log(n) + n * m + n + m) = O(n * m + log(n))$+
-  * **complexitate spațială** : Se construiește ​un alt graf (**new_node** - n**new_adj** - m), se produc câțiva vectori temporari de lungime n (**h**, **d_dijkstra**,​ **p_dijkstra**)+
-  * **optimizare**: ​Deoarece Dijsktra se poate optimiza (vezi laborator anterior), putem obține $O(n^2 * log n + n * m)$ complexitatea pentru ultima etapăComplexitatea finală este $O(n ^ 2 log (m) n * m)$.+
  
 </​spoiler>​ \\ </​spoiler>​ \\
Line 411: Line 320:
 ===== TLDR ===== ===== TLDR =====
  
-  * Pentru ​cazul **shortest-path single source** am studiat ​în laboratorul anterior algoritmul lui Dijkstra / algoritmul Bellman-Ford. +  * Pentru **minimum spanning tree problem**am studiat ​2 algoritmi (Kruskal ​și Prim) cu aceeași complexitate ($O(m log m)$)
-  * Pentru cazul **shortest-path all-pairs**,​ discuția se facă după numărul de muchii din graf: +  Algoritmul lui Kruskal este de preferat pentru simplitatea / intuitivitatea lui, dificultatea reducându-se la a implementa structura de date **DisjointSet**.
-    * **graf dens**: aplicăm Roy-Floyd ​și obținem ​$O(n ^ 3)$. +
-    * **graf rar**: aplicăm Johnson și obținem $O(n * m * log (n))$.+
  
 ===== Exerciții ===== ===== Exerciții =====
Line 421: Line 328:
 <​note>​ <​note>​
  
-Scheletul de laborator se găsește pe pagina [[https://​github.com/​acs-pa/​pa-lab/​tree/​main/​skel/​lab10|pa-lab::​skel/​lab10]].+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>​
Line 437: Line 344:
 </​note>​ </​note>​
  
-==== Roy-Floyd ​====+==== Kruskal ​====
  
-Se dă un graf **orientat** cu **n** noduri. Graful are **costuri strict pozitive**.+Se dă un graf **neorientat** și **conex** cu **n** noduri ​și **m** muchii (cu costuri oarecare pe muchii).
  
-Se dă matricea ponderilor - **w**, se cere matricea drumurilor minime - **d**, aplicând algoritmul ​**Roy-Floyd**.+Folosiți algoritmul lui **Kruskal** pentru a găsi un **MST**.
  
 +Task-uri:
  
-<note warning>​ +  - Găsiți **costul** ​MST
- +  - Găsiț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ă.
-Restricții șprecizări:​ +
- +
-  ​$ n <= 100 $ +
-  ​$ 0 <= c <= 1.000$, unde c este costul ​unui arc. +
-    ​Dacă **nu există arc** între o pereche de noduri x și y, distanța de la nodul x la nodul y din **matricea ponderilor** va fi 0+
-    * Dacă după aplicarea algoritmului **nu se găsește drum** pentru o pereche de noduri x șy, se va considera ​**distanța** dintre ele egală cu 0 (se stochează în **matricea distantelor** valoarea 0). +
-    ​Drumul de la nodul i la nodul i are lungime 0 (prin convenție). +
-  ​timp de execuție +
-    ​C++: 1s +
-    ​Java: 8s +
- +
- +
-</​note>​ +
- +
-==== Johnson ==== +
- +
-Se dă un graf **orientat** cu **n** noduri. Graful are **costuri oarecare** (pot fi șnegative). +
- +
-Se dă lista de adiacență cu costurile aferente, se cere matricea drumurilor minime - **d**, aplicând algoritmul **Johnson**.+
  
  
Line 471: Line 360:
 Restricții și precizări: Restricții și precizări:
  
-  * $ n <= 1000 +  * $ n <= 2 * 10^5 
-  * $ m <= 25000 +  * $ m <= 4 * 10^5 
-  * $ -1000 <= c <= 1.000$, unde c este costul ​unui arc. +  * $ -10^3 <= c <= 10^3$, unde c este costul ​unei muchii
-    * Dacă **nu există arc** între o pereche de noduri x și y, distanța de la nodul x la nodul y din **matricea ponderilor** va fi 0. +
-    * Dacă după aplicarea algoritmului **nu se găsește drum** pentru o pereche de noduri x și y, se va considera **distanța** dintre ele egală cu 0 (se stochează în **matricea distantelor** valoarea 0). +
-    * Drumul de la nodul i la nodul i are lungime 0 (prin convenție). +
-    * Dacă graful conține un ciclu de cost negativ, se va afișa mesajul: Ciclu negativ!+
   * timp de execuție   * timp de execuție
-    * C++: 1s +    * C++: ''​%%1s%%''​ 
-    * Java: 8s+    * Java: ''​%%7s%%''​ 
 +  * Vă recomandăm să parcurgeți [[https://​infoarena.ro/​problema/​disjoint | DisjointSet]]. **ATENȚIE!** Scheletul de laborator oferă o astfel de implementare.
  
  
Line 491: Line 377:
 ==== Extra ==== ==== Extra ====
  
-  * [[https://​infoarena.ro/​problema/​rfinv|infoarena/rfinv]] +  * [[https://​infoarena.ro/​problema/​desen|infoarena/desen]] 
-  * [[https://​infoarena.ro/​problema/​rf|infoarena/rf]] +  * [[https://​infoarena.ro/​problema/​radiatie|infoarena/radiatie]] 
-  * [[https://​infoarena.ro/​problema/​coach|infoarena/coach]] +  * [[https://​infoarena.ro/​problema/​bile|infoarena/bile]] 
-  * [[https://​codeforces.com/​contest/295/problem/B|codeforces/​greg-and-graph]] +  * [[https://​codeforces.com/​problemsets/acmsguru/problem/99999/323|codeforces/​aviamachinations]]
-  * [[https://​codeforces.com/​contest/​25/​problem/​C|codeforces/​roads-in-berland]] +
-  * [[https://​codeforces.com/​problemset/​problem/​21/​D|codeforces/​traveling-graph]] +
-  * [[https://​codeforces.com/​gym/​101498/​problem/L|codeforces/​the-shortest-path]]+
  
 ===== Referințe ===== ===== 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.+[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.txt · Last modified: 2023/03/15 16:55 by radu.nichita
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