Dato un grafo (diretto o indiretto), vogliamo sapere se esso è ciclico

Un’idea (errata) di partenza potrebbe essere quella di visitare un grafo e, se si incontra un nodo già visitato, interrompere la visita e restituire (e, altrimenti, restituire ), ma:

  • non funziona con i grafi indiretti perché in un grafo indiretto, se , anche , quindi nella lista di adiacenza di ci sarà e viceversa ogni arco verrebbe considerato un ciclo e farebbe ritornare True
  • non funziona neanche con alcuni grafi diretti: prendiamo come esempio il grafo : partendo dal nodo , si visita prima il nodo (che non ha archi uscenti), e poi il nodo . Ma il nodo ha un arco uscente verso (già visitato) l’algoritmo ritornerebbe quindi , nonostante non ci siano cicli.

algoritmo per grafi indiretti

Per il caso dei grafi indiretti, si può risolvere il problema tenendo traccia del nodo padre ed escludendolo dai controlli.

def DFSc(u, padre, G, visitati):
	visitati[u] = 1
	for v in G[u]:
		if visitati[v] == 1:
			if v != padre:
				return True
		else:
			if DFSc(v, u, G, visitati):
				return True
	return False
 
def ciclo(u, G):
	visitati = [0]*len(G)
	return DFSc(u, u, G, visitati)

Questo algoritmo ha complessità perché:

  • se il grafo non contiene cicli, avrà al più archi e
  • se contiene cicli, se ne scopre uno dopo aver considerato al più archi

algoritmo per grafi diretti

Si nota che durante la visita DFS, si possono incontrare nodi già visitati in 3 casi:

  1. archi in avanti (da un antenato a un discendente)
  2. archi all’indietro (da un discendente a un antenato)
  3. archi di attraversamento (tutti gli altri)

Solo gli archi all’indietro mostrano la presenza di un ciclo (un nodo punta a un suo antenato).

diversi tipi di archi

center

Serve quindi un modo di distinguere i tre casi. Si nota che solo nel caso degli archi all’indietro la visita di un nodo non ha terminato la sua sezione ricorsiva.

Basta quindi modificare il vettore dei visitati in modo che i suoi valori siano:

  • se il nodo non è ancora stato visitato
  • se il nodo è stato visitato, ma la ricorsione su di esso non è ancora finita
  • se il nodo è stato visitato e la ricorsione su di esso è finita

In questo modo, scopro un ciclo quando trovo un arco diretto verso un nodo già visitato che si trova nello stato .

algoritmo corretto:

def DFSc(u, G, visitati):
	visitati[u] = 1
	for v in G[u]:
		if visitati[v] == 1: return True # stato "in elaborazione"
			
		if visitati[v] == 0:
			if DFSc(v, G, visitati): return True
	
	visitati[u] = 2 # nodo completamente esplorato
 
# se so già da che nodo partire:
def cicloD(u, G)
	visitati = [0]*len(G)
	return DFSc(u, G, visitati)
 
# se non so da che nodo partire:
def cicloD(G):
	visitati = [0]*len(G)
	for u in range(len(G)):
		if visitati[u] == 0:
			if DFSc(u, G, visitati):
				return True
	return False
  • la complessità è in entrambi i casi