Se segui Algoritmi e strutture dati (informatica o ingegneria), le lezioni sono partite da qualche settimana e di solito è il periodo in cui dopo la complessità arrivano grafi e alberi. L’esame della sessione invernale, tra gennaio e febbraio, cade fra otto e dieci settimane: hai tempo per costruire il metodo con calma. Questo argomento ha un vantaggio: le tracce a mano si imparano a fare, e all’esame sono quasi sempre il modo più sicuro di prendere punti. Prima di questo post conviene avere chiara la complessità con la notazione O-grande; per la rappresentazione in memoria con nodi e riferimenti ti servono anche i puntatori in C.
Rappresentare un grafo
Un grafo ha un insieme di nodi (vertici) e uno di archi . Useremo e anche per i loro numeri, come nei costi. Due rappresentazioni standard:
- Liste di adiacenza: per ogni nodo, la lista dei suoi vicini. Occupa spazio ed è la scelta naturale per grafi radi.
- Matrice di adiacenza: una tabella con 1 (o il peso) se esiste l’arco. Occupa e permette di sapere in tempo costante se due nodi sono collegati.
In un grafo non orientato ogni arco compare due volte (in due liste, in due celle simmetriche): la somma dei gradi è .
Il grafo di tutto l’articolo ha 6 nodi e 6 archi: , , , , , . Liste di adiacenza, in ordine alfabetico:
| Nodo | Vicini |
|---|---|
| A | B, C |
| B | A, D |
| C | A, D, E |
| D | B, C |
| E | C, F |
| F | E |
I gradi sono 2, 2, 3, 2, 2, 1: sommano a 12, cioè con .
BFS: visita in ampiezza con la coda
Si parte da una sorgente, la si marca e la si mette in coda. Finché la coda non è vuota: si estrae il primo nodo e si mettono in coda i suoi vicini non ancora marcati, marcandoli subito.
BFS(G, s):
marca s; coda = [s]
finché coda non è vuota:
u = estrai(coda)
visita u
per ogni v in adiacenti(u):
se v non è marcato:
marca v; inserisci v in coda
Traccia da :
- Estraggo : inserisco . Coda: .
- Estraggo : è marcato, inserisco . Coda: .
- Estraggo : e già marcati, inserisco . Coda: .
- Estraggo : i vicini sono già marcati. Coda: .
- Estraggo : inserisco . Coda: .
- Estraggo . Coda vuota.
Ordine di visita BFS: . Il livello a cui scopro un nodo è la distanza minima in archi: , , , . Per questo la BFS trova i cammini minimi nei grafi non pesati.
DFS: visita in profondità con ricorsione
Si va avanti sul primo vicino non visitato e si torna indietro solo quando i vicini sono finiti.
DFS(u):
marca u; visita u
per ogni v in adiacenti(u):
se v non è marcato:
DFS(v)
Traccia da : parto da e vado in ; da il vicino è marcato, quindi vado in ; da , è marcato, vado in ; da , e sono marcati, vado in ; da vado in . Da non c’è altro e torno indietro fino ad .
Ordine di visita DFS: . Rispetto alla BFS cambia: viene prima di , perché la DFS si addentra prima di tornare. La pila delle chiamate ricorsive fa il lavoro che nella BFS fa la coda. Se scrivi la DFS iterativa con una pila esplicita, attenzione all’ordine: se inserisci i vicini in ordine alfabetico, il primo estratto è l’ultimo, quindi l’ordine di visita si inverte rispetto a quello ricorsivo, a meno di inserirli al contrario.
Complessità: O(V+E)
Con le liste di adiacenza, in entrambe le visite ogni nodo viene marcato e processato una volta (costo ) e ogni lista viene scorsa una volta; le liste hanno in tutto elementi per un grafo non orientato, per uno orientato. Il costo è
Con la matrice di adiacenza, per trovare i vicini di un nodo bisogna scorrere un’intera riga di celle: per nodi il costo diventa . Su grafi radi le liste vincono.
Alberi binari: pre-, in- e post-ordine
Un albero è un grafo connesso e senza cicli; l’albero binario ha per ogni nodo al più un figlio sinistro e uno destro. Le visite sono una DFS con tre varianti, secondo il momento in cui visiti la radice:
- pre-ordine: radice, sottoalbero sinistro, sottoalbero destro;
- in-ordine: sinistro, radice, destro;
- post-ordine: sinistro, destro, radice.
Prendiamo l’albero con radice 8, figli di 8: 3 e 10; figli di 3: 1 e 6; figli di 6: 4 e 7; 10 ha solo il figlio destro 14, e 14 ha il solo figlio sinistro 13.
- Pre-ordine:
- In-ordine:
- Post-ordine:
- Per livelli (la BFS sull’albero):
In questo albero valgono le regole dell’albero binario di ricerca (a sinistra i minori, a destra i maggiori), perciò l’in-ordine esce ordinato. È un’osservazione da esame, ma vale solo per quel tipo di albero.
Gli errori tipici all’esame
Ordine dei vicini. L’ordine di visita dipende dall’ordine con cui sono scritte le liste di adiacenza. Se il testo non lo dice, dichiaralo (“vicini in ordine alfabetico”): con le liste invertite, sul nostro grafo la BFS darebbe e la DFS . Una risposta diversa dalla soluzione può comunque essere corretta, se dichiari la convenzione.
Nodi già visitati. Dimenticare il marcamento significa tornare su nodi già visti e, se c’è un ciclo, non terminare. Un’altra variante: nella BFS marchi il nodo quando lo estrai e non quando lo inserisci, e così lo puoi mettere in coda più volte.
Confondere coda e pila. BFS con la pila non è una BFS.
Pre-, in- e post-ordine. Scrivere la sequenza dalla definizione, partendo dalla radice e scendendo ricorsivamente, non per intuizione.
Costo. Dire per le liste, o senza il : in un grafo non connesso o con nodi isolati il termine non si può tralasciare.
Tre esercizi svolti
1. Cammino minimo con la BFS
Qual è il numero minimo di archi per andare da a nel grafo sopra? Dai un cammino.
Dalla traccia BFS: è scoperto da a livello 3, da a livello 2, da a livello 1. Cammino , 3 archi. Non esiste un cammino più corto: per arrivare a bisogna passare da , unico vicino di , e è raggiungibile solo da , che dista 1 da .
2. Liste o matrice?
Confronta lo spazio per il grafo di 6 nodi e 6 archi, e per un grafo con 1000 nodi e 3000 archi.
Per il nostro grafo la matrice ha celle, le liste elementi (più 6 teste). Con e : la matrice ha celle, le liste circa elementi (più 1000 teste). Il grafo è molto rado, e infatti le liste costano una piccola frazione. Per una visita, il confronto è contro .
3. Alberi: ricostruire dalla coppia di visite
Pre-ordine ; in-ordine . Ricostruisci l’albero e scrivi il post-ordine.
La radice è il primo del pre-ordine: . Nell’in-ordine, stanno a sinistra di e a destra. Il sottoalbero sinistro ha pre-ordine : radice ; nell’in-ordine è a sinistra di e a destra. Quindi ha come figli (con figli e ) e . Post-ordine: .
Metti alla prova
Per grafi e alberi è utile la pagina delle simulazioni per l’università, che raccoglie i quesiti per corso: controlla quanto copre di Algoritmi, perché la copertura dipende dal corso. L’esercizio più utile resta rifare le tracce a mano su un grafo tuo, cambiando sorgente e ordine dei vicini. Anche lo studio di mappe di Karnaugh e algebra di Boole è un argomento di Fondamenti dove la traccia a mano paga allo stesso modo. Altre risorse sono nella pagina informatica; per un confronto 1-a-1 online sulle tracce, puoi partire da una diagnosi gratuita.