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.
Un graf neorientat este conex dacă conține o singură componentă conexă.
$T = O(n + m)$
$T = O(n + m)$
O componentă tare conexă (CTC) / Strongly Connected Component (SCC) într-un graf orientat 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.
Un graf orientat este tare conex dacă conține o singură componentă tare conexă.
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).
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:
// the timestamp when node was found (when started to visit its subtree) found[node] = start[node]; // 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) };
Tarjan SCC: node is root for a SCC if low_link[node] == found[node].
// Tarjan_SCC // * visit all nodes with DFS // * compute found[node] and 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 1: initialize 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's in the visiting stack; // otherwise, neigh is from other SCC, so 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); } }
Observații:
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.
Puteți consulta următoarele materiale dacă doriți să aflați mai multe:
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.
Putem modifica ușor algoritmul TARJAN SCC astfel încât să obținem Algoritmul lui Tarjan pentru CV.
În mod analog, pentru a determina dacă un nod este CV, se definesc și folosesc found și low_link.
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
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.
Punem la dispoziție un diff de pseudocod: TARJAN_SCC vs TARJAN_CV. Se observă că este același algoritm, singurele diferențe relevante sunt:
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.
Se modifică algoritmul de CV. Se folosesc aceleași definiții și semnificații pentru found și low_link.
TARJAN CE: (node, neigh) is a CE if low_link[neigh] > found[node] where neigh in adj[node].
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.
Un graf neorientat este biconex dacă nu conține puncte de articulație - conține o singură componentă biconexă.
Se modifică algoritmul de CV. Se folosesc aceleași definiții și semnificații pentru found și low_link.
Prin citirea acestor precizări vă asigurați că:
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.
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.
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.
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.
[0] Chapter Elementary Graph Algorithms, “Introduction to Algorithms”, Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest and Clifford Stein
[1] https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
[2] https://en.wikipedia.org/wiki/Biconnected_component
[3] “Depth-first search and linear graph algorithms”, R.Tarjan