Skip to Content

Encontrar puntos de articulación en un grafo en O(N+M)O(N+M)

Se nos da un grafo no dirigido. Un punto de articulación (o cut vertex) se define como un vértice que, al quitarlo junto con las aristas asociadas, desconecta el grafo (o, más precisamente, aumenta el número de componentes conexas del grafo). La tarea es encontrar todos los puntos de articulación del grafo dado.

El algoritmo descrito acá se basa en búsqueda en profundidad y tiene complejidad O(N+M)O(N+M), donde NN es la cantidad de vértices y MM la cantidad de aristas del grafo.

Algoritmo

Elegí un vértice arbitrario del grafo rootroot y corré búsqueda en profundidad desde él. Nótese el siguiente hecho (fácil de demostrar):

  • Digamos que estamos en el DFS, mirando las aristas que salen del vértice vrootv\ne root. Si la arista actual (v,to)(v, to) es tal que ninguno de los vértices toto o de sus descendientes en el árbol de recorrido DFS tiene una back-edge hacia alguno de los ancestros de vv, entonces vv es un punto de articulación. En caso contrario, vv no es un punto de articulación.

  • Consideremos el caso restante v=rootv=root. Este vértice será punto de articulación si y solo si tiene más de un hijo en el árbol DFS.

Ahora hay que aprender a chequear este hecho para cada vértice de forma eficiente. Usaremos el “tiempo de entrada al nodo” computado por la búsqueda en profundidad.

Así, sea tin[v]tin[v] el tiempo de entrada del nodo vv. Introducimos un arreglo low[v]low[v] que nos permitirá chequear el hecho para cada vértice vv. low[v]low[v] es el mínimo de tin[v]tin[v], los tiempos de entrada tin[p]tin[p] de cada nodo pp conectado al nodo vv vía una back-edge (v,p)(v, p) y los valores de low[to]low[to] de cada vértice toto que es descendiente directo de vv en el árbol DFS:

low[v]=min{tin[v]tin[p] for all p for which (v,p) is a back edgelow[to] for all to for which (v,to) is a tree edgelow[v] = \min {tin[v]tin[p]amp; for all p for which (v,p) is a back edgelow[to]amp; for all to for which (v,to) is a tree edge\begin{cases} tin[v] \ tin[p] &\text{ for all }p\text{ for which }(v, p)\text{ is a back edge} \ low[to]& \text{ for all }to\text{ for which }(v, to)\text{ is a tree edge} \end{cases}

Ahora, hay una back-edge desde el vértice vv o uno de sus descendientes hacia uno de sus ancestros si y solo si el vértice vv tiene un hijo toto para el cual low[to]<tin[v]low[to] < tin[v]. Si low[to]=tin[v]low[to] = tin[v], la back-edge llega directamente a vv; si no, llega a uno de los ancestros de vv.

Así, el vértice vv en el árbol DFS es un punto de articulación si y solo si low[to]tin[v]low[to] \geq tin[v].

Implementación

La implementación necesita distinguir tres casos: cuando bajamos por la arista en el árbol DFS, cuando encontramos una back-edge hacia un ancestro del vértice y cuando volvemos al padre del vértice. Estos son los casos:

  • visited[to]=falsevisited[to] = false - la arista es parte del árbol DFS;
  • visited[to]=truevisited[to] = true && toparentto \neq parent - la arista es una back-edge hacia uno de los ancestros;
  • to=parentto = parent - la arista vuelve al padre en el árbol DFS.

Para implementar esto, necesitamos una función de búsqueda en profundidad que acepte el vértice padre del nodo actual.

int n; // number of nodes vector<vector<int>> adj; // adjacency list of graph vector<bool> visited; vector<int> tin, low; int timer; void dfs(int v, int p = -1) { visited[v] = true; tin[v] = low[v] = timer++; int children=0; for (int to : adj[v]) { if (to == p) continue; if (visited[to]) { low[v] = min(low[v], tin[to]); } else { dfs(to, v); low[v] = min(low[v], low[to]); if (low[to] >= tin[v] && p!=-1) IS_CUTPOINT(v); ++children; } } if(p == -1 && children > 1) IS_CUTPOINT(v); } void find_cutpoints() { timer = 0; visited.assign(n, false); tin.assign(n, -1); low.assign(n, -1); for (int i = 0; i < n; ++i) { if (!visited[i]) dfs (i); } }

La función principal es find_cutpoints; hace la inicialización necesaria y arranca búsqueda en profundidad en cada componente conexa del grafo.

La función IS_CUTPOINT(a) es alguna función que procesará el hecho de que el vértice aa es un punto de articulación, por ejemplo, imprimirlo (cuidado: se puede llamar varias veces para un mismo vértice).

Problemas de práctica