Differences

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

Link to this comparison view

pa:laboratoare:laborator-07 [2017/04/09 10:53]
andrei_mario.dinu [Probleme]
pa:laboratoare:laborator-07 [2023/03/15 16:53] (current)
radu.nichita
Line 1: Line 1:
-====== Laborator 7: Aplicatii DFS ======+====== Laborator 07: Parcurgerea grafurilor. Aplicații (2/2) ====== 
  
 ===== Obiective laborator ===== ===== Obiective laborator =====
-Întelegerea noţiunilor teoretice: 
  
-  * tare conexitatecomponente tare conexe (pentru grafuri orientate) +  * Înțelegerea conceptelor de grafreprezentare șparcugere 
-  * punct de articulaţie (pentru grafuri neorientate) +  * Studierea unor aplicații ​pentru ​parcurgeri
-  * punţ(pentru grafuri neorientate) +
-  * componente biconexe (în general, ​pentru ​grafuri neorientate)+
  
-Înţelegerea algoritmilor ce rezolvă aceste probleme şi implementarea acestor algoritmi. ​ 
-===== Importanţă – aplicaţii practice ===== 
  
-  *Componentele biconexe au aplicaţii importante în reţelistică,​ deoarece o componentă biconexă asigură redundanţa. +===== Componente Conexe  ​===== 
-  *Descompunerea în componente tare conexe: data mining, compilatoare,​ calcul ştiinţific,​ 2SAT +>> O **componentă conexă** (**CC**) / **Connected Component** (**CC**) într-un graf **neorientat** este o submulțime maximală de noduri, cu proprietatea că oricare ar fi două noduri x și y din aceasta, există drum de la x la y.
-===== Notiuni teoretice ​=====+
  
-    ***Tare conexitate.** Un graf orientat este tare conex, dacă oricare ar fi două vârfuri u şi v, ele sunt tare conectate (strongly connected) ​există drum atât de la u la v, cât şi de la v la u.+<spoiler CC exemplu 01> 
 +$n = 6$   $m = 6$
  
-    *O componentă tare conexă este un subgraf maximal tare conex al unui graf orientatadică o submulţime de vârfuri U din Vastfel încât pentru orice u şi v din U ele sunt tare conectate. Dacă fiecare componentă tare conexă este redusă într-un singur nodse va obţine **un graf orientat aciclic**.+$muchii: { (1,2); (1,5); (2,5); (2,3); (3, 5); (4, 6);} $
  
-De exemplu: 
  
-{{ pa:laboratoare:7.1.png?400 |}} +{{pa:new_pa:lab08-cc-example01.png}}
-  +
-    ***Un punct de articulaţie (cut vertex)** este un nod al unui graf a cărui eliminare duce la creşterea numărului de componente conexe ale acelui graf.+
  
-    ​***O punte (bridge)** este o muchie a unui graf (se mai numeşte şi **muchie critică**) a cărei eliminare duce la creşterea numărului de componente conexe ale acelui graf.+Sunt **2 CC**-uri în graful dat: 
 +  ​{1, 2, 3, 5} 
 +  ​{4, 6}
  
-{{ pa:laboratoare:​7.2.png?400 |}}+Explicație: 
 +  * Cele 2 sunt mulțimi maximale pentru care se respectă proprietatea de conexitate. 
 +  * 4 și 6 nu sunt accesibile din nodurile 1, 2, 3 și 5, prin urmare, acestea trebuie să facă parte din componente diferite 
 +</​spoiler>​
  
-    ***Biconexitate**. Un graf biconex este un graf conex cu proprietatea că eliminând oricare nod al acestuia, graful rămâne conex. ​ 
  
-    *O componentă biconexă a unui graf este o mulţime ​**maximală** de noduri care respectă proprietatea de biconexitate. +\\ 
-  +>> Un graf **neorientat** ​este **conex** dacă conține ​**o singură** componentă conexă. 
-{{ pa:​laboratoare:​7.3.png?400 |}}+
  
-===== Componente tare conexe =====+<spoiler CC - exemplu 02> 
 +$n 6$   ​$m ​7$
  
