Esercizio 23escursioni e componenti connesse
In questa pagina 4
Testo (scritto del 07/08/2026, seconda parte, esercizio 1, 5 punti). Un parco naturale è costituito da numerosi punti di interesse collegati da sentieri. Il parco è rappresentato mediante un grafo non orientato , in cui ogni vertice rappresenta un punto di interesse e ogni arco rappresenta un sentiero percorribile. Un'escursione può visitare tutti e soli i punti di interesse appartenenti alla stessa componente connessa del grafo. Progettare un algoritmo planExcursions(G) che restituisca: il numero minimo di escursioni necessarie per visitare tutti i punti di interesse; il numero di punti di interesse visitabili durante l'escursione più lunga. Si assuma di avere già a disposizione gli algoritmi di visita DFS e BFS.
(a) Descrivere a parole la strategia adottata, indicando quale algoritmo di visita si intende utilizzare e quali eventuali modifiche di tale algoritmo è necessario apportare. (b) Scrivere lo pseudocodice dell'algoritmo. Non è necessario riportare lo pseudocodice completo di DFS o BFS; è sufficiente richiamare l'algoritmo scelto e descrivere in dettaglio le eventuali modifiche necessarie. (c) Analizzare la complessità al caso pessimo, giustificando il risultato anche in relazione ai dettagli implementativi del grafo.
(a) Strategia
Ogni escursione copre una componente connessa (vedi Grafi - definizioni e proprietàGrafo G=(V,E) diretto e non diretto, grafo semplice e pesato; incidenza, adiacenza, grado; cammini, cicli, sottografi, grafi connessi e componenti connesse; alberi liberi, foreste, spanning tree e spanning forest; proprietà con dimostrazioni (somma dei gradi = 2m, m <= n(n-1)/2, alberi m = n-1, connessi m >= n-1, foreste m <= n-1).Grafi - definizioni e proprietà →). Quindi:
- il numero minimo di escursioni è il numero di componenti connesse (non se ne possono fare meno: due vertici di componenti diverse non stanno nella stessa escursione; e con una escursione per componente si coprono tutti);
- il numero di punti nell'escursione più lunga è la dimensione della componente più grande.
Si usa la BFS (la DFS va ugualmente bene, vedi Visite di grafi - BFS e DFSVisite in ampiezza (BFS) e in profondità (DFS) come design pattern; etichette discovery, cross e back edge; BFS tree e distanze; complessità Theta(n+m) con liste di adiacenza; applicazioni (connettività, componenti, spanning tree, cammini minimi non pesati, cicli, vertici a distanza al più d); esempio svolto su un grafo di 7 vertici.Visite di grafi - BFS e DFS →). Si scorrono i vertici: appena se ne trova uno non visitato (.ID ) si è trovata una nuova componente e si lancia la visita da lì. Modifica: la visita conta i vertici che scopre, con un contatore count azzerato a ogni visita; ogni vertice scoperto riceve come ID il valore corrente del contatore (positivo: serve solo a distinguere "visitato" da "non visitato", e a ricordare l'ordine di scoperta). Il valore finale di count è la dimensione della componente appena esplorata. La BFS modificata, BFS-COUNT(G, v), restituisce questo valore.
Esempio: punti, sentieri , , , (il punto è isolato). Si lancia la BFS da : scopre e restituisce ; si passa a (non visitato): scopre e restituisce ; è non visitato: restituisce . Risultato: escursioni, la più lunga con punti.
(b) Pseudocodice
Algoritmo planExcursions(G)
Input: grafo non orientato G=(V,E) con liste di adiacenza; per ogni v, v.ID = 0 (non visitato)
Output: (numExcursions, maxSize): numero di componenti connesse e dimensione della maggiore
numExcursions <- 0; maxSize <- 0
forall v in V do
if v.ID = 0 then
numExcursions <- numExcursions + 1
size <- BFS-COUNT(G, v)
if size > maxSize then maxSize <- size
return (numExcursions, maxSize)
Algoritmo BFS-COUNT(G, s) (BFS di G, s con una modifica)
count <- 1; s.ID <- count; L0 <- lista con s
mentre la lista di livello corrente non è vuota: per ogni vertice v del livello
per ogni arco e incidente su v: sia w il vertice opposto
se w.ID = 0 allora count <- count + 1; w.ID <- count; w va nel livello successivo
return count(Se non è vuoto, maxSize .) Il contatore è azzerato a ogni chiamata, quindi gli identificatori sono progressivi all'interno della singola visita: non serve che siano distinti tra componenti diverse.
(c) Complessità
Ogni vertice viene scoperto, e inserito nelle liste della BFS, una sola volta in tutte le chiamate (dopo la scoperta ha ID , quindi non genera altre visite). Con le liste di adiacenza di una visita si scorre per ogni vertice la sua lista, e ogni arco compare in due liste (una per estremo, grafo non orientato): ogni arco è esaminato due volte. Il ciclo esterno sui vertici costa . In totale:
Se il grafo fosse rappresentato con matrice di adiacenza scorrere i vicini di un vertice costerebbe e il totale salirebbe a ; con la sola lista degli archi ogni visita costerebbe per vertice, quindi (vedi Rappresentazione dei grafiStrutture di base (lista dei vertici LV, lista degli archi LE), liste di adiacenza, matrice di adiacenza; operazioni incidentEdges, opposite, areAdjacent; spazio e tempi a confronto; esempio con un grafo di 5 vertici.Rappresentazione dei grafi →). Lo stesso conteggio vale se si usa la DFS al posto della BFS. (Controllato con una implementazione confrontata con un calcolo diretto delle componenti su 300 grafi casuali.)
Errori comuni
- Lanciare la BFS da ogni vertice senza controllare
ID: ogni componente verrebbe visitata più volte, con costo al caso pessimo. - Non azzerare il contatore tra una visita e l'altra: la dimensione della componente risulterebbe cumulativa.
- Contare le escursioni come il numero di vertici o di archi, o restituire solo una delle due informazioni.
- Dimenticare di specificare (come chiede il testo) il collegamento tra la complessità e la rappresentazione del grafo.
Versione ripasso
Testo. Grafo non orientato di punti di interesse e sentieri; un'escursione = una componente connessa. planExcursions(G) restituisce il numero minimo di escursioni e la dimensione della più grande; strategia, pseudocodice, complessità.
- Strategia (vedi Visite di grafi - BFS e DFSVisite in ampiezza (BFS) e in profondità (DFS) come design pattern; etichette discovery, cross e back edge; BFS tree e distanze; complessità Theta(n+m) con liste di adiacenza; applicazioni (connettività, componenti, spanning tree, cammini minimi non pesati, cicli, vertici a distanza al più d); esempio svolto su un grafo di 7 vertici.Visite di grafi - BFS e DFS →): escursioni = componenti connesse; più lunga = componente maggiore; BFS lanciata da ogni vertice con
ID = 0, con un contatorecountper visita (w.ID <- ++count), restituito come dimensione. - Pseudocodice: per ogni con
ID = 0:numExcursions++,size <- BFS-COUNT(G, v),maxSize <- max(maxSize, size); restituisce(numExcursions, maxSize). - Esempio: archi , vertice isolato ⇒ escursioni, la più lunga con punti.
- Complessità: ogni vertice scoperto una volta, ogni arco due volte (liste di adiacenza): ; con matrice (vedi Rappresentazione dei grafiStrutture di base (lista dei vertici LV, lista degli archi LE), liste di adiacenza, matrice di adiacenza; operazioni incidentEdges, opposite, areAdjacent; spazio e tempi a confronto; esempio con un grafo di 5 vertici.Rappresentazione dei grafi →).
- Pseudocodice:
numExcursions <- 0; maxSize <- 0; per ogni conID = 0:numExcursions++,size <- BFS-COUNT(G, v),maxSize <- max(maxSize, size);BFS-COUNT:count <- 1; s.ID <- count; ogni vertice nuovo ottiene++count; restituiscecount. - Dettaglio:
IDpositivo basta a distinguere visitato/non visitato; non serve distinguere le componenti. - Errori: BFS senza controllo di
ID; contatore non azzerato; una sola delle due informazioni.