Un ordinamento topologico è una permutazione dei nodi di un grafo tale che, se , allora compare prima di nell’ordinamento (ovvero tutte le frecce puntano in una sola direzione).
- non è sempre possibile trovarne uno
Un grafo diretto può avere da a ordinamenti topologici (ha il massimo numero di ordinamenti topologici quando non ha archi - tendenzialmente, più archi ci sono, meno sono gli ordinamenti topologici)
esiste un sort topologico il grafo è un DAG (grafo diretto aciclico)
Un DAG ha infatti sempre un nodo sorgente, ovvero un nodo in cui non entrano archi. Questa proprietà ci permette di costruire un ordinamento topologico in questo modo:
- inizio la sequenza dei nodi con una sorgente
- cancello dal DAG quel nodo sorgente e gli archi che partono da esso - otterrò un nuovo DAG
- ripeto fino ad aver sistemato in ordine lineare tutti i nodi
fatto anche qui (basi di dati 1, serializzabilità)
algoritmo per il sort topologico (basato sulle sorgenti)
- restituisce un sort topologico di se esiste - altrimenti, una lista vuota
def sortTop(G):
n = len(G)
gradoEnt = [0]*n
for i in range(n):
for j in G[i]:
gradoEnt[j] += 1
# se j compare in una qualsiasi lista di adiacenza,
# vuol dire che ha archi entranti
sorgenti = [ i for i in range(len(G)) if gradoEnt[i]==0 ]
ST = []
while sorgenti:
u = sorgenti.pop()
ST.append(u)
for v in G[u]:
gradoEnt[v] -= 1
if gradoEnt[v] == 0:
sorgenti.append(v)
if len(ST) == len(G): return ST
return []
- creare il vettore dei gradi entranti costa
- inizializzare l’insieme delle sorgenti costa
- il while viene eseguito volte e il costo totale del for al termine del while è
il costo totale è quindi .
algoritmo per il sort topologico basato su DFS
- si effettua una visita DFS
- man mano che termina la visita dei vari nodi, si inseriscono in una lista
- si restituisce come ordinamento dei nodi il reverse della lista
prova di correttezza
Siano e due nodi in , con arco . Consideriamo sia il caso in cui l’arco viene attraversato che quello in cui non viene attraversato dalla visita, e vediamo che in entrambi, prima di effettuare il reverse, precede .
- l’arco viene attraversato durante la visita ⇒ la visita di finisce prima della visita di , e finisce nella lista prima di
- l’arco non viene attraversato durante la visita ⇒ significa che il nodo è già stato visitato prima della visita di , e la sua visita è già terminata (infatti, non può esserci un cammino , o il grafo non sarebbe aciclico). Quindi, anche in questo caso, si trova prima di nella lista.
esempio
(il sort topologico sarà quindi )
def DFS(u, G, visitati, lista):
visitati[u] = 1
for v in G[u]:
if visitati[v] == 0:
DFSr(v, G, visitati, lista)
lista.append(u)
def sortTop1(G):
visitati = [0]*len(G)
lista = []
for u in range(len(G)):
if visitati[u] == 0:
DFS(u, G, visitati, lista)
lista.reverse()
return lista
La complessità di questo algoritmo è (perché nella DFS si visitano sempre nodi diversi) (per il reverse()
), ovvero .
perche non appendere in testa ?
Il costo di un append in testa è proporzionale alla dimensione della lista, quindi, per la sommatoria di Gauss, il loro costo totale arriverebbe a . Appendere in coda invece costa sempre , e il reverse costa .