-Vom porni de la definiţie pentru a afla componenta tare conexă din care face parte un nod v. Vom parcurge graful ​(DFS sau BFSpentru a găsi o mulţime de noduri S ce sunt accesibile din v. Vom parcurge apoi graful transpus ​(obţinut prin inversarea arcelor din graful iniţial), determinând o nouă mulţime de noduri T ce sunt accesibile din v în graful transpus. Intersecţia dintre S şi T va reprezenta componenta tare conexa. Graful iniţial şi cel transpus au aceleaşi componente conexe.+$muchii: {(1, 2)(1, 5); (25); (2, 3); (3, 5); (4, 6); (5, 4)} $
  
-==== Algoritmul lui Kosaraju ==== 
  
-Algoritmul foloseşte două DFS (una pe graful iniţial şi una pe graful transpus) şi o stivă pentru a reţine ordinea terminării parcurgerii nodurilor grafului original (evitând astfel o sortare a nodurilor după acest timp la terminarea parcurgerii).+{{pa:​new_pa:​lab08-cc-example02.png}}
  
-<code cpp> +Graful dat este conex există **CC**: {1, 23, 4, 5, 6}.
-ctc(G = (V, E)) +
- S <stiva vida +
- culoare[1..n] = alb +
- cat timp exista un nod v din V care nu e pe stiva +
- dfs(G, v) +
- culoare[1..n] = alb +
- cat timp S != stiva vida +
- v = pop(S) +
- dfsT(GTv) /* toate nodurile ce pot fi vizitate din v fac  parte din ctc; dupa vizitareaceastea sunt scoase din S si din G */+
  
-dfs(G, v) +Explicație:​ Se poate ajunge de la oricare nod la oricare altul. 
-        ​culoare[v] = gri +</spoiler>
-        pentru fiecare (v, u) din E +
-        daca culoare[u] == alb +
-                dfs(u) +
-        push(S, v) // nodul este terminat de expandat, este pus pe stiva +
-        culoare[v] = negru+
  
-dfsT(G, v) – similar ​cu dfs(G, v)fara stivadar cu retinerea solutiei +<​note>​ 
-</code>+O componentă conexă reprezintă o partiție a nodurilor în submulțimi! <=> Fiecare nod face parte dintr-o singură componentă conexă! 
 +</​note>​ 
 +==== Algoritmi ==== 
 +=== DFS === 
 +<​note>​ 
 +CC cu DFS: 
 +  * În algoritmul clasic de parcurgere DFSde fiecare dată când se găsește un nod fără părinte și se apeleză DFS_RECURSIVE,​ se descoperă o nouă componentă conexă. 
 +      * Toate nodurile vizitate în acel subarbore fac parte din aceeași componentă conexă. ​ 
 +</note>
  
-Complexitate: O(|V| + |E|) +== Complexitate ​==
-  +
-{{ pa:​laboratoare:​7.4.png?​400 |}}+
  
-==== Algoritmul lui Tarjan ====+$T O(n + m)$
  
-Algoritmul foloseşte o singură parcurgere ​DFS şi o stivăIdeea de bază a algoritmului este că o parcurgere ​în adâncime porneşte dintr-un nod de start. Componentele tare conexe formează subarborii arborelui de căutare, rădăcinile cărora sunt de asemenea rădăcini pentru componentele tare conexe.+=== BFS === 
 +<​note>​ 
 +CC cu BFS: 
 +  * Se parcurge lista de noduri. 
 +      * Pentru fiecare nod care nu are părinte, se pornește o nouă parcurgere ​BFS din nodul curent. 
 +      * Toate nodurile vizitate într-o parcurgere ​BFS fac parte din aceeași componentă conexă
 +  * Observație:​ Se păstreză lista de părinți ​de la o parcurgere la alta. 
 +</​note>​
  
-Nodurile sunt puse pe o stivă, în ordinea vizitării. Când parcurgerea termină de vizitat un subarbore, nodurile sunt scoase din stivă şi se determină pentru fiecare nod dacă este rădăcina unei component tare conexe. Dacă un nod este rădăcina unei componente, atunci el şi toate nodurile scoase din stivă înaintea lui formează acea componenta tare conexă.+== Complexitate ==
  
-Pentru a determina daca un nod este radacina unei componente conexe, calculam pentru fiecare nod:+$T = O(n + m)$
  
-<code cpp+<note warning
-idx[v] ​    -> nivelul / ordinea de vizitare +Deși ambele abordări au aceeași complexitate,​ recomandăm abordarea cu DFS pentru simplitate. 
-lowlink[v] -> min { idx[u] | u este accesibil din v } +</note>
-</code>+
  
-**v este rădăcina unei componente tare conexe <=> lowlink[v] ​idx[v].**+===== Componente Tare Conexe =====
  
-<code cpp> +>> O **componentă tare conexă** ​(**CTC**/ **Strongly Connected Component** ​(**SCC**într-un graf **orientat** este o submulțime maximală de noduricu proprietatea că oricare ar fi două noduri x și y din aceasta, există drum de la x la y.
-ctc_tarjan(G = (V, E))  +
-        index = 0 +
-        S = stiva vida +
-        pentru fiecare v din V +
-                daca (idx[v] nu e definit// nu a fost vizitat +
-                        tarjan(Gv)+
  
-tarjan(G, v)      ​ +<spoiler SCC - exemplu 01> 
-        ​idx[v] ​index +$n 6$   ​$m ​6$
-        lowlink[v] ​index  +
-        index = index + 1  +
-        push(S, v)  +
-         +
-        pentru (v, u) din E  +
-                daca (idx[u] nu e definit)  +
-                        tarjan(G, u)  +
-                        lowlink[v] = min(lowlink[v],​ lowlink[u])  +
-                altfel +
-                        daca (u e in S)  +
-                                lowlink[v] = min(lowlink[v],​ idx[u])  +
-         +
-        daca (lowlink[v] == idx[v])  +
-                // este v radacina unei CTC?  +
-                print "O noua CTC: "  +
-                repeat  +
-                        u = pop(S)  +
-                        print u  +
-                until (u == v) +
-</​code>​+
  
-ComplexitateO(|V| + |E|)+$arce{(1, 2); (1, 5); (5, 2); (2, 3); (3, 5); (4, 6)} $
  
-{{ pa:laboratoare:7.5.png?400 |}} +{{pa:new_pa:lab08-scc-example01.png}}
-  +
-===== Puncte de articulatie =====+
  
-Pentru determinarea punctelor de articulaţie într-un graf neorientat se foloseşte o parcurgere ​în adâncime modificată,​ reţinându-se informaţii suplimentare pentru fiecare nod. Acest algoritm a fost identificat tot de către Tarjan si este foarte similar cu algoritmul pentru determinarea CTC în grafuri orientate prezentat anterior. +Sunt **4 SCC**-uri în graful dat: 
-Fie T un arbore de adâncime descoperit de parcurgerea grafului. Atunciun nod v este punct de articulaţie dacă: ​+  * {1} 
 +  * {23, 5} 
 +  * {4} 
 +  * {6}
  
-  ​*v este rădăcina lui T şv are doi sau mai mulţcopii +Explicație:​ 
 +  ​În nodul 1 nu se poate ajunge, prin urmare acesta formează o componentă separată. Analog pentru 4. 
 +  * Similar, din nodul 6 nu se poate ajunge în alt nod, deci șacesta formeză singur o componentă. 
 +  * Nodurile 2, 3 ș5 formeză un ciclu, prin urmare se poate ajunge de la oricare la oricare. 
 +</​spoiler>​
  
-sau  
  
-  ​*v nu este rădăcina lui T şi are un copil u în T, astfel încât nici un nod din subarborele dominat de u nu este conectat cu un strămoş al lui v printr-o muchie înapoi (copii lui nu pot ajunge pe altă cale pe un nivel superior în arborele de adâncime)+\\ 
 +>> Un graf **orientat** este **tare conex** dacă conține o **singură componentă** tare conexă.
  
-Găsirea punctelor care se încadrează în primul caz este uşor de realizat.+<spoiler SCC - exemplu 02> 
 +$n = 6$   $m = 6$
  
-Notăm+$arce: {(1, 2); (1, 5); (5, 2); (2, 3); (3, 5); (4, 6); (4, 1); (5, 4); (6, 5)} $ 
-<code cpp+ 
-idx[v] = timpul de descoperire ​nodului u  +{{pa:​new_pa:​lab08-scc-example02.png}} 
-low[u] ​min( {idx[u]} U { idx[v] (u, v) este o muchie înapoi } U  + 
-        { low[vi] : vi copil al lui în arborele ​de adâncime} ​) +Graful este tare conex - există **1 SCC**{1, 2, 3, 4, 5, 6}; 
-</​code>​+ 
 +Explicație:​ Se poate vedea că pentru fiecare nod x se poate ajunge în oricare alt nod y. 
 +</spoiler
 + 
 +<​note>​ 
 +O componentă tare conexă reprezintă o partiție ​nodurilor în submulțimi! <=> Fiecare nod face parte dintr-o singură componentă tare conexă! 
 +</​note>​ 
 +==== Algoritmi ==== 
 +=== TARJAN SCC === 
 + 
 +[[https://​en.wikipedia.org/​wiki/​Tarjan%27s_strongly_connected_components_algorithm|Algoritmul ​lui Tarjan pentru SCC]] foloseşte o singură parcurgere DFS în urma căreia rezultă o pădure ​de arbori DFS. Componentele tare conexe vor fi subarbori în această pădure. Rădăcinile acestor subarbori se vor numi **rădăcinile componentelor tare conexe** (**SCC roots**).
  
-** v este punct de articulaţie <=> low[u] ≥ idx[v]pentru orice copil u al lui v în T**+Nodurile sunt puse pe o stivă, în ordinea vizitării. Când parcurgerea termină de vizitat un subarbore, se determină dacă rădăcina arborelui care s-a terminat de vizitat este și rădăcina unui SCC. Dacă un nod este rădăcina unei componente, atunci el şi toate de deasupra sa din stivă formează acea componentă tare conexă.
  
 +Pentru a determina dacă un nod este rădăcina unei componente tare conexe, se definesc:
 <code cpp> <code cpp>
-puncte_articulatie(G = (V, E)+// the timestamp when node was found (when started to visit its subtree
-        ​timp ​+found[node] ​start[node];
-        pentru fiecare v din V +
-        daca (idx[vnu e definit) +
-                dfsCV(G, v)+
  
-dfsCV(G, v) +// the minimum accessible timestamp that node can see/access 
-        idx[v] = timp +low_link[node] =  min { found[x] | x is node OR x in ancestors(nodeOR x in descendants(node};
-        low[v] = timp +
-        timp = timp + 1 +
-        copii = { } // multime vida +
-        pentru fiecare (v, u) din E +
-                daca (idx[u] nu e definit) +
-                        ​// inseamna ca nodul u este nedescoperit,​ deci alb +
-                        copii = copii U {u} +
-                        dfsCV(G, u) +
-                        low[v] = min(low[v], low[u]) +
-                altfel +
-                        // inseamna ca nodul u este descoperit, deci gri, iar muchia v->u este muchie inapoi +
-                        low[v] = min(low[v], idx[u]) +
-                         +
-        daca v radacina arborelui +
-                daca |copii| >= 2 +
-                        v este punct de articulatie +
-        altfel +
-                daca (∃u ∈ copiiastfel incat (low[u] >= idx[v]) +
-                        v este punct de articulatie+
 </​code>​ </​code>​
  
-ComplexitateO(|V| + |E|)+>> **Tarjan SCC****node** is root for a SCC if **low_link[node] == found[node]**.
  
-{{ pa:​laboratoare:​7.6.png?400 |}}  +<spoiler Explicații found+low_link>​ 
-===== Punti =====+**found[node]** reprezintă timpul de **start** din DFS, definit în laboratorul anteriorÎn implementare reținem o variabilă **timestamp** care se incrementează de fiecare dată când se vizitează un nod. Noua valoare a lui **timestamp** este **found[node]** (momentul la care **node** a fost găsit).
  
-Pentru a determina muchiile critice se foloseşte tot o parcurgere în adâncime modificată, pornind ​de la următoarea observaţie: ​**muchiile critice sunt muchiile ​care nu apar în niciun ciclu.** Prin urmareo muchie ​de întoarcere nu poate fi critică, deoarece o astfel de muchie închide întotdeauna ​un cicluTrebuie să verificăm pentru muchiile de avansare (în număr de |V| 1) dacă fac parte dintr-un ciclu. Să considerăcă dorim să verificăm muchia ​de avansare ​(vu).+**low_link[node]** reprezintă cel mai mic timp de descoperire al unui nod **x** la care se poate ajunge pornind din **node** și mergând pe arcele/​muchii nevizitate (se poate coborî sau urca). Practicnodul cu cel mai mic timp de descoperire care se poate atinge prin traversarea a 0 sau mai multe arce.  
 +   * observații prelimilare:​ 
 +    * nodurile vizitate înaintea lui **node** au valoare **found** mai mică (deci și orice strămoș a lui **node** - mulțimea **ancestors(node)**) 
 +    * nodurile descendente ale lui **node** au valoare **found** mai mare (mulțimea **descendants(node)**) 
 +    * întrucât și **node** face parte din subarbore, inițializăm **low_link[node] = found[node]**. Valoarea finală va fi mai mică sau egală decât aceasta (conform definițieivom căuta ​un minim). 
 +    
 +   * după ce **toate** nodurile accesibile din **node** au fost vizitate, se cunoaște ***valoarea finală** a lui  **low_link[node]** și putem avea 2 cazuri: 
 +    * **low_link[node] == found[node]** 
 +       * dacă valoarea finală a rămăs cea inițială, înseamnă că **NU** s-a urcat în arbore (altfel am fi întâlnit valori mai mici decât cea inițială
 +       * prin urmare **node** este rădăcina unui SCC (primul nod întâlnit din acest SCC) 
 +       * nodurile din vârful stivei ​de deasupra lui **node** formează SCC-ul găsit 
 +    * **low_link[node] < found[node] ** 
 +       ​* ​dacă valoarea finală pentru **low_link[node]** este mai mică decât cea inițială, înseamnă că s-a urcat în arbore 
 +       * în acest caz există cel puțin o muchie **(y, x)** unde **x** este strămoș și **y** este descendent pentru **node**, prin care **y** (si implicit și **node**) își actualizează minimul cu valoarea din **x** 
 +       * drumul **x - ... - node -... y - ... - x ** este atunci un ciclu care face parte dintr-un SCC; **node** este un nod oarecare dintr-un astfel ​de ciclu ("la mijloc"​întrucât mai sus de el există cel puțin un nod mai aproape de "​începutul ciclului",​ adică nodul **x**) 
 +       * prin urmare, suntem siguri că **node** nu este rădăcina unui SCC  
 +</​spoiler>​
  
-Ne vom folosi de low[v] (definit la punctul anterior): dacă din nodul u putem să ajungem pe un nivel mai mic sau egal cu nivelul lui vatunci muchia nu este criticăîn caz contrar ea este critică.+== Algoritm == 
 +<code cpp | TARJAN_SCC>​ 
 +// Tarjan_SCC 
 +// * visit all nodes with DFS 
 +//      * compute found[nodeand low_link[node] 
 +//      * extract SCCs 
 +// 
 +// nodes     = list of all nodes from G 
 +// adj[node] = the adjacency list of node 
 +//             ​example:​ adj[node] = {..., neigh, ...} => edge (node, neigh) 
 +TARJAN_SCC(G = (nodes, adj)) { 
 +    // STEP 1initialize results 
 +    // parent[node] = parent of node in the DFS traversal 
 +    // 
 +    // the timestamp when node was found (when started to visit its subtree) 
 +    // Note: The global timestamp is incremented everytime a node is found. 
 +    // 
 +    // the minimum accessible timestamp that node can see/​access 
 +    // low_link[node] =  min { found[x] | x is node OR x in ancestors(node) OR x in descendants(node) }; 
 +    // 
 +    foreach (node in nodes) { 
 +        parent[node] = null; // parent not yet found 
 +        found[node] = +oo; // node not yet found 
 +        low_link[node] = +oo; // value not yet computed 
 +    } 
 +    nodes_stack = {}; // visiting order stack 
 + 
 +    // STEP 2: visit all nodes 
 +    timestamp = 0; // global timestamp 
 +    foreach (node in nodes) { 
 +        if (parent[node] == null) { // node not visited 
 +            parent[node] = node; // convention: the parent of the root is actually the root 
 + 
 +            // STEP 3: start a new DFS traversal this subtree 
 +            DFS(node, adj, parent, timestamp, found, low_link, nodes_stack);​ 
 +        } 
 +    } 
 +
 + 
 +DFS(node, adj, parent, ref timestamp, found, low_link, nodes_stack) { 
 +    // STEP 1: a new node is visited - increment the timestamp 
 +    found[node] = ++timestamp;​ // the timestamp when node was found 
 +    low_link[node] = found[node];​ // node only knows its timestamp 
 +    nodes_stack.push(node);​ // add node to the visiting stack 
 + 
 +    // STEP 2: visit each neighbour 
 +    foreach (neigh in adj[node]) { 
 +        // STEP 3: check if neigh is already visited 
 +        if (parent[neigh] != null) { 
 +            // STEP 3.1: update low_link[node] with information gained through neigh 
 +            // note: neigh is in the same SCC with node only if it'in the visiting stack; 
 +            // otherwiseneigh is from other SCCso it should be ignored 
 +            if (neigh in nodes_stack) { 
 +                low_link[node] = min(low_link[node],​ found[neigh]);​ 
 +            } 
 + 
 +            continue; 
 +        } 
 + 
 +        // STEP 4: save parent 
 +        parent[neigh] = node; 
 + 
 +        // STEP 5: recursively visit the child subtree 
 +        DFS(neigh, adj, parent, timestamp, found, low_link, nodes_stack);​ 
 + 
 +        // STEP 6: update low_link[node] with information gained through neigh 
 +        low_link[node] = min(low_link[node],​ low_link[neigh]);​ 
 +    } 
 + 
 +    // STEP 7: node is root in a SCC if low_link[node] == found[node] 
 +    // (there is no edge from a descendant to an ancestor) 
 +    if (low_link[node] == found[node]) { 
 +        // STEP 7.1: pop all elements above node from stack => extract the SCC where node is root 
 +        new_scc = {}; 
 +        do { 
 +            x = nodes_stack.pop();​ 
 +            new_scc.push(x);​ 
 +        } while (x != node); // stop when node was popped from the stack 
 + 
 +        // STEP 7.2: save / print the new SCC 
 +        print(new_scc);​ 
 +    } 
 +}
  
-<code cpp> 
-dfsB(G, v, parinte) 
- idx[v] = timp 
- low[v] = timp 
- timp = timp + 1 
- pentru fiecare (v, u) din E 
- daca u nu este parintele lui v 
- daca idx[u] nu este definit 
- dfsB(G, u, v) 
- low[v] = min(low[v], low[u]) 
- daca (low[u] > idx[v]) 
- (v, u) este muchie critica 
- altfel 
- low[v] = min(low[v], idx[u]) 
 </​code>​ </​code>​
  
-Complexitate:​ O(|V| |E|)+Observații:​ 
 +  * La pasul **3.1** se încearcă actualizarea lui **low_link[node]** cu informația din **neigh** doar dacă **neigh** este în stivă. 
 +    * Nodul **neigh** are deja părinte, deci poate fi în unul din următoare 2 cazuri: 
 +      * **neigh** este în curs de vizitare (deci este în stivă) => **neigh** este strămoș a lui **node** 
 +        * Reactualizăm **low_link[node]** cu valoarea din **neigh**. 
 +      * **neigh** este deja vizitat (deci a fost scos din stivă) => **neigh** face parte din alt subarbore, terminat anterior. 
 +        * Prin urmare, anterior s-a stabilit că **neigh** face parte dintr-un alt SCC și trebuie ignorat (întrucât sigur are valoare **found** mai mică decât a lui **node** și ar reactualiza **low_link[node]** în mod eronat.  
 +    * Se face această actualizare doar dacă **neigh** este strămoș al lui  
 +== Complexitate ​== 
 +  * **complexitate temporală **$T = O(m)$ 
 +  * **complexitate spațială ** : $S = O(n)$ 
 +    * recursivitate +  câteva structuri de date de lungime $O(n)$
  
-{{ pa:​laboratoare:​7.7.png?​400 |}} +=== Kosaraju ​=== 
-  +Există și alt algoritm pentru determinarea componentelor tare conexe. Algoritmul lui Kosaraju se bazează pe compactarea ciclurilor. Deoarece are aceeași complexitate ca și Tarjan, nu îl vom studia la laborator la PA. Am ales algoritmul lui Tarjan întrucât îl putem modifica ușor pentru a produce și alte rezultate.
-===== Componente biconexe =====+
  
-**O componenta biconexă (sau biconectată) este o componentă a grafului care nu conţine puncte de articulatie.** Astfel după eliminarea oricărui vârf din componenta curentă, restul vârfurilor vor rămâne conectate întrucât între oricare două vărfuri din aceeașcomponentă biconexă există cel puțin două cădisjuncte.+Puteți consulta următoarele materiale dacă dorițsă aflați mai multe:  
 +  * https://​www.youtube.com/​watch?​v=RpgcYiky7uw 
 +  * https://​iq.opengenus.org/​kosarajus-algorithm-for-strongly-connected-components/​ 
 +===== Puncte de articulație =====
  
-Astfel, pentru a determina componentele biconexe ale unui graf, vom adapta algoritmul ​de aflare a punctelor critice, reţinând şi o stivă cu toate muchiile de avansare şi de întoarcere parcurse până la un moment dat. La întâlnirea unui nod critic v se formează o nouă componentă biconexă pe care o vom determina extrăgând din stivă muchiile corespunzătoare. Nodul **v** este critic ​dacă am găsit un copil **u** din care nu se poate ajunge pe un nivel mai mic în arborele de adâncime pe un alt drum care foloseşte muchii de întoarcere ​**(low[u] >= idx[v])**. Atunci când găsim ​un astfel de nod **u**, toate muchiile aflate în stivă până la muchia ​(v, uinclusiv formează o nouă componentă biconexă.+>> **Punct ​de articulație**  / ​**nod critic** ​**Cut Vertex** (**CV**) este un nod într-un graf **neorientat** a cărui eliminare duce la creșterea numărului de componente conexe ​(CC- se elimină nodul împreună cu muchiile incidente.
  
-Nodul rădăcină trebuie tratat separat. Conform regulilor acestuia pentru a fi marcat nod critic (numărul de copii >= 2), este suficient să considerăcă fiecare copil face parte dintr-o componentă biconexă separată.+<spoiler CV - exemplu 01> 
 +$n 8$   $= 6$
  
-**Atenție! Împărțirea în componente biconexe a unui graf neorientat reprezintă o partiție disjunctă a muchiilor grafului ​(împreună cu vârfurile adiacente muchiilor corespunzătoare fiecărei componente în parte). Acest lucru implică că unele vârfuri pot face parte din mai multe componente biconexe diferite. Care sunt acestea?**+$muchii: {(1, 2); (2, 3); (3, 4); (4, 1); (1, 5); (5, 6); (6, 7); (7, 5); (7, 8)} $
  
-Complexitate:​ O(|V| + |E|) +{{pa:new_pa:lab08-cv-example01.png}}
-   +
-{{ pa:laboratoare:7.8.png?400 |}} +
-===== Concluzii =====+
  
-Algoritmul de parcurgere ​în adâncime poate fi modificat pentru calculul componentelor tare conexea punctelor de articulaţie,​ a punţilor şa componentelor biconexe. Complexitatea acestor algoritmi va fi cea a parcurgerii:​ O(|V| + |E|).+Sunt **3 CV**-uri ​în graful dat: 15 ș7.
  
-===== Referinte =====+Explicație:​ 
 +  * Dacă ștergem nodul 1, graful se sparge în 2 CC-uri: {2, 3, 4}, {5, 6, 7, 8}. 
 +  * Dacă ștergem nodul 5, graful se sparge în 2 CC-uri: {1, 2, 3, 4}, {6, 7, 8}. 
 +  * Dacă ștergem nodul 7, graful se sparge în 2 CC-uri: {1, 2, 3, 4, 5, 6}, {8}. 
 +  * Dacă ștergem oricare alt nod, ,graful rămâne conex. 
 +</​spoiler>​
  
-[1] Introducere in Algoritmi, Thomas H. Cormen, Charles E. Leiserson, Ronald L. – Capitolul 23 Algoritmi elementari pe grafuri ​[[http://net.pku.edu.cn/~course/cs101/​resource/​Intro2Algorithm/​book6/​chap23.htm]]+==== TARJAN CV ==== 
 +Putem modifica ușor algoritmul TARJAN SCC astfel încât să obținem ​[[https://en.wikipedia.org/wiki/Biconnected_component | Algoritmul lui Tarjan pentru CV]].
  
-[2] Wikipedia – Algoritmul lui Tarjan [[http://en.wikipedia.org/​wiki/​Tarjan%27s_strongly_connected_components_algorithm]]+În mod analog, pentru a determina dacă un nod este CV, se definesc și folosesc **found** și **low_link**.
  
-[3Wikipedia – Algoritmul lui Kosaraju ​[[http://​en.wikipedia.org/​wiki/​Kosaraju%27s_algorithm]]+>> **TARJAN CV**: **node** is **CV** if 
 +>>​**i)** node is NOT root and **low_link[neigh>= found[node]** for at least one **neigh** in **adj[node]** 
 +>> ​ OR 
 +>> **ii)** node is root and children(node) > 1
  
-[4Wikipedia - Componente biconexe ​[[http://en.wikipedia.org/​wiki/​Biconnected_component]]+Dacă **node** este rădăcină într-un subarbore, acesta are valoarea **found** mai mică decât a oricărui nod. Prin urmare, condiția **low_link[neigh>= found[node]** ar fi adevărată mereu și nu ne-ar putea furniza o informație utilăDe aceea, cazul **i)** nu este aplicabil pentru rădăcină. Putem trata foarte simplu cazul pentru rădăcină folosind **ii)**: dacă **node** este rădăcină a unui subarborele și are cel puțin 2 copii, atunci, prin eliminarea lui **node**, arborele acestuia se sparge într-un număr de subarbori egal cu numărul de copii.
  
-[5Infoarena - Arhiva educationala - Componente tari conexe [[http://​infoarena.ro/​problema/​ctc]]+<spoiler Explicații found+low_link>​ 
 +**found[node]** are aceeași semnificație ca la SCC.
  
-[6Infoarena ​Arhiva educationala ​Componente biconexe ​   [[http://infoarena.ro/​problema/​biconex]]+**low_link[node]** are aceeași semnificație ca la SCC.  
 +   * observații/​diferențe:​ 
 +    * Nu este nevoie de folosirea stivei de vizitare. 
 +      * La SCC aveam nevoie de stiva de noduri pentru a nu folosi o muchie **node ​-> neigh** (arc) care unea 2 SCC-uri. 
 +      * Într-un graf neorientat nu putem avea o muchie care să unească 2 subarbori, deoarece în momentul în care un capăt este vizitat, adaugă și celălalt capăt în același subarbore. 
 +    * **neigh** este copil al lui **node** în parcurgerea DFS => **found[neigh] > found[node]** 
 +   * după ce un copil **neigh** este vizitat, se cunoaște ***valoarea finală** a lui  **low_link[neigh]** și putem avea 2 cazuri: 
 +    * **low_link[neigh] < found[node] ** 
 +       * inițial **low_link[neigh] = found[neigh]**,​ deci **low_link[neigh] > found[node]** 
 +       * în acest caz există cel puțin o muchie **(y, x)** unde **x** este strămoș ​ și **y** este descendent pentru **node** prin care **y** (și implicit și **node**) își actualizează minimul cu valoarea din **x** 
 +       * drumul **x - ... - node - neigh - ... y - x ** este atunci un ciclu 
 +         * dacă **node** este eliminat din graf, toate nodurile din subarborele lui **neigh** vor rămâne conectate de restul grafului prin muchia **(y, x)** 
 +         * deci nu putem trage vreo concluzie doar analizând vecinul curent **neigh**, trecem la următorul 
 +    * **low_link[neigh>= found[node** 
 +      * în acest caz nu există ciclul **x - ... - node - neigh - ... y - x ** de la cazul anterior 
 +      * eliminarea lui **node** ar duce la separarea subarborelui lui **neigh** de restul grafului 
 +      * prin urmare, numărul de componente conexe crește cu cel puțin 1, deci **node** este sigur un **CV** (concluzie corectă chiar dacă ne-am uitat la un singur vecin **neigh**)
  
-[7] Infoarena - Arhiva educationala - 2SAT [[http://​infoarena.ro/​problema/​2sat]]+</spoiler>
  
 +\\
 +\\
 +Punem la dispoziție un diff de pseudocod: [[https://​pastebin.com/​raw/​8th3Pnjg | TARJAN_SCC vs TARJAN_CV]]. Se observă că este același algoritm, singurele diferențe relevante sunt:
 +  * STEP **3.1**: condiția după care se reactulizează **low_link[node]** în funcție de **neigh** atunci când cel din urmă este deja vizitat
 +  * STEP **7**: condiția prin care se determină dacă **node** este o rădăcină de SCC / CV.
 +\\
  
-===== Resurse ​=====+== Complexitate ​== 
 +  * **complexitate temporală **: $T O(n + m)$ 
 +  * **complexitate spațială ** : $S O(n)$ 
 +    * recursivitate +  câteva tablouri auxiliare de lungime n 
 +===== Punți / muchii critice ===== 
 +>> **Punte** ​ / **muchie critică** / **Critical Edge** (**CE**) este o muchie într-un graf **neorientat** a cărei eliminare duce la creșterea numărului de  componente conexe (CC) - se elimină muchia, fără a se sterge capetele (nodurile) acesteia.
  
-  * [[http://​cs.curs.pub.ro/​2015/​pluginfile.php/​32199/​mod_resource/​content/​1/​Lab%207%20Aplicatii%20DFS.pdf|Varianta PDF a laboratorului]] +<spoiler CE - exemplu 01> 
-===== Probleme =====+$n 8$   ​$m ​6$
  
-<note important>​ +$muchii{ (1,2); (2, 3); (3, 4); (4, 1); (1, 5); (5, 6); (6, 7); (7, 5); (7, 8)} $
-La finalul laboratorului,​ încărcați soluțiile [[http://​cs.curs.pub.ro/​2016/​mod/​assign/​view.php?​id=5493|aici]]. +
-</​note>​+
  
-Tocmai s-a lansat o nouă reţea socială, dedicată exclusiv gamerilor. Întrucât toată lumea doreşte să discute cu prietenii săi din comunitate într-un mod cât mai privat, comunicarea este unidirecțională prin canale dedicate. Astfel, atunci când un jucator intră prima dată pe un server, indică toate persoanele pe care le consideră prieteni și stabilește relațiile outbound (un arc de la noul jucător către fiecare prieten). (**Graf Orientat**) +{{pa:​new_pa:​lab08-ce-example01.png}}
-Ulterior, terțe persoane pot crea tuneluri permanente în sens invers.+
  
-Consideram că doi jucatori ​** aparţin unui clan ** dacă ** fac parte din aceeași componentă tare conexă **.+Sunt **2 CE**-uri în graful dat: (1, 5) și (7,8)
  
 +Explicație:​
 +  * Dacă ștergem muchia (1, 5), graful se sparte în 2 CC-uri: {1, 2, 3, 4}, {5, 6, 7, 8}.
 +  * Dacă ștergem muchia (7, 8), graful se sparte în 2 CC-uri: {1, 2, 3, 4, 5, 6, 7}, {8}.
 +  * Dacă ștergem oricare altă muchie, graful rămâne conex.
 +</​spoiler>​
  
-** Partea 1 **+==== TARJAN CE ==== 
 +Se modifică algoritmul de CV. Se folosesc aceleași definiții și semnificații pentru ​**found** și **low_link**.
  
-1Identificați toate clanurile ​de pe un server+>> **TARJAN CE**: **(node, neigh)** is a **CE** if **low_link[neigh] > found[node]** where **neigh** in **adj[node]**. 
-( **Componente tare conexe** )+ 
 + 
 +<spoiler Explicație> 
 +Există 2 tipuri de muchii în parcugerea DFS într-un graf neorientat:​ 
 +  * **(node, neigh)**: muchiile din arbore (numită șmuchie de arbore) 
 +  * **(y, x)**: muchie de la un nod **y** la un strămoș **x** (numită și muchie înapoi) 
 +Tipul al 2-lea de muchie închide un ciclu, deci clar nu reprezintă un CE. Prin urmare trebuie să căutăm ​toate CE-urile printre muchiile **(node, neigh)** din arbore. 
 + 
 +Când se termină de vizitat subarborele lui **neigh** și cunoaștem valoarea finală a lui **low_link[neigh]** putem avea: 
 +  * **low_link[neigh] <= found[node]**:​  
 +    * analog explicațiilor de la CV, din subarborele lui **neigh** se poate urca până la un nod **x** (x este **node** SAU un strămoș al lui **node**) => muchia **(node, neigh)** face parte dintr-un ciclu 
 +    * prin urmare, dacă se taie aceasta, toate nodurilor ​de pe ciclu rămân conectate, deci nu este CE 
 +  * **low_link[neigh] > found[node]**:​ 
 +    * înseamnă că nu există acel ciclu de la pasul anterior (nu s-a putut urca în arbore mai sus de **node**) 
 +    * prin urmare, **(node, neigh)** este **CE** 
 +</​spoiler>​ 
 + 
 +== Complexitate == 
 +  * **complexitate temporală **: $T = O(n + m)$ 
 +  * **complexitate spațială ** : $S = O(n)$ 
 +    * recursivitate +  câteva structuri de date de lungime $O(n)$ 
 +===== Componente Biconexe ===== 
 + 
 +>> O **componentă biconexă** / **BiConnected Component** (**BCC**) într-un graf **neorientat** este o submulțime maximală de noduri cu proprietarea că nu conține puncte de articulație - oricare nod s-ar elimina, nodurile rămase sunt încă conectate
 + 
 +<spoiler BCC - exemplu 01> 
 +$n = 8$   $m = 9$ 
 + 
 +$muchii: {(1, 2); (2, 3); (3, 4); (4, 1); (1, 5); (5, 6); (6, 7); (7, 5); (7, 8)} $ 
 + 
 +{{pa:​new_pa:​lab08-bcc-example01.png}} 
 + 
 +Sunt **4 BCC**-uri în graful dat:  
 +* {1, 2, 3, 4} 
 +* {1, 5} 
 +* {5, 6, 7} 
 +* {7, 8} 
 + 
 +Explicație:​ 
 +  * Dacă ștergem muchia (1, 5), graful se sparge în 2 CC-uri: {1, 2, 3, 4}, {5, 6, 7, 8}. 
 +  * Dacă ștergem muchia (7, 8), graful se sparge în 2 CC-uri: {1, 2, 3, 4, 5, 6, 7}, {8}. 
 +  * Dacă ștergem oricare altă muchie, graful rămâne conex. 
 +</​spoiler>​
  
-**(Bonus)** Descoperiţi ce relaţii există între clanuri. Care ar fi cel mai numeros clan dacă s-ar adăuga o legatură artificială între oricare doi jucători? ( ) 
  
 \\ \\
-** Partea ​2 **+>> Un graf **neorientat** este **biconex** dacă nu conține puncte de articulație - conține o singură componentă biconexă. 
 + 
 +<spoiler BCC - exemplu 02> 
 +$n = 8$   $m = 10$ 
 + 
 +$muchii: {(1, 2); (2, 3); (3, 4); (4, 1); (1, 5); (5, 6); (6, 7); (7, 5); (7, 8); (8, 2)} $ 
 + 
 +{{pa:​new_pa:​lab08-bcc-example02.png}} 
 + 
 +Sunt **1 BCC**-uri în graful dat: {1, 2, 3, 4, 5, 6, 7, 8} 
 + 
 +Explicație:​ 
 +  * Nu există noduri/​puncte critice în graf (se poate șterge orice nod și graful rămâne conex). 
 +</​spoiler>​ 
 + 
 +<note warning>​ 
 +Împărțirea în componente biconexe a unui graf neorientat reprezintă **o partiție disjunctă a muchiilor grafului** (împreună cu vârfurile adiacente muchiilor). Acest lucru implică faptul că unele vârfuri pot face parte din mai multe componente biconexe diferite (vezi BCC - exemplu 01) - mai exact, punctele de articulație vor face parte din mai multe componente. 
 +</​note>​ 
 +==== TARJAN BCC ==== 
 +Se modifică algoritmul de CV. Se folosesc aceleași definiții și semnificații pentru **found** și **low_link**. 
 + 
 +  * Se folosește o stivă **edges_stack** în care se adaugă toate muchiile **(node, neigh)** atunci când se înaintează în recursivitate.  
 +  * Atunci când se termină de vizitat un copil **neigh**, dacă se îndeplinește condiția de **CV** (**low_link[neigh] >= found[node]**),​ înseamnă că prin eliminarea lui **node** tot subarborele **node - neigh - ...** rămâne deconectat. Prin urmare, toate muchiile din stivă de deasupra muchiei **(node, neigh)** ​ (inclusiv) formează o componentă biconexă (mulțimea de noduri formată din capetele acestor muchii). 
 +  * Se termină de vizitat copilul curent și se trece la următorul. De fiecare dată când se găsește un copil **neigh** cu **low_link[neigh] >= found[node]** se formează o nouă **BCC**. 
 + 
 +== Complexitate == 
 +  * **complexitate temporală **: $T = O(n + m)$ 
 +  * **complexitate spațială ** : $S = O(n + m)$ 
 +    * recursivitate + câteva structuri de date de lungime $O(n)$ / $O(m)$ 
 +      * ATENȚIE! În plus, față de CE/CV , se stochează o stivă de muchii. 
 + 
 + 
 +===== Importanţă – aplicaţii practice ===== 
 +  * SCC: Data Mining, Compilatoare,​ problema 2-SAT. 
 +  * BCC: cele mai importante aplicații se găsesc în rețelele de calculatoare,​ deoarece un BCC asigură redundanţă (există cel puțin 2 căi de a conecta o entitate la celelalte). 
 +   
 +===== TLDR ===== 
 + 
 +  * Se poate folosi/​modifica algoritmul lui Tarjan pentru a determina **SCC**, **CV** / **CE** / **BCC**. 
 +  * Deoarece algoritmul se folosește de o parcurgere DFS, complexitatea este liniară în toate cazurile. 
 + 
 +===== Exercitii ===== 
 +<​note>​ 
 +Scheletul de laborator se găsește pe pagina [[https://​github.com/​acs-pa/​pa-lab/​tree/​main/​skel/​lab08|pa-lab::​skel/​lab08]]. 
 +</​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>​ 
 + 
 +=== SCC === 
 +Se dă un graf **orientat** cu **n** noduri și **m** arce. Să se găsească **componentele tare-conexe** folosind algoritmul lui **Tarjan**. Secțiunea de teorie conține exemple grafice explicate. 
 + 
 +<note warning>​ 
 +Restricții și precizări:​ 
 +  * $ n <= 10^5 $ 
 +  * $ m <= 2 * 10^5 $ 
 +  * timp de execuție 
 +    * C++: 1s 
 +    * Java: 4s 
 +</​note>​ 
 + 
 + 
 +=== CV === 
 +Se dă un graf **neorientat conex** cu **n** noduri și **m** muchii. Se cere să se găsească toate **punctele critice** folosind algoritmul lui **Tarjan**. Secțiunea de teorie conține exemple grafice explicate. 
 + 
 +<note warning>​ 
 +Restricții și precizări:​ 
 +  * $ n <= 10^5 $ 
 +  * $ m <= 2 * 10^5 $ 
 +  * timp de execuție 
 +    * C++: 1s 
 +    * Java: 4s 
 +</​note>​ 
 + 
 + 
 +=== CE === 
 +Se dă un graf **neorientat conex** cu **n** noduri și **m** muchii. Se cere să se găsească toate **muchiile critice** folosind algoritmul lui **Tarjan**. Secțiunea de teorie conține exemple grafice explicate. 
 + 
 +<note warning>​ 
 +Restricții și precizări:​ 
 +  * $ n <= 10^5 $ 
 +  * $ m <= 2 * 10^5 $ 
 +  * timp de execuție 
 +    * C++: 1s 
 +    * Java: 4s 
 +</​note>​ 
 + 
 +=== BCC === 
 +Se dă un graf **neorientat conex** cu **n** noduri și **m** muchii. Se cere să se găsească toate **componentele biconexe** folosind algoritmul lui **Tarjan**. Secțiunea de teorie conține exemple grafice explicate. 
 + 
 +<note warning>​ 
 +Restricții și precizări:​ 
 +  * $ n <= 10^5 $ 
 +  * $ m <= 2 * 10^5 $ 
 +  * timp de execuție 
 +    ​C++: 1s 
 +    ​Java: 4s 
 +</​note>​ 
 + 
 + 
 +=== Extra === 
 +<spoiler rețele>​ 
 +Rezolvați problema [[https://​infoarena.ro/​problema/​retele| retele]] pe infoarena. 
 +</​spoiler>​ 
 + 
 +<spoiler clepsidra>​ 
 +Rezolvați problema [[https://​infoarena.ro/​problema/​clepsidra| clepsidra]] pe infoarena. 
 +</​spoiler>​
  
-Ulterior, pentru a spori uşurința utilizării,​ canalele de comunicație au devenit bi-direcționale și, pentru a evalua relațiile dintre jucători, dorim să identificăm următoarele elemente cheie din fiecare clan: 
  
-2) Care sunt jucătorii critici a căror eliminare ar duce la distrugerea comunicației în cadrul clanului?+<spoiler Course schedule>​ 
 +Rezolvați problema [[https://​leetcode.com/​problems/​course-schedule/​description/​| course-schedule]] pe leetcode. 
 +(aplicație tipuri de muchii) 
 +</​spoiler>​
  
-3) Care canale între 2 jucători sunt critice la nivelul clanului?+===== Referințe =====
  
-<​hidden>​ +[0] Chapter ​**Elementary Graph Algorithms**, “Introduction to Algorithms”,​ Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest and Clifford Stein
-În urma unui update, rețeaua sociala a căpătat legături bidirectionale. (** Graf Neorientat ​**) Pentru a evalua relațiile dintre jucători dorim să identificăm următoarele elemente cheie din graf:+
  
-2) Care jucători reprezintă **puncte de articulatie** în rețea?+[1] [[https://​en.wikipedia.org/​wiki/​Tarjan%27s_strongly_connected_components_algorithm]]
  
-3) Ce relații reprezintă **muchii critice** în rețea?+[2] [[https://​en.wikipedia.org/​wiki/​Biconnected_component]]
  
-4) Care grupuri de jucatori formează ** componente biconexe **?+[3] "​Depth-first search and linear graph algorithms",​ R.Tarjan ​
  
-</hidden>+[4] [[https://​en.wikipedia.org/​wiki/​Kosaraju%27s_algorithm]]
pa/laboratoare/laborator-07.1491724382.txt.gz · Last modified: 2017/04/09 10:53 by andrei_mario.dinu
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