Una componente connessa (o semplicemente “componente”) di un grafo indiretto è un sottografo composto da un insieme massimale di nodi connessi da cammini.

grafo connesso

Un grafo indiretto si dice connesso se ha una sola componente.

esempio di grafo con 5 componenti:

center

Si può calcolare il vettore delle componenti connesse di un grafo . Il vettore () ha tanti elementi quanti sono i nodi del grafo, e e sono nella stessa componente connessa.

Per l’esempio sopra, il vettore sarebbe:

algoritmo per il vettore delle componenti connesse:

def Componenti(x, G, C, c):
	C[x] = c
	for y in G[x]:
		if C[y] == 0:
			Componenti(y, G, C, c)
 
def main(G):
	C = [0]*len(G)
	c = 0
	for x in range(len(G)):
		if C[x] == 0:
			c+=1
			Componenti(x, G, C, c)
	return C
  • questo algoritmo ha complessità

componente fortemente connessa

Una componente fortemente connessa di un grafo diretto è un sottografo composto da un insieme massimale di nodi connessi da cammini.

grafo fortemente connesso

Come per i grafi indiretti, un grafo diretto si dice fortemente connesso se ha una sola componente.

  • un grafo fortemente connesso con più di un nodo è sempre ciclico

grafo fortemente connesso

center

  • ogni nodo deve quindi essere raggiungibile da tutti gli altri nodi

esempio di grafo con 5 componenti:

center

attenzione

l’algoritmo per le componenti connesse non funziona nel caso di componenti fortemente connesse: infatti, se c’è un cammino da a , non è detto che ce ne sia anche uno da a .

Per scrivere un algoritmo che, dato un grafo diretto ed un suo nodo , calcola i nodi della componente fortemente connessa che contiene , è utile creare il grafo trasposto di .

grafo trasposto

Dato un grafo diretto , il suo grafo trasposto ha gli stessi nodi di , ma archi con direzione opposta.

(quindi, se facciamo una DFS a partire da un nodo , otterremo tutti i nodi che, in , portano a )

algoritmo

Un possibile algoritmo per trovare il vettore delle componenti fortemente connesse funziona quindi così:

  1. si calcola l’insieme dei nodi raggiungibili da
    • semplice visita DFS
  2. si costruisce il grafo trasposto
  3. si calcola l’insieme dei nodi che portano a
    • visita DFS su
  4. si restituisce l’intersezione dei due insiemi e -
    • a questo punto si hanno due vettori dei visitati (con la stessa cardinalità) - si scorrono semplicemente gli indici e si controlla per quali nodi entrambi hanno valore

algoritmo per trovare la componente fortemente connessa di un nodo:

def ComponenteFC(x, G):
	visitati = DFS(x,G)
	GT = Trasposto(G)
	visitatiT = DFS(x, GT)
	
	componente = []
	for i in range(len(G)):
		if visitati[i] == visitatiT[i] == 1:
			componente.append(i)
	return componente
def Trasposto(G):
	GT = [ [] for _ in G]
	for i in range(len(G)):
		for v in G[i]:
			GT[v].append(i) 
# (metto il nodo sorgente nella lista di adiacenza del n. destinazione)
	return GT

questo algoritmo può essere usato come subroutine per trovare le componenti fortemente connesse di tutti i nodi:

algoritmo per trovare il vettore delle componenti FC:

def compFC(G):
	FC = [0]*len(G)
	c = 0
	for i in range(len(G)):
		if FC[i] == 0: 
			E = ComponenteFC(i, G) 
			# ^ ritorna un array con i numeri dei nodi nella comp.
			c+=1
			for x in E:
				FC[x] = c
	return FC

Al caso pessimo, la complessità sarà . Consideriamo il caso di un grafo diretto avente un arco per ogni .

  • facciamo visite, di cui ognuna costa
  • ma gli archi sono , quindi:

Esistono algoritmi che lavorano in tempo , come l’algoritmo di Tarjan e quello di Kosaraju (da aggiungere !! ma non in programma).