Differences

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

Link to this comparison view

pa:laboratoare:laborator-09 [2014/04/23 15:51]
traian.rebedea [1.Buncar (8p)]
pa:laboratoare:laborator-09 [2023/06/10 18:58] (current)
radu.nichita [Exemple]
Line 1: Line 1:
-====== Laborator 9 - Arbori Minimi de Acoperire ​======+====== Laborator 09: Drumuri minime în grafuri: surse / destinații multiple. (2/2) ====== 
 + 
 + 
 +{{:​pa:​new_pa:​partners:​adobe-logo.png?​155 |}} 
 +\\ \\ \\ 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 =====
-  *Însuşirea conceptului de arbore minim de acoperire; 
-  *Înţelegerea modului de funcţionare a algoritmilor de determinare a unui arbore minim de acoperire prezentaţi;​ 
-  *Aplicarea algoritmilor în rezolvarea problemelor;​ 
  
-===== Importanţă – aplicaţii practice =====+Î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**.
  
-Găsirea unui arbore minim de acoperire pentru un graf are aplicaţii în domenii cât se poate de variate: +  ​Înțelegerea conceptelor ​de cost asociat unei muchiirelaxare ​a unei muchii
-  ​*Reţele (de calculatoare,​ telefonie, cablu TV, electricitate,​ drumuri): se doreşte interconectarea mai multor puncte, cu un cost redus şi atunci este utilă cunoaşterea arborelui care conectează toate punctelecu cel mai mic cost posibil. STP(Spanning Tree Protocol) este un protocol de rutare care previne apariţia buclelor într-un LAN, şi se bazează pe crearea unui arbore de acoperire. Singurele legături active sunt cele care apar în acest arbore, iar astfel se evită buclele. +  * Prezentarea problemei drumului ​de cost minim (diverse variante)
-  *Segmentarea imaginilor: împărţirea unei imagini în regiuni de pixeli cu proprietăţi asemănătoare. E utilă mai apoi în analiza medicală ​a unei zone afectate de o tumoare de exemplu+  * Prezentarea algoritmilor ​pentru ​calculul drumurilor minime.
-  *Algoritmi ​de aproximare pt probleme NP-dure: problema comis-voiajorului,​ arbori Steiner+
-  *Clustering: ​pentru ​detectarea de clustere cu forme neregulate [8], [9].+
  
-===== Descrierea problemei şi a rezolvărilor ​=====+===== Shortest-paths problem: all-pairs ​=====
  
-Dându-se un graf conex neorientat G =(V, E), se numeşte arbore de acoperire al lui G un subgraf G’=(V, E’)  care conţine toate vârfurile grafului G şi o submulţime minimă de muchii E’⊆ E cu proprietatea că uneşte toate vârfurile şnu conţine cicluriCum G’ este conex şi aciclic, el este arborePentru un graf oarecareexistă mai mulţarbori de acoperire.+Vă rugăm să parcugeț[[https://​ocw.cs.pub.ro/​courses/​pa/​laboratoare/​shortest-paths-problem|Shortest-paths problem]] pentru a vă familiariza cu contextulproblema șnotațiile folosite.
  
-Dacă asociem o matrice de costuri, w, pentru ​muchiile din G, fiecare arbore de acoperire va avea asociat un cost egal cu suma costurilor muchiilor conţinute. Un arbore care are costul asociat mai mic sau egal cu costul oricărui alt arbore ​de acoperire se numeşte arbore minim de acoperire (minimum spanning treeal grafului GUn graf poate avea mai mulţi arbori minimi de acoperireDacă toate costurile muchiilor sunt diferite, există un singur AMA.  +Concepte necesare: 
-Primul ​algoritm ​pentru determinarea unui arbore minim de acoperire a fost scris în 1926 de Otakar Boruvka. În prezentcei mai folosiţi algoritmi sunt Prim şi KruskalToţtrei sunt algoritmi ​greedy, şi rulează în timp polinomial. La fiecare paspentru a construi arborele se alege cea mai bună variantă posibilă la momentul respectivGeneric, algoritmul de determinare a unui AMA se poate scrie astfel:+ 
 +  * **cost muchie** / **edge cost** 
 +  * **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 ===== 
 + 
 +În acest laborator vom studia **all-pairs shortest-paths problem**. Pentru această problemă, vom prezenta 2 algoritmi:​ 
 + 
 +  * **Roy-Floyd**:​ eficient ​pentru ​grafuri **dense** ($m >> n$ sau $|E| >> |V|$ - a.k.a. număde muchii mult mai decât număr ​de noduri). 
 +  * **Johnson**:​ eficient pentru grafuri **rare** ($n >> m$ sau $|V| >> |E|$ - a.k.anumă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țconsulta capitolul **All-Pairs Shortest Paths** din **Introduction to Algorithms** [0] pentru mai multe detalii despre acești ​algoritmi
 + 
 +===== Roy-Floyd ===== 
 + 
 +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 = (VE)** care sunt **dense**. 
 + 
 +==== Roy-Floyd - Pseudocod ====
  
 <code cpp> <code cpp>
-ArboreMinimDeAcoperire(G(VE), c+// apply Roy-Floyd'​s algorithm for all-pairs shortest-paths problem 
- MuchiiAMA ​; +// 
- while ​(MuchiiAMA nu reprezintă muchiile unui arbore minim de acoperire+// nodes     = list of all nodes from G 
- găseşte o muchie ​(uvcare este sigură pentru MuchiiAMA+// adj[node] = the adjacency list of node 
- MuchiiAMA ​MuchiiAMA ∪ {(uv)}+//             ​example:​ adj[node] = {..., neigh, ...} => edge (node, neigh) of cost w[node][neigh] 
- return MuchiiAMA;+// 
 +// 
 +// returns: d, p 
 +//          d = distance matrix 
 +//          p = parent matrix 
 +// 
 +Roy-Floyd(G=(nodesadj)) { 
 +  // STEP 1: initialize results 
 +  // d[i][j] = minimum distance from i to j 
 +  // p[i][j] = parent of node jon 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, 
 +  // try to update shortest path from i to j. 
 +  for (k in nodes
 +    for (i 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; 
 +
 + 
 +// Usage example: 
 +d, p = Roy-Floyd(G=(nodesadj)); 
 +// 1. Use distances from d 
 +// 2. Rebuild path from node to source using parents (p) 
 +RebuildPath(source,​ destination,​ p);
 </​code>​ </​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| \\
 +|1|0|-1|-2|0| \\
 +|2|4|0|2|4| \\
 +|3|5|1|0|2| \\
 +|4|3|-1|1|0| \\
 +
 +
 +<spoiler Explicație pas cu pas> În exemplul atașat, avem un graf **orientat** cu următoare configurație:​
 +
 +  * ''​%%n = 4%%'',​ ''​%%m = 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.
 +  * 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| \\
 +|1|0|+∞|-2|+∞| \\
 +|2|4|0|3|+∞| \\
 +|3|+∞|+∞|0|2| \\
 +|4|+∞|-1|+∞|0| \\
 +
 +
 +  * Rulăm algoritmul Roy-Floyd, vom scrie matricea pentru fiecare $ k = 1, 2, 3, 4$.
 +    * $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]** și **d[4][1]**)
 +
 +|-|1|2|3|4| \\
 +|1|0|+∞|-2|+∞| \\
 +|2|4|0|2|+∞| \\
 +|3|+∞|+∞|0|2| \\
 +|4|**3**|-1|**1**|0| v
 +
 +  * $d_3$ (matricea dupa primul pas din algoritm; se modifică doar **d[1][4]** și **d[2][4]**)
 +
 +|-|1|2|3|4| \\
 +|1|0|+∞|-2|**0**| \\
 +|2|4|0|2|**4**| \\
 +|3|+∞|+∞|0|2| \\
 +|4|3|-1|1|0| \\
 +
 +
 +  * $d_4$ (matricea dupa primul pas din algoritm; se modifică doar **d[1][2]**,​ **d[3][1]** și **d[3][2]**)
 +
 +|-|1|2|3|4| \\
 +|1|0|**-1**|-2|0| \\
 +|2|4|0|2|4| \\
 +|3|**5**|**1**|0|2| \\
 +|4|3|-1|1|0| \\
 +
 +
 +  * Drumurile minime sunt finale (cele menționate anterior - $d_4$).
 +
 +</​spoiler>​ \\
 +
 +
 +==== Complexitate ====
 +
 +  * **complexitate temporală**:​ $T = O(n^3)\ sau\ O(|E|^3)$
 +  * **complexitate spațială** : $S = O(1)$
 +
 +<spoiler Detalii (analiză + optimizări)>​
 +
 +  * **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ă.
 +  * **complexitate spațială** : Nu stocăm tablouri auxilare.
 +
 +</​spoiler>​ \\
 +
 +
 +===== Johnson =====
 +
 +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**.
  
-O muchie sigură este o muchie ​care se poate adăuga unei submulţimi ​de muchii ale unui arbore minim de acoperireastfel încât noua mulţime obţinută să aparţină tot unui arbore minim de acoperire. IniţialMuchiiAMA este o mulţime vidă. La fiecare passe adaugă câte o muchie sigură, deci MuchiiAMA rămâne o submulţime a unui AMAÎn consecinţă, la sfarşitul rulării algoritmului (când muchiile din mulţime unesc toate nodurile din graf), MuchiiAMA va conţine de fapt arborele minim de acoperire dorit. +Ideea de la care pornește acest algoritm este de a rula cel mai rapid algorithm pentru **shortest-paths single source**adică algoritmul lui Dijkstrapentru fiecare sursă (nod) din grafDacă toate costurile sunt pozitiveputem face direct acest lucru. Dacă însă există costuri negative, nu putem aplica Dijkstra pe acest grafAlgoritmul 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**.
-  +
-==== Algoritmul Kruskal ====+
  
-Algoritmul a fost dezvoltat în 1956 de Joseph Kruskal. Determinarea arborelui minim de acoperire ​se face prin reuniuni de subarbori minimi de acoperire. Iniţial, se consideră ​că fiecare nod din graf este un arbore. Apoila fiecare pas se selectează muchia de cost minim care uneşte doi subarbori disjuncţi, şi se realizează unirea celor doi subarbori. Muchia respectivă se adaugă la mulţimea MuchiiAMA, care la sfârşit va conţine chiar muchiile ​din arborele minim de acoperire+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.
  
-====Pseudocod ​=====+==== Johnson - Pseudocod ====
  
 <code cpp> <code cpp>
-Kruskal(G(VE)w) +// apply Johnson'​s algorithm for all-pairs shortest-paths problem 
-MuchiiAMA <- ∅; +// 
-for each v in V do +// nodes     = list of all nodes from G 
- MakeSet(v); //fiecare nod e un arbore diferit +// adj[node] = the adjacency list of node 
-sort(E); //sortează muchiile în ordine crescătoare a costului +//             ​example:​ adj[node] = {..., neigh, ...} => edge (nodeneighof cost w[node][neigh] 
-for each (u,vin E do +// 
-if (FindSet(u) !FindSet(v)) then //capetele muchiei fac parte //din subarbori disjuncţi +// returns: has_cycle, d, p 
- MuchiiAMA ​MuchiiAMA ∪ {(u, v)}; //adaugă muchia la arbore +//          has_cycle = negative cycle detection flag (true if found) 
-Union(u, v);    //uneşte subarborii corespunzători lui u şi v +//          d = distance matrix ​(defined only if has_cycle == false) 
-return ​MuchiiAMA+//          p = parent matrix ​(defined only if has_cycle == false
-</​code>​+// 
 +Johnson(G=(nodes, adj)) 
 +  ​// STEP 1: Compute adjustment distances h (using Bellmand-Ford). 
 +  ​has_cycle,​ h ComputerH(G); 
 +  ​if ​(has_cycle{ 
 +    return ​true, null, null
 +  }
  
-Bucla principală for poate fi înlocuită cu o buclă whileîn care se verifică dacă în MuchiiAMA există mai puţin de |V| 1 muchii, pentru că orice arbore de acoperire are |V| - 1 muchii, iar la fiecare pas se adaugă o muchie sigură. +  // STEP 2: Update all costs in G to obtain all costs nonnegative. 
- +  foreach ((uv) in edges) { 
 +    if (w[u][v] != infinity) { 
 +      w[u][v] = w[u][v] + (h[u] h[v]); 
 +    } 
 +  }
  
-===== Exemplu de rulare =====+  // STEP 3: Now all costs are nonnegative,​ so we can apply Dijsktra. 
 +  // Start Dijkstra for each source u, saving just distances. 
 +  foreach (u in nodes) { 
 +    d_dijkstra, p_dijkstra ​Dijkstra(u, G);
  
-Se consideră graful ​ din figura următoare:+    // STEP 4Compute distance (u, v) on initial graph. 
 +    foreach (v in nodes) { 
 +      d[u][v] = d_dijkstra[v] + (h[v] - h[u]); 
 +      p[u][v] = p_dijkstra[v];​ 
 +    } 
 +  }
  
-Fiecare subarbore va fi colorat diferit. Cum iniţial fiecare nod reprezintă un subarborenodurile au culori diferite. Pe măsură ce subarborii sunt uniţinodurile aparţinând aceluiaşi subarbore vor fi coloraţi identic. Costurile muchiilor sunt sortate în ordine crescătoare.+  return falsedp;  // no negative cycles detected 
 +}
  
-{{ pa:laboratoare:​92.jpg |}}+ComputeH(G=(nodes,​ adj)){ 
 +  // STEP 0Create 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; 
 +  ​}
  
-**Pas 1**+  // 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 
 +  }
  
-Se alege prima muchie(1,4). Se observă că uneşte subarborii {1} şi {4}, deci muchia e adăugată la MuchiiAMA, iar cei doi subarbori se unesc. +  return falsed; 
-{{ pa:​laboratoare:​93.jpg |}} +}
-MuchiiAMA = {(1,4)}.+
  
-**Pas 2** 
  
-Următoarea muchie este (7,8)care uneşte ​{7} şi {8}. Se adaugă la MuchiiAMA şi se unesc cei doi subarbori+// Usage example: 
-{{ pa:​laboratoare:​94.jpg |}}  +has_cycle, d, p = Johnson(sourceG=(nodesadj)); 
-MuchiiAMA ​{(1,4),(7,8)}.+if (has_cycle) ​{ 
 +  print "Has Cycle!"​ 
 +  STOP
 +} else { 
 +  // 1Use distances from d 
 +  // (e.g. d[node] ​distance from source to node) 
 +  // 
 +  // 3. Rebuild path from node to source using parents ​(p) 
 +  RebuildPath(sourcedestination,​ p)
 +} 
 +</​code>​ 
 +==== Exemple ====
  
-**Pas 3**+=== Exemplu Johnson ===
  
-Următoarea muchie este (5,6), care uneşte {5} şi {6}. Se adaugă la MuchiiAMA şi se unesc cei doi subarbori. +În această secțiune exemplificăm grafic cum rulează algoritmul lui Johnson pe un graf dat.
-{{ pa:​laboratoare:​95.jpg |}}  +
-MuchiiAMA = {(1,​4),​(7,​8),​(5,​6)}.+
  
-**Pas 4**+<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}}
  
-Următorul cost este 4. Se observă că muchiile (1,2) şi (2,4) au costul 4 şi unesc {2} cu {1,4}. Se adaugă la MuchiiAMA una dintre cele două muchii, fie ea (1,2), şi se unesc cei doi subarbori. Alegerea muchiei (2,4) va duce la găsirea unui alt AMA. [Am spus anterior că un graf poate avea mai mulţi arbori minimi de acoperire, ​cu acelaşi cost, dacă există muchii diferite cu acelaşi cost.] +În exemplul atașatavem un graf **orientat** ​cu următoare configurație:
-{{ pa:laboratoare:​96.jpg |}}  +
-MuchiiAMA = {(1,​4),​(7,​8),​(5,​6),​(1,​2)}.+
  
-**Pas 5**+  ​''​%%n = 5%%'',​ ''​%%m = 8%%''​ 
 +  ​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 negativ, deci 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 0. Obținem graful din figura următoare:
  
-Următoarea muchie de cost minim este (5,8), care uneşte ​{5,6} şi {7,8}Se adaugă la MuchiiAMA şi se unesc cei doi subarbori, rezultând {5,6,7,8}. +{{https://ocw.cs.pub.ro/​courses/​_media/​pa/​new_pa/​lab10-graph-johnson-example02.png?512Exemplu Johnson - 2/3}}
-{{ pa:​laboratoare:​97.jpg |}}  +
-MuchiiAMA = {(1,​4),​(7,​8),​(5,​6),​(1,​2),​ (5,8)}.+
  
-**Pas 6**+  ​Pe acest graf putem rula o dată algoritmul Bellman-Ford,​ considerând sursă noul nod adăugat. 
 +  ​Obținem vectorul de distanțe $h[node] = distanța\ de\ la\ nodul\ fictiv\ (6)\ la\ nodul\ node$
  
-Muchia (5,7), care are cel mai mic cost actual, are ambele extremităţi în subarborele {5,6,7,8}. În consecinţă,​ nu se efectuează nicio schimbare.+|node|1|2|3|4|5|6| \\ 
 +|h[node]|-1|-7|-4|0|-2|0| \\
  
-MuchiiAMA = {(1,​4),​(7,​8),​(5,​6),​(1,​2),​(5,​8)}. 
  
-**Pas 7**+  ​''​%%STEP 2%%'':​ Revenim la graful inițial (cel cu 5 noduri, 8 muchii) și îi alterăm costurile:​ 
 +    ​$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ă:
  
-Următorul cost este 7. Se observă că muchiile (1,6) şi (4,5) au costul 7 şi unesc subarborii ​{1,2,4} şi {5,6,7,8}Se adaugă la MuchiiAMA (1,6), şi se unesc cei doi subarboriAlegerea muchiei (4,5) va duce la găsirea unui alt AMA. +{{https://ocw.cs.pub.ro/​courses/​_media/​pa/​new_pa/​lab10-graph-johnson-example03.png?512Exemplu Johnson - 2/3}}
-{{ pa:​laboratoare:​98.jpg |}}  +
-MuchiiAMA = {(1,​4),​(7,​8),​(5,​6),​(1,​2),​(5,​8),​(1,​6)}.+
  
-**Pas 8**+  ​''​%%STEP 3%%'':​ Deoarece toate costurile sunt pozitive, putem rula Dijkstra pe rând, pentru fiecare sursă ''​%%source = 1, 2, 3, 4, 5%%''​ din graf. 
 +  ​''​%%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$
  
-Muchia (4,6) de cost 8 are capetele în acelaşi subarbore, deci nu se produc schimări.+|node|1|2|3|4|5| \\ 
 +|d_dijkstra[node]|0|1|1|1|1| \\
  
-MuchiiAMA = {(1,​4),​(7,​8),​(5,​6),​(1,​2),​(5,​8),​(1,​6)}. 
  
-**Pas 9**+|node|1|2|3|4|5| \\ 
 +|d[1][node]|0|-5|-2|2|0| \\
  
-Muchia (1,3) de cost 9 uneşte cei doi subarbori rămaşi, {1,​2,​4,​5,​6,​7,​8} şi {3}. Deci după unire obţinem un singur arbore. (1,3) se adaugă la MuchiiAMA, care va conţine acum 7 muchii, iar algoritmul se opreşte. ​ 
-{{ pa:​laboratoare:​91.jpg |}}  
-Arborele minim de acoperire obţinut este {(1,​4),​(7,​8),​(5,​6),​(1,​2),​(5,​8),​(1,​6),​ (1,​3)}.Costul său se calculează însumând costurile tuturor muchiilor: 
  
-Cost(MuchiiAMA) = 1 + 2 + 3 + 4 + 5 + 31 +  * ''​%%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$
  
-Alţi arbori minimi de acoperire pentru exemplul propus sunt: +|node|1|2|3|4|5| \\ 
-* {(1,​4),​(7,​8),​(5,​6),​(1,​2),​(5,​8),​(4,​5),​ (1,3)} +|d_dijkstra[node]|0|0|0|1|1| \\
-* {(1,4),(7,8),(5,​6),​(2,​4),​(5,​8),​(1,​6),​ (1,3)} +
-* {(1,​4),​(7,​8),​(5,​6),​(2,​4),​(5,​8),​(4,​5),​ (1,3)}.+
  
-Pentru alte exemple explicate consultaţi [2], [3] şi [5]. 
  
-===== Complexitate =====+|node|1|2|3|4|5| \\ 
 +|d[2][node]|6|0|3|8|6| \\
  
-Timpul de execuţie depinde de implementarea structurilor de date pentru mulţimi disjuncte. Vom presupune că se foloseşte o pădure cu mulţimi disjuncte[cor]. Iniţializarea se face într-un timp O(|V|). Sortarea muchiilor în funcţie de cost se face în O(|E|log|E|). În bucla principală se execută |E| operaţii care presupun două operaţii de găsire a subarborilor din care fac parte extremităţile muchiilor şi eventual o reuniune a acestor arbori, într-un timp O(|E|log|E|). ​ 
  
-Deci complexitatea totală ​este: O(|V|) + O(|E|log|E|) + O(|E|log|E|) = O(|E|log|E|).+  * ''​%%STEP 3%%'':​ $ source = 3 $ 
 +    * Vectorul de distanțe ​este ''​%%d_dijkstra%%''​ atașat mai jos. 
 +    * 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$ 
 +      * $d[3][2] = d_{dijkstra}[2] ​+ (h[2] - h[3]) = 1 + [ (-7- (-4) ] = -2$ 
 +      * $d[3][3] = 0$ 
 +      * $d[3][4] = d_{dijkstra}[4] + (h[4] - h[3]) = 1 + [ (0) - (-4) ] = 5$ 
 +      * $d[3][5] = d_{dijkstra}[5] + (h[5] - h[3]) = 1 + [ (-2) - (-4) ] = 3$
  
-Cum |E|<= |V|2, şi log(|V|2) = 2log(|V|) = log(|V|), rezultă o complexitate O(|E|log|V|).+|node|1|2|3|4|5\\ 
 +|d_dijkstra[node]|1|1|0|1|1\\
  
-=== Algoritmul Prim === 
  
-Algoritmul a fost prima oară dezvoltat în 1930 de matematicianul ceh Vojtěch Jarnik, şi independent în 1957 de informaticianul Robert Prim, al cărui nume l-a luat. Algoritmul consideră iniţial că fiecare nod este un subarbore independent,​ ca şi Kruskal. Însă spre deosebire de acesta, nu se construiesc mai mulţi subarbori care se unesc şi în final ajung să formeze AMA, ci există un arbore principal, iar la fiecare pas se adaugă acestuia muchia cu cel mai mic cost care uneşte un nod din arbore cu un nod din afara sa. Nodul rădăcină al arborelui principal se alege arbitrar. Când s-au adăugat muchii care ajung în toate nodurile grafului, s-a obţinut AMA dorit. Abordarea seamănă cu algoritmul Dijkstra de găsire a drumului minim între două noduri ale unui graf.+|node|1|2|3|4|5| \\ 
 +|d[3][node]|4|-2|0|5|3| \\
  
-Pentru o implementare eficientă, următoarea muchie de adăugat la arbore trebuie să fie uşor de selectat. Vârfurile care nu sunt în arbore trebuie sortate în funcţie de distanţa până la acesta (de fapt costul minim al unei muchii care leagă nodul dat de un nod din interiorul arborelui). Se poate folosi pentru aceasta o structură de heap. Presupunând că (u, v) este muchia de cost minim care uneşte nodul u cu un nod v din arbore, se vor reţine două informaţii:​ 
  
-* d[u] = w[u,vdistanţa de la u la arbore +  ​''​%%STEP 3%%'':​ $ source = 4 $ 
-p[u] = v predecesorul lui u în drumul minim de la arbore la u.+    * Vectorul de distanțe este ''​%%d_dijkstra%%''​ atașat mai jos. 
 +    * Distanțele față de nodul ''​%%4%%''​ pe graful inițial sunt: 
 +      * $d[4][1] = d_{dijkstra}[1+ (h[1] - h[4]) = 0 + [ (-1) - (0) ] = -1$ 
 +      $d[4][2] = d_{dijkstra}[2] + (h[2] - h[4]) = 0 + [ (-7) - (0) ] = -7$ 
 +      * $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$
  
-La fiecare pas se va selecta nodul u cel mai apropiat de arborele principal, reunind apoi arborele principal cu subarborele corespunzător nodului selectat. Se verifică apoi dacă există noduri mai apropiate de u decât de nodurile care erau anterior în arbore, caz în care trebuie modificate distanţele dar şi predecesorul. Modificarea unei distanţe impune şi refacerea structurii de heap.+|node|1|2|3|4|5| \\ 
 +|d_dijkstra[node]|0|0|0|0|0| \\
  
-===== Pseudocod ===== 
  
-<code cpp> +|node|1|2|3|4|5| \\ 
-Prim(G(V,​E),​ w, root) +|d[4][node]|-1|-7|-4|0|2| \\ 
-1. MuchiiAMA <- ∅; + 
-2. for each u in V do + 
-3. d[u] = INF;​ //​iniţial ​distanţele sunt infinit +  * ''​%%STEP 3%%'':​ $ source ​5 $ 
-4. p[u] = NIL; //şi nu există predecesori +    * Vectorul de distanțe este ''​%%d_dijkstra%%''​ atașat mai jos. 
-5. d[root] = 0;​ //​distanţa de la rădăcină la arbore e 0 +    * Distanțele față de nodul ''​%%4%%''​ pe graful inițial ​sunt: 
-6. H = Heap(V,d); //se construieşte heap-ul +      * $d[5][1] = d_{dijkstra}[1+ (h[1] - h[5]) 2 + [ (-1) - (-23$ 
-7. while ​(H not emptydo //cât timp mai sunt noduri neadăugate +      * $d[5][2] ​d_{dijkstra}[2] + (h[2] - h[5]) = 2 + [ (-7- (-2) ] = -3$ 
-8. u GetMin(H);​ //​se selectează cel mai apropiat nod u +      * $d[5][3= d_{dijkstra}[4+ (h[3] - h[5]) = 1 + [ (-4) - (-2) ] = -1$ 
-9. MuchiiAMA ​MuchiiAMA ∪ {(u, p[u])};//se adaugă muchia care uneşte u cu un nod din arborele principal  +      * $d[5][4] = d_{dijkstra}[5+ (h[4- h[5]2 + [ (0- (-2) ] = 4$ 
-10. for each v in Adj(udo +      * $d[5][5] ​0$ 
- //pentru toate nodurile adiacente lui u se verifică dacă + 
- //trebuie făcute modificări +|node|1|2|3|4|5| ​\
-11. if w[u][v< d[vthen +|d_dijkstra[node]|2|2|1|2|0| \\ 
-12. d[v] = w[u][v]+ 
-13. p[v] = u; + 
-14. Heapify(v, H);​ //​refacerea structurii de heap +|node|1|2|3|4|5| \\ 
-15. MuchiiAMA ​MuchiiAMA ​{(root, p[root])}; +|d[5][node]|3|-3|-1|4|0| \\ 
-16. return MuchiiAMA; + 
-</​code> ​+ 
 +  * STOP! Am obținut toate distanțele $d[u][v]$ cerute!
  
-===== Exemplu de rulare =====+</​spoiler>​ \\
  
-Se consideră graful folosit pentru exemplificarea algoritmului Kruskal. 
  
-Iniţial fiecare nod reprezintă un arbore independent,​ şi are o culoare unică. Se alege ca rădăcină a arborelui principal nodul 1. +==== Complexitate ====
  
-{{ pa:laboratoare:​99.jpg ​|}}  +  * **complexitate temporală**$T = O(n * m * log (n))\ sau\ O(|V| * |E| * log (|V|))$ 
-**Pas 1**+  * **complexitate spațială** : $S = O(n + m)\ sau \ O(|V| + |E|)$
  
-Se alege muchia de cost minim care uneşte rădăcina cu un alt nod: (1,4) de cost 1 si se adaugă arborelui principal. +<spoiler Detalii (analiză + optimizări)>
-{{ pa:​laboratoare:​910.jpg |}}  +
-MuchiiAMA = {(1,4)}.+
  
-**Pas 2**+  ​* **complexitate temporală**:​ 
 +    * **ComputeH**:​ 
 +      * Construire graf nou - $O(n + m))$. 
 +      * Aplicare Bellman-Ford pe noul graf - $O(n * m)$. 
 +    * **Update edges** - pasul se face în $O(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)$.
  
-Se alege muchia de cost minim care uneşte 1 sau 4 cu un alt nod. (1,2) şi (2,4) au ambele costul 4. Se alege una dintre ele, fie ea (1,2). +</​spoiler>​ \\
-{{ pa:​laboratoare:​911.jpg |}}  +
-MuchiiAMA = {(1,​4),​(1,​2)}.+
  
-**Pas 3** 
  
-Se alege următoarea muchie care uneşte {1,4,2} cu alt nod. (1,6) şi (4,5) au acelaşi cost, dar adaugă arborelui noduri diferite. La acest pas se selectează de exemplu (1,6). Selectând la acest pas (4,5) s-ar obţine un alt arbore de acoperire. +===== TLDR =====
-{{ pa:​laboratoare:​912.jpg |}}  +
-MuchiiAMA ​{(1,​4),​(1,​2),​(1,​6)}.+
  
-**Pas 4**+  ​Pentru cazul **shortest-path single source** am studiat în laboratorul anterior algoritmul lui Dijkstra / algoritmul Bellman-Ford. 
 +  * Pentru cazul **shortest-path all-pairs**,​ discuția se facă după numărul de muchii din graf: 
 +    * **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))$.
  
-Se caută muchia de cost minim care uneşte {1,4,2,6} cu un alt nod, şi se găseşte (6,5) de cost 3. Nodul 5 va fi adăugat arborelui principal. +===== Exerciții =====
-{{ pa:​laboratoare:​913.jpg |}}  +
-MuchiiAMA ​{(1,​4),​(1,​2),​(1,​6),​(6,​5)}.+
  
-**Pas 5** 
  
-Se alege muchia de cost minim care uneşte {1,4,2,6,5} cu un alt nod. (5,8) de cost 5 se adaugă listei de muchii. +<​note>​
-{{ pa:​laboratoare:​914.jpg |}}  +
-MuchiiAMA = {(1,​4),​(1,​2),​(1,​6),​(5,​6),​(5,​8)}+
  
-**Pas 6**+Scheletul de laborator se găsește pe pagina [[https://​github.com/​acs-pa/​pa-lab/​tree/​main/​skel/​lab10|pa-lab::​skel/​lab10]].
  
-Se alege muchia de cost minim (8,7) care uneşte {1,​4,​2,​6,​5,​8} cu nodul 7. +</​note>​ 
-{{ pa:​laboratoare:​915.jpg |}}  +<note warning>
-MuchiiAMA = {(1,​4),​(1,​2),​(1,​6),​(5,​6),​(5,​8),​(8,​2)}.+
  
-**Pas 7** +Înainte ​de a rezolva exercițiileasiguraț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]].
-{{ pa:​laboratoare:​916.jpg |}}  +
-Se alege muchia (1,3) de cost 9care uneşte arborele principal cu ultimul nod ramas, şse adaugă mulţimii de muchiiCum toate nodurile sunt acoperite, am obţinut un arbore minim de acoperire.+
  
-MuchiiAMA = {(1,​4),​(1,​2),​(1,​6),​(5,​6),​(5,​8),​(8,​2),​(1,​3)}.+Prin citirea acestor precizări vă asigurați că:
  
-Cost(MuchiiAMA) = 1 + 4 + 7 + 3 + 5 + 2 + 9 = 31 +  * știți **convențiile** folosite 
 +  * evitați **buguri** 
 +  * evitați **depunctări** la lab/​teme/​test
  
-Alţi arbori minimi de acoperire pentru exemplul propus se pot obţine alegând diferit muchiile cu acelaşi cost (vezi paşii 2 şi 3) . 
  
-Pentru alte exemple explicate consultaţi [2], [4] şi [6].+</​note>​
  
-===== Complexitate =====+==== Roy-Floyd ​====
  
-Iniţializările se fac în O(|V|)Bucla principală while se execută de |V| ori. Procedura GetMin() ​are nevoie de un timp de ordinul O(lg|V|), deci toate apelurile vor dura O(|V|lg|V|). Bucla for este executată în total de O(|E|) ori, deoarece suma tuturor listelor de adiacenţă este 2|E|. Modificarea distanţei, a predecesorului,​ şi refacerea heapului se execută într-un timp de O(1), O(1) şi respectiv O(lg|V|). Deci în total bucla interioară for durează O(|E|lg|V|).+Se dă un graf **orientat** cu **n** noduriGraful ​are **costuri strict pozitive**.
  
-În consecinţă, timpul total de rulare este O(|V|lg|V|+|E|lg|V|),​ adică O(|E|lg|V|). Aceeaşi complexitate s-a obţinut şi pentru algoritmul Kruskal. Totuşitimpul de execuţie al algoritmului Prin se poate îmbunătăţi până la O(|E|+|V|lg|V|)folosind heap-uri Fibonacci.+Se dă matricea ponderilor ​**w**, se cere matricea drumurilor minime - **d**aplicând algoritmul **Roy-Floyd**.
  
-===== Concluzii ===== 
  
-Un arbore minim de acoperire al unui graf este un arbore care conţine toate nodurile, şi în plus acestea sunt conectate prin muchii care asigură un cost total minim. Determinarea unui arbore minim de acoperire pentru un graf este o problemă cu aplicaţii în foarte multe domenii: reţele, clustering, prelucrare de imagini. Cei mai cunoscuţi algoritmi, Prim şi Kruskal, rezolvă problema în timp polinomial. Performanţa algoritmilor depinde de modul de reprezentare a structurilor de date folosite. ​+<note warning>
  
-===== Referinţe =====+Restricții și precizări:
  
-[1] – [[http://en.wikipedia.org/​wiki/​Minimum_spanning_tree]]+  * $ 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 ș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). 
 +  * timp de execuție 
 +    * C++: 1s 
 +    * Java: 8s
  
-[2] – T. Cormen, C. Leiserson, R. Rivest, C. Stein – Introducere în Algoritmi, cap. 24 
  
-[3] – [[http://​en.wikipedia.org/​wiki/​Kruskal%27s_algorithm]]+</note>
  
-[4] – [[http://​en.wikipedia.org/​wiki/​Prim%27s_algorithm]]+==== Johnson ====
  
-[5] – [[http://w3.cs.upt.ro/​~calin/​resources/​sdaa/​kruskal.ppt]]+Se dă un graf **orientat** cu **n** noduriGraful are **costuri oarecare** (pot fi și negative).
  
-[6] – [[http://​www.cs.upt.ro/​~calin/​resources/​sdaa/​prim.ppt]]+Se dă lista de adiacență cu costurile aferente, se cere matricea drumurilor minime - **d**, aplicând algoritmul **Johnson**.
  
-[7] – [[http://​www.cs.princeton.edu/​~wayne/​kleinberg-tardos/​04mst.pdf]] 
  
-[8] – [[http://​hc.ims.u-tokyo.ac.jp/​JSBi/​journal/​GIW01/​GIW01F03.pdf]]+<note warning>
  
-[9] – [[http://​www4.ncsu.edu/​~zjorgen/​ictai06.pdf]]+Restricții și precizări:
  
-[10] – CGiumale – Introducere în Analiza Algoritmilorcap.5.5+  * $ n <= 1000 $ 
 +  * $ m <= 25000 $ 
 +  * $ -1000 <= 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 ș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 
 +    * C++: 1s 
 +    * Java: 8s
  
  
-====== Probleme ======+</​note>​
  
-==== 1. Problema 1 (10p) ====+==== BONUS ====
  
-Enunt nou:+La acest laborator, asistentul va alege 1-2 probleme din secțiunea extra.
  
-Enunt vechi: +==== Extra ====
-Datorita miscarilor politice la nivel inalt, Gigel, dictatorul Bitlandiei, doreste sa isi construiasca un <​del>​palat</​del>​ buncar antinuclear. Buncarul este format din N camere conectate prin coridoare. Coridoarele dintre camere sunt destul de scumpe de construit asa ca Gigel doreste sa construiasca cat mai putine si cat mai ieftine astfel incat tot sa aiba acces in toate camerele. El va da 2 planuri si voi trebuie sa folositi 2 algoritmi diferiti (Prim [4p] si Kruskal [4p]) pentru a determina care este cea mai buna alegere de coridoare.+
  
 +  * [[https://​infoarena.ro/​problema/​rfinv|infoarena/​rfinv]]
 +  * [[https://​infoarena.ro/​problema/​rf|infoarena/​rf]]
 +  * [[https://​infoarena.ro/​problema/​coach|infoarena/​coach]]
 +  * [[https://​codeforces.com/​contest/​295/​problem/​B|codeforces/​greg-and-graph]]
 +  * [[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 =====
  
 +[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.
  
pa/laboratoare/laborator-09.1398257512.txt.gz · Last modified: 2014/04/23 15:51 by traian.rebedea
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