In un file di tipo testo è descritto, in modo ridondante, un grafo non
Transcript
In un file di tipo testo è descritto, in modo ridondante, un grafo non
GRAFO In un file di tipo testo è descritto, in modo ridondante, un grafo non orientato come quello mostrato nell’esempio in figura, secondo il seguente formato: nodo-1 nodo_adiacente-11 ... nodo_adiacente-1j nodo-2 nodo_adiacente-21 ... nodo_adiacente-2k ................... nodo-m nodo_adiacente-m ... nodo_adiacente-mn A B C E D 1 I nodi sono identificati dalle lettere dell’alfabeto. Per il grafo in esempio, il file conterrebbe le seguenti linee: ABDE BACD CBD DABCE EAD 2 Realizzare il diagramma di flusso di un programma che, dopo aver letto il file di descrizione di un grafo, richieda il nome di un nodo d’inizio e di un nodo di fine e produca in output tutti i possibili percorsi dal primo al secondo nodo, senza ripetizioni e non ripassando due volte per lo stesso nodo. 3 Per il grafo in esempio, tutti i percorsi dal nodo B al nodo E sono i seguenti: BDE-BDAE-BCDAE-BCDEBADE-BAE Soluzione: La base dati per mantenere la descrizione del grafo sarà un vettore di record, detto adiacenze. In ciascun record c’è: un campo, num_adiac, che contiene il numero di nodi adiacenti al primo, 4 un campo vettore di caratteri, vett_adiac, che memorizza ciascun nodo esistente e il nome dei nodi adiacenti. La dimensione di questo vettore di record può essere limitato a 26, quante sono le lettere dell’alfabeto. Per cercare una soluzione del problema proposto, faccio riferimento all’esempio riportato nel testo. 5 Immagino di costruire un percorso inserendo in successione gli identificatori dei nodi nel campo vett_nodi di un record di supporto che chiamo percorso. Un campo num_nodi dello stesso record conterrà il numero di nodi attualmente presente. Il primo nodo è necessariamente quello fornito in input come nodo di partenza. 6 Per scegliere il secondo, devo tener conto di tutti i nodi connessi al primo. Per ciascuno dei nodi in seconda posizione, poi, devo considerare tutti i nodi ad essi connessi, e così via. Mi accorgo che per costruire i percorsi non è sufficiente il record di supporto, poiché devo memorizzare tutte le scelte per la seconda posizione e, per ciascuna di queste, tutte le scelte per la terza e così avanti. 7 Ricorro quindi ad una struttura di supporto più complessa, lo stack, in cui memorizzare i percorsi parziali man mano che vengono creati. Per uno stack sono definite le funzioni: push(record): memorizza record in cima allo stack; pop(record): estrae l’elemento in cima allo stack e lo pone in record; 8 stack_vuoto: fornisce il valore VERO se lo stack è vuoto. Procedo così. All’inizio lo stack è vuoto. Pongo nel campo vett_nodi del record percorso, in prima posizione, il nodo di partenza, B, e metto 1 nel campo num_nodi. Inserisco il record nello stack, che si presenta così 1B 9 Ora entro in un ciclo in cui: 1) estraggo un elemento dallo stack ponendolo in percorso; 1B 2) cerco la posizione dell’ultimo elemento del campo vett_nodi (B nel nostro caso) nella matrice adiacenze; 3) per ognuno dei nodi adiacenti 3.1) lo aggiungo nel campo vett_nodi, aggiustando il campo num_nodi 10 3.2) inserisco il record nello stack Dopo queste operazioni, lo stack si presenta così (immaginando che lo stack cresca verso il basso): 2 BA 2 BC 2 BD Se immagino di eseguire di nuovo il passo 1), in percorso avrò: 2 BD 11 e, dopo aver eseguito tutti i passi successivi, nello stack avrò: 2 2 3 3 3 3 BA BC B DA B DB B DC B DE Sono così in grado di costruire tutti i percorsi possibili. Il procedimento è da raffinare. 12 Dopo aver aggiunto un nodo adiacente nel campo vett_nodi di percorso, devo verificare che questo non sia il nodo finale che voglio raggiungere. Se accade questo caso, non inserirò il record nello stack, ma piuttosto segnalerò il successo e visualizzerò il percorso. Se non si tratta di un nodo finale, dovrò verificare che il nodo che ho appena inserito non sia già presente: 13 in questo caso si tratterebbe di un percorso chiuso (ciclo) che devo escludere, evitando di inserire percorso nello stack. Il procedimento prosegue finché lo stack non si svuota. 14 Base dati: percorso: record composto dai seguenti campi: num_nodi: intero; vett_nodi: vettore di caratteri lungo 26; new_percorso: come percorso; 15 adiacenze: vettore di record composti dai seguenti campi: num_adiac: intero; vett_adiac: vettore di caratteri lungo 26. 16 Algoritmo: 1 Leggo_adiacenze 2 Inizializzo_stack 3 Leggo da tastiera nodo_iniziale e nodo_finale 4 Pongo nodo_iniziale nel campo vett_nodi di percorso, in prima posizione 5 Pongo 1 nel campo num_nodi di percorso 17 6 Inserisco percorso nello stack /* uso push(percorso) */ 7 Finché stack_vuoto dà FALSO 7.1estraggo un elemento dallo stack e lo pongo in percorso pop(percorso) */ 18 /* uso 7.2cerco in adiacenze il nodo contenuto nel campo vett_nodi nella posizione data da num_nodi; utilizzo la funzione cerca_nodo, che restituisce l’indice nel vettore, posiz_nodo 7.3con indice_adiac che va da 1 al valore contenuto nel campo num_adiac dell’elemento di adiacenze indicato da posiz_nodo 19 7.3.1pongo in nodo_adiacente il nome del nodo che si trova in adiacenze[posiz_nodo], nel campo vett_adiac[indice_adiac] 7.3.2copio percorso in new_percorso 7.3.3memorizzo nodo_adiacente in new_percorso, incrementando num_nodi 7.3.4se nodo_adiacente do_finale 20 = no- 7.3.4.1stampo “Trovato percorso” 7.3.4.2stampo_nodi 7.3.5altrimenti 21 7.3.5.1se cerca_circuiti fornisce FALSO 7.3.5.1.1memorizzo new_percorso nello stack /* uso push(new_percorso) */ 8 Fine. 22 Funzione leggo_adiacenze 1 Apro il file file_in in lettura 2 Pongo posiz_adiac a 0 3 Pongo indice_adiac a 0 4 Finché (carat = getc(file_in)) è da EOF 4.1se carat = new_line 4.1.1pongo indice_adiac nel campo num_adiac di ze[posiz_adiac] 23 adiacen- 4.1.2pongo indice_adiac a 0 4.1.3incremento posiz_adiac 4.2altrimenti 4.2.1pongo carat in nel campo vett_adiac[indice_adiac] di adiacenze[posiz_adiac] 4.2.2incremento indice_adiac 5 Chiudo file_in 6 Memorizzo posiz_adiac in totale_nodi 7 Fine. 24 Nell’ipotesi che la descrizione del grafo sia coerente e completa, la variabile totale_nodi è ridontante e serve solo per debug. Funzione cerca_nodo(nodo_da_cercare, posiz_nodo) 1 Pongo posiz_nodo a 0 2 Pongo trovato a FALSO 25 3 Finché trovato è FALSO 3.1se il confronto tra nodo_da_cercare e l’elemento vett_adiac[0] di adiacenze[posiz_nodo] dà VERO 3.1.1pongo trovato a VERO 3.2altrimenti 3.2.1incremento posiz_nodo 4 Fine. 26 Funzione stampo_nodi 1 Con indice che va da 0 al valore del campo num_nodi di percorso 1.1stampo il campo vett_nodi[indice] di percorso 1.2se indice < num_nodi - 2 /* esclu- do il trattino dopo l’ultimo nodo */ 1.2.1stampo ‘-’ 2 Fine. 27 Funzione cerca_circuiti(new_percorso) 1 Pongo trovato a FALSO 2 Pongo in num_elementi il campo num_nodi di new_percorso 3 Pongo indice a 0 4 Finché (trovato è FALSO) e insieme (indice < (num_elementi - 1)) 4.1se l’elemento vettore[indice] di new_percorso è uguale all’elemento vettore[num_elementi - 1] 28 4.1.1pongo trovato a VERO 4.2altrimenti 4.2.1incremento indice 5 Restituisco trovato 6 Fine. 29 Mi posso occupare ora della realizzazione dello stack. Ho due possibilità: una lista concatenata (allocazione dinamica della memoria) oppure un vettore (allocazione statica). La prima soluzione è generalmente da preferire perché pone vincoli meno stringenti alla dimensione massima che può assumere la struttura. 30 La seconda invece, richiedendo la definizione statica di un vettore, pone limiti più pesanti ed inoltre blocca l’uso di una grossa porzione di memoria. Tuttavia scelgo questa seconda soluzione, poiché è di facile realizzazione e permette un accesso più veloce (non richiede di allocare dinamicamente porzioni di memoria, ma semplicemente di modificare un indice!). 31 Lo stack sarà costituito da un vettore di record, detto vett_stack, e da un indice, indice_stack, il quale punta sempre al primo elemeto libero (punta cioè oltre la “cima” dello stack). Pertanto deve essere inizializzato con la prima posizione del vettore, quella con indice 0. La base dati che costituisce lo stack deve essere definita come globale. 32 Base dati: vett_stack: vettore di record composti dai seguenti campi: num_nodi: intero; vett_nodi: vettore di caratteri lungo 26; indice_stack: intero; Algoritmo: Funzione inizializzo_stack 1 Pongo indice_stack a 0 2 Fine. 33 Funzione push(record) 1 Copio record vett_stack[indice_stack] 2 Incremento indice_stack 3 Fine. 34 in Funzione pop(record) 1 Decremento indice_stack 2 Copio vett_stack[indice_stack] in record 3 Fine. 35 Funzione stack_vuoto 1.Pongo in flag_vuoto il valore dell’espressione relazionale indice_stack = 0 2.Restituisco flag_vuoto 3.Fine. 36 Note: la funzione pop non è protetta: deve essere eseguita solo dopo il test che lo stack non sia vuoto! La funzione push non controlla che ci sia memoria disponibile per l'inserimento! Per gestire correttamente queste (e altre eventuali) situazioni d'errore occorrerebbe complicare notevolmente l'algoritmo. 37