I punti di articolazione si cercano mediante la ricerca in profondità. Per ogni vertice vengono calcolate due etichette:
— il tempo di ingresso nel vertice durante la visita in profondità;
— il tempo di ingresso minimo tra i vertici raggiungibili dal sottoalbero con radice in , percorrendo un solo arco all'indietro.
Un vertice , che non sia la radice dell'albero di visita, è un punto di articolazione se ha un figlio nell'albero di visita in profondità, per il quale vale la disuguaglianza up[to] \ge d[v]. Questa disuguaglianza significa che dal sottoalbero con radice in , tramite archi all'indietro, non è possibile risalire più in alto del vertice . Pertanto, rimuovendo , questo sottoalbero risulterà tagliato fuori dal resto del grafo.
La radice dell'albero di visita è un punto di articolazione se e solo se ha almeno due figli nell'albero di visita.
Esempio
Il grafo dell'esempio ha il seguente aspetto:
Con linee in grassetto sono evidenziati gli archi dell'albero di visita in profondità. Accanto a ogni vertice sono indicate le etichette d[v] / up[v]. I punti di articolazione sono evidenziati con un colore.
Il vertice 1 — è la radice dell'albero di visita. È un punto di articolazione, poiché nell'albero di visita ha due figli: i vertici e .
Il vertice 2 — è un punto di articolazione, poiché per l'arco dell'albero vale up[6] \ge d[2]~(2 \ge 2).
Il vertice 3 — è un punto di articolazione, poiché per l'arco dell'albero vale up[8] \ge d[3]~(3 \ge 3).
Implementazione dell'algoritmo
Il grafo viene memorizzato come lista di adiacenza . L'array segna i vertici visitati, negli array e sono memorizzate le omonime etichette. La variabile contiene il tempo corrente della visita in profondità. I punti di articolazione trovati vengono raccolti nell'insieme : esso esclude i duplicati e mantiene i vertici in ordine crescente.
vector<vector<int>> g;
vector<int> d, up, used;
set<int> ArtP;La funzione dfs esegue la ricerca in profondità a partire dal vertice e contestualmente trova i punti di articolazione. Il parametro — è il genitore del vertice nell'albero di visita. Per la radice dell'albero p = -1, questo è il valore predefinito.
void dfs(int v, int p = -1)
{All'ingresso nel vertice lo segniamo come visitato. Le etichette e ricevono il valore corrente del timer, dopodiché il timer viene incrementato.
used[v] = true;
d[v] = up[v] = timer++;La variabile children conta i figli del vertice nell'albero di visita; questo numero servirà se risulta essere la radice.
int children = 0;Scorriamo i vertici adiacenti a . L'arco (v, to) può essere di uno dei tre tipi:
un arco dell'albero, esaminato in direzione inversa, verso il genitore (to = p). Un tale arco viene saltato;
un arco all'indietro (il vertice è già stato visitato e to \ne p);
un arco dell'albero che conduce a un vertice non ancora visitato.
for (int to : g[v])
{
if (to == p) continue;Se il vertice è già stato visitato, allora (v, to) — è un arco all'indietro. Ricalcoliamo il valore di .
if (used[to])
up[v] = min(up[v], d[to]); // back edge
else
{Altrimenti (v, to) — è un arco dell'albero. Avviamo la ricerca in profondità dal vertice , il cui genitore diventa . Dopo il ritorno dalla ricorsione ricalcoliamo il valore di .
dfs(to, v);
up[v] = min(up[v], up[to]);Se up[to] \ge d[v] e il vertice non è la radice (p \ne -1), allora — è un punto di articolazione: dal sottoalbero con radice in non si può risalire più in alto di , quindi la rimozione di taglierà fuori questo sottoalbero dal resto del grafo.
if (up[to] >= d[v] && p != -1) // vertice non radice
ArtP.insert(v);Il vertice — è l'ennesimo figlio di nell'albero di visita, incrementiamo il contatore children.
children++;
}
}La radice dell'albero di visita (p = -1) è un punto di articolazione se e solo se ha più di un figlio.
if (p == -1 && children > 1) // radice dell'albero DFS
ArtP.insert(v);
}Parte principale del programma. Leggiamo i dati di input.
scanf("%d %d", &n, &m);
g.resize(n + 1);
for (int i = 0; i < m; i++)
{
scanf("%d %d", &a, &b);Il grafo è non orientato, quindi ogni arco viene aggiunto alle liste di adiacenza di entrambi i suoi estremi.
g[a].push_back(b);
g[b].push_back(a);
}Allochiamo memoria per gli array , e .
d.resize(n + 1);
up.resize(n + 1);
used.resize(n + 1);Inizializziamo il timer e avviamo la ricerca in profondità da ogni vertice non ancora visitato: il grafo potrebbe essere non connesso.
timer = 1;
for (int i = 1; i <= n; i++)
if (!used[i]) dfs(i);Stampiamo il numero di punti di articolazione, e poi i vertici stessi. L'insieme è ordinato, quindi i vertici vengono stampati in ordine crescente.
printf("%d\n", ArtP.size());
for (int v : ArtP)
printf("%d\n", v);Implementazione in Java
import java.util.*;
public class Main
{
static ArrayList<Integer>[] g;
static int used[], d[], up[];
static int time;
static TreeSet<Integer> ArtPoints = new TreeSet<Integer>();
static void dfs (int v, int p)
{
int i, to, children;
used[v] = 1;
d[v] = up[v] = time++;
children = 0;
for (i = 0; i < g[v].size(); i++)
{
to = g[v].get(i);
if (to == p) continue;
if (used[to] == 1)
up[v] = Math.min(up[v], d[to]);
else
{
dfs (to, v);
up[v] = Math.min(up[v], up[to]);
if ((up[to] >= d[v]) && (p != -1)) ArtPoints.add(v);
children++;
}
}
if ((p == -1) && (children > 1)) ArtPoints.add(v);
}
@SuppressWarnings("unchecked")
public static void main(String[] args)
{
Scanner con = new Scanner(System.in);
int n = con.nextInt();
int m = con.nextInt();
g = new ArrayList[n+1]; // unchecked
used = new int[n+1];
d = new int[n+1];
up = new int[n+1];
for (int i = 0; i <= n; i++)
g[i] = new ArrayList<Integer>();
for (int i = 0; i < m; i++)
{
int a = con.nextInt();
int b = con.nextInt();
g[a].add(b);
g[b].add(a);
}
time = 1;
for(int i = 1; i <= n; i++)
if (used[i] == 0) dfs(i,-1);
System.out.println(ArtPoints.size());
for(int i : ArtPoints)
System.out.println(i);
con.close();
}
}Implementazione in Python
Aumentiamo la profondità massima di ricorsione.
import sys
sys.setrecursionlimit(10**6)Il grafo viene memorizzato come lista di adiacenza . La lista segna i vertici visitati, nelle liste e sono memorizzate le omonime etichette. I punti di articolazione trovati vengono raccolti nell'insieme ArtPoints.
g = []
used = []
d = []
up = []
ArtPoints = set()La funzione dfs esegue la ricerca in profondità a partire dal vertice e contestualmente trova i punti di articolazione. Il parametro — è il genitore del vertice nell'albero di visita; per la radice p = -1. La variabile children conta i figli del vertice nell'albero di visita.
def dfs(v, p = -1):
global t
children = 0All'ingresso nel vertice lo segniamo come visitato. Le etichette e ricevono il valore corrente di , dopodiché viene incrementato.
used[v] = 1
d[v] = up[v] = t
t += 1Scorriamo i vertici adiacenti a . L'arco (v, to) può essere di uno dei tre tipi:
un arco dell'albero, esaminato in direzione inversa, verso il genitore (to = p). Un tale arco viene saltato;
un arco all'indietro (il vertice è già stato visitato e to \ne p);
3. un arco dell'albero che conduce a un vertice non ancora visitato.
for to in g[v]:
if to == p: continueSe il vertice è già stato visitato, allora (v, to) — è un arco all'indietro. Ricalcoliamo il valore di .
if used[to]:
up[v] = min(up[v], d[to])
else:Altrimenti (v, to) — è un arco dell'albero. Avviamo la ricerca in profondità dal vertice , il cui genitore diventa . Dopo il ritorno dalla ricorsione ricalcoliamo il valore di .
dfs(to, v)
up[v] = min(up[v], up[to])Se up[to] \ge d[v] e il vertice non è la radice (p ≠ -1), allora — è un punto di articolazione.
if up[to] >= d[v] and p != -1:
ArtPoints.add(v)Il vertice — è l'ennesimo figlio di nell'albero di visita, incrementiamo il contatore children.
children += 1La radice dell'albero di visita (p = -1) è un punto di articolazione se e solo se ha più di un figlio.
if p == -1 and children > 1:
ArtPoints.add(v)Parte principale del programma. Creiamo le liste e leggiamo i dati di input.
n, m = map(int, input().split())
g = [[] for _ in range(n + 1)]
used = [0] * (n + 1)
d = [0] * (n + 1)
up = [0] * (n + 1)Leggiamo gli archi del grafo non orientato e riempiamo le liste di adiacenza.
for _ in range(m):
a, b = map(int, input().split())
g[a].append(b)
g[b].append(a)Avviamo la ricerca in profondità da ogni vertice non ancora visitato: il grafo potrebbe essere non connesso.
t = 1
for i in range(1, n + 1):
if not used[i]: dfs(i)Stampiamo il numero di punti di articolazione, e poi i vertici stessi in ordine crescente.
print(len(ArtPoints))
for x in sorted(ArtPoints):
print(x)