Strutture dati Strutture dati Tipi di dato astratti (strutture dati dinamiche) q Strutture dati elementari n n n q Alberi n Organizzazione sistematica dei dati e del loro accesso n n n q Tipo di dato astratto r Tipo di dato astratto o ADT (Abstract Data Type): insieme di oggetti e insieme di operazioni definite su di esso r Es.: lista con operazioni di inserimento e cancellazione r Attenzione: l’ADT specifica cosa fa ogni operazione, non come r In Java: o Rappresentazione con interfaccia o Implementazione con classe Tipo di dato Lista n n n Insieme di elementi tra i quali è definito un ordinamento totale. Numerose varianti Ammette (almeno) le operazioni seguenti: q q q q n cons(elem): inserisce elem in testa alla lista cdr(): elimina l’ elemento in testa alla lista car(): restituisce l’ elemento in testa alla lista senza eliminarlo null(): verifica se la lista e’ vuota Nelle implementazioni (es. Java) sono spesso presenti altre operazioni q Liste Stack (pile) Code Alberi binari Alberi di ricerca (BST) Alberi bilanciati (AVL) B-tree (gestione memoria secondaria) Grafi Strutture dati Tipi di dato astratti (strutture dati dinamiche) q Strutture dati elementari n n n q Alberi n n n n q Liste Stack (pile) Code Alberi binari Alberi di ricerca (BST) Alberi bilanciati (AVL) B-tree (gestione memoria secondaria) Grafi Liste n n n si usa quando è necessario scandire un insieme di oggetti in modo sequenziale è vantaggiosa quando sono previste frequenti operazioni di cancellazione o inserzioni lo svantaggio sta nel fatto che si può accedere ad un elemento di posizione i solo dopo aver acceduto a tutti gli i-1 elementi precedenti cons(elem, i), remove(i), get(i) 1 Implementazione delle liste Implementazione con array r Occorre conoscere la dimensione max della lista r Può portare a spreco di memoria Array n q Si usa un array in memoria Strutture collegate n q Ogni elemento contiene un riferimento al successivo Costo delle principali operazioni: n q q q A0 A1 A2 AN-3 AN-2 AN-1 cons: O(n) (caso peggiore: elemento in prima posizione) cdr: O(n), (caso peggiore: primo elemento) car: O(1) Elemento non usato Inserimento in pos. 2 Altri tipi di lista Implementazione con strutture collegate n Efficienza q q n cons, remove: O(i) (bisogna trovare la posizione dell’elemento da inserire/rimuovere). O(1) per inserimenti/cancellazioni in prima posizione get: O(i) (posizione dell’elemento cercato) A0 r A0 A1 Ai Lista doppia: consente una scansione in entrambe le direzioni AN A1 Ai AN Lista circolare: consente di rappresentare strutture in cui l’ordinamento è mod N A0 A1 Ai AN Inserimento in pos. 1 Strutture dati Tipi di dato astratti (strutture dati dinamiche) q Strutture dati elementari n n n q n Lista nella quale inserimenti e cancellazioni avvengono solo in testa (disciplina LIFO). n Operazioni sempre presenti q Liste Stack (pile) Code n n n q q Alberi binari Alberi di ricerca (BST) Alberi bilanciati (AVL) B-tree (gestione memoria secondaria) Grafi push(el): inserisce l'elemento specificato da el in cima alla pila Alberi n q Tipo astratto Pila pop(): elimina l'elemento in cima alla pila top(): restituisce l'elemento in cima alla pila senza cancellarlo dalla lista q n isEmpty(): verifica se la pila è vuota Altre operazioni q clear(): elimina tutti gli elementi dalla pila 2 Implementazione del tipo Pila Strutture dati Tipi di dato astratti (strutture dati dinamiche) r Realizzazione tramite Array q Ai Strutture dati elementari n top = i n A1 A0 n q Alberi n r Liste: realizzazione tramite lista concatenata n n top n Start q A0 A1 Ai Liste Stack (pile) Code Alberi binari Alberi di ricerca (BST) Alberi bilanciati (AVL) B-tree (gestione memoria secondaria) Grafi AN Tipo astratto coda Implementazione di code Con array n n n Lista nella quale gli inserimenti avvengono in coda e le cancellazioni (estrazioni) in testa (disciplina FIFO) Operazioni sempre presenti q isEmpty(): verifica se la coda è vuota q enqueue(el): inserisce l'elemento specificato da el alla fine della coda q dequeue(): elimina il primo elemento della coda q firstEl(): restituisce il primo elemento della coda senza eliminarlo Altre operazioni clear(): elimina tutti gli elementi dalla coda q A0 A1 A2 AN-3 AN-2 AN-1 testa coda Elemento non usato enqueue -> coda = coda + 1 (mod N) dequeue -> testa = testa + 1 (mod N) Se (coda == testa – 1 mod N) coda piena Se (coda == testa) coda vuota (un solo elemento presente) Con liste concatenate Strutture dati Tipi di dato astratti (strutture dati dinamiche) q Strutture dati elementari n n n q Liste Stack (pile) Code Alberi n n n n q alberi Alberi binari Alberi di ricerca (BST) Alberi bilanciati (AVL) B-tree (gestione memoria secondaria) definizione, algoritmi di visita, alberi binari Grafi 3 Tipo di dato astratto albero n n Terminologia insieme vuoto di nodi oppure costituito da una radice R e da 0 o più alberi (sottoalberi) la radice di ogni sottoalbero è collegata a R da un arco n genitore, figlio, fratello n nodo q q R q foglia se ha zero figli interno se ha almeno un figlio es.: radice R con n sottoalberi T1 T2 n n livello radice = 1 lunghezza (n.ro nodi) percorso radice-nodo n percorso radice-foglia n altezza = 3 n n Rappresentazione di alberi con liste collegate Primo figlio di R B q q C Fratello di A operazioni più importanti q R q Elemento continente, nazione, regione, provincia ecc (alcuni) organigrammi file system domini Internet Operazioni sugli alberi n livello di C = 3 ad es., in Java classificazioni di specie animali organizzazione del territorio q lunghezza (n.ro nodi) ramo più lungo A alberi genealogici gerarchie di ereditarietà q altezza albero q n n ramo q n foglia Esempi di alberi livello di un nodo q nodo interno Tn Terminologia n radice concetti intuitivi q q element(v): restituisce l’elemento memorizzato nel nodo v root(): restituisce la radice dell’albero parent(v): restituisce il genitore del nodo v children(v): restituisce i figli di v isLeaf(v): restituisce true se v è una foglia isRoot(v): restituisce true se v è la radice Riferimento al prossimo fratello Riferimento al primo figlio 4 Operazioni sugli alberi n Algoritmi su alberi Altre operazioni q livello di un nodo altezza dell’albero # nodi # foglie q max # di figli di un nodo dell’albero q q q q n q in profondità (depth-first search, a scandaglio): DFS q in ampiezza (breadth-first search, a ventaglio): BFS vengono visitati i rami, uno dopo l’altro n isEmpty n visita (o attraversamento) di un albero true se l’albero ha zero nodi a livelli, partendo dalla radice n visite in profondità/preordine n in preordine (preorder, o ordine anticipato) q dato un nodo v n n n Visite in profondità visita v visita i sotto-alberi aventi come radice i figli di v, da sinistra verso destra 2 1 in ordine simmetrico (inorder) dato un nodo v con k sotto-alberi 7 q n n n n 2 visita il primo sotto-albero (radice v.firstChild) visita v visita gli altri k -1 sotto-alberi 3 4 in postordine (postorder, o ordine posticipato) q dato un nodo v n n 6 7 inordine 3 2 6 4 5 postordine n strutture dati per alberi binari q 5 5 1 un albero si dice binario se ogni nodo può avere al più 2 figli q 4 3 6 n q 3 7 4 Alberi binari 1 2 6 1 preordine visita i sotto-alberi aventi come radice i figli di v, da sinistra verso destra visita v visita in ampiezza (usa una coda) 5 la rappresentazione si semplifica vettori rappresentazioni collegate 7 BFS 5 uso di vettori ogni nodo v è memorizzato in posizione p(v) n q q q se v è la radice allora p(v)=1 (indice 0 in Java, C, C++) se v è il figlio sinistro di u allora p(v)=2p(u) se v è il figlio destro di u allora p(v)=2p(u)+1 1 2 3 4 6 7 - 1 3 2 6 4 7 n Binary Tipo di dato astratto che supporta le seguenti operazioni: q q q n albero binario che soddisfa la seguente proprietà search insert delete (remove) Ø per ogni nodo, tutte le chiavi nel suo sottoalbero sinistro sono ≤ della chiave v associata al nodo e tutti le chiavi nel suo sottoalbero destro sono ≥ di v le liste possono essere usate come dizionari n n q le tre operazioni hanno costo lineare (sia nel worst case sia in quello average) in molti casi un costo lineare è giudicato inaccettabile q strutture più efficienti? n q n n alberi di ricerca bilanciati tavole hash 49 17 49 82 57 20 22 88 ok 17 94 91 82 47 20 utilizzabile quando le chiavi appartengono a un universo totalmente ordinato Una visita simmetrica (inorder) dell’albero da’ le chiavi dell’albero in modo non descrescente ipotesi semplificativa di lavoro: chiavi strettamente minori nei sottoalberi sinistri e strettamente maggiori nei sotto alberi destri Costi delle operazioni nei BST: ricerca di un elemento albero binario di ricerca 22 Search Tree Albero binario di ricerca Dizionari n Alberi binari di ricerca (BST) 88 errato! 94 search(tree T, int x) { v = root(T ) while (v != null) { if (x == key(v)) return v; else if (x < key(v)) v = left child (v); else v = right child (v); } return null; } 91 ricorda la ricerca binaria! 6 costo della ricerca in un BST BST di n nodi caso peggiore 49 n O(n) q n n caso migliore Ø costo dell'inserimento in un BST q q 56 dipende dalla distribuzione 54 67 O(1) (poco interessante) q n n 21 52 caso medio q Costi delle operazioni nei BST q nel caso di distribuzione uniforme delle chiavi il 77 valore atteso dell'altezza dell'albero è O(log n) 75 83 Ø istanze problematiche: alberi molto profondi e sbilanciati... ogni inserimento introduce una nuova foglia il costo è (proporzionale a) la lunghezza del ramo radice-foglia nel caso peggiore O(n ) istanze problematiche nei BST : alberi molto profondi e sbilanciati... Introduzione al bilanciamento Alberi bilanciati n nozione intuitiva di bilanciamento q q n nil costo delle varie operazioni su BST è O(h), dove h = altezza dell’albero npuò essere O(n) worst case!!! nse l’albero fosse bilanciato… nma poi dobbiamo mantenerlo tale quando inseriamo o cancelliamo elementi! Bilanciamento perfetto 34 n 21 16 6 63 30 43 18 28 32 37 52 72 tutti i rami di un albero hanno approssimativamente la stessa lunghezza ciascun nodo interno ha “molti” figli caso ideale per un albero k-ario q q ciascun nodo ha 0 o k figli la lunghezza di due rami qualsiasi differisce di al più una unità bilanciamento perfetto un albero binario perfettamente bilanciato di n nodi ha altezza n lg2 n + 1 se ogni nodo ha 0 o 2 figli le foglie sono circa il 50% dei nodi (interni +foglie) Il caso binario e’ facilmente generalizzabile ad alberi di parità k q n n nf = (k − 1)ni + 1 ⇒ nf = (k − 1)n + 1 k costo di ricerca/inserimento/eliminazione O(log n) ripetuti inserimenti/eliminazioni possono distruggere il bilanciamento q degrado delle prestazioni 7 bilanciamento in altezza Fattore di bilanciamento un albero è bilanciato in altezza se le altezze dei sottoalberi sinistro e destro di ogni nodo differiscono di al più un’unità gli alberi bilanciati in altezza sono detti alberi AVL n n q da Adel’son-Vel’skii & Landis, primi proponenti n Alberi di fibonacci n q alberi AVL col minimo numero di nodi (fissata l’altezza) 34 21 16 3 63 30 43 72 18 28 32 37 52 29 un albero AVL è quindi un albero binario di ricerca in cui ad ogni nodo v viene associato un valore detto fattore di bilanciamento che corrisponde alla differenza tra la profondita’ del sottoalbero sinistro e quello del sottoalbero destro di v Il fattore di bilanciamento puo’ assumere solo I valori -1,0, 1 Bilanciamento fattore di bilanciamento 6 n fattore di bilanciamento (FDB): altezza sottoalbero dx – altezza sottoalbero sx 78 57 n +1 -1 Mantenere la proprieta’ di bilanciamento durante inseriementi e cancellazioni garantisce che l’operazione di ricerca abbia sempre costo logaritmico e quindi sia molto efficiente 0 in un albero bilanciato in altezza |FDB| ≤ 1, per ogni nodo inserimento in AVL Rotazioni n 1. inserire nuovo nodo come in un BST 2. ricalcolare i fattori di bilanciamento che sono mutati in seguito all’inserimento il nuovo nodo diviene una foglia L’idea alla base delle rotazioni e’ quella di portare l’elemento centrale alla radice dell’albero e di far discendere l’elemento che causa lo sbilanciamento solo nel ramo interessato all’inserimento (gli altri fattori non possono mutare), dal basso verso l’alto 3. se nel ramo appare un fattore di bilanciamento pari a ±2 occorre ribilanciare tramite “rotazioni” 8 rotazione rotazioni negli AVL verso sinistra o verso destra casi possibili verso destra q a b q T1 T3 T1 q a b T2 T2 T3 q verso sinistra DD: inserimento nel sottoalbero destro di un figlio destro (del nodo che si sbilancia) SD: inserimento nel sottoalbero sinistro di un figlio destro (del nodo che si sbilancia) DS: inserimento nel sottoalbero destro di un figlio sinistro (del nodo che si sbilancia) SS: inserimento nel sottoalbero sinistro di un figlio sinistro (del nodo che si sbilancia) la proprietà di ricerca è mantenuta rotazione semplice (caso DD) Costo delle operazioni in AVL n n Si dimostra che le rotazioni che si devono eseguire in seguito ad inseriemnti e/o cancellazioni non alterano il costo di queste operazioni che e’ sempre uguale ad O(logn) come per la ricerca di un elemento. gli antenati di P non sono interessati all’inserimento perché in seguito alla rotazione recuperano il loro fattore di bilanciamento precedente Coda con priorità Heap e code di priorita’ n n Una coda con priorità è una struttura dati dinamica che permette di gestire una collezione di dati con chiave numerica. Una coda con priorità offre le operazioni di inserimento: di un elemento nell’insieme massimo: restituisce l’elemento con chiave più grande q cancellazione-massimo: restituisce l’elemento con chiave più grande e lo rimuove dalla collezione q q 9 Applicazioni della Coda con Priorità n n Le Code con priorità sono strutture dati molto comuni in informatica. Es: q n Gestione di processi: ad ogni processo viene associata una priorità. Una coda con priorità permette di conoscere in ogni istante il processo con priorità maggiore. In qualsiasi momento i processi possono essere eliminati dalla coda o nuovi processi con priorità arbitraria possono essere inseriti nella coda. n heap = catasta condizione di heap albero binario perfettamente bilanciato ogni nodo contiene una chiave maggiore o eguale di quelle presenti negli eventuali figli 1. 2. n non memorizza un ordinamento totale le visite in ampiezza e in pre- in- post-ordine non forniscono un ordinamento delle chiavi q rappresentazione tramite array Si implementa l’albero tramite un vettore q q q q q q q Uno heap A ha un attributo heap-size[A] che specifica il numero di elementi contenuto nello heap nessun elemento in A[1,length[A]] dopo heap-size[A] è un elemento valido dello heap La radice dell’albero è A[1] L’indice del padre di un nodo di posizione i è i/2 L’indice del figlio sinistro di un nodo i è 2 i L’indice del figlio destro di un nodo i è 2 i +1 Implemenatzione particolarmente efficiente n n n Per implementare una coda con priorità utilizzeremo una struttura dati chiamata heap Heap n heap n ogni nodo v è memorizzato in posizione p(v) q q q se v è la radice allora p(v)=0 se v è il figlio sinistro di u allora p(v)=2p(u)+1 se v è il figlio destro di u allora p(v)=2p(u)+2 89 67 66 Tempo di ricerca/ordinamenyto in struttura di heap = O(logn) 1 Liste collegate 68 65 43 21 5 66 67 4 64 89 67 68 66 65 66 67 1 43 21 5 4 64 0 1 2 3 4 5 6 7 8 9 10 11 12 maggio 2002 Coda con priorità con heap n Risulta semplice implementare le varie operazioni di una coda con priorità utilizzando uno heap q q q Extract Max: basta restituire la radice dello heap Heap Extract Max: dopo la restituzione dell’elemento massimo, posiziona l’ultimo elemento dello heap nella radice ed ripristina la proprietà di ordinamento parziale Heap Insert: la procedura inserisce il nuovo elemento come elemento successivo all’ultimo e lo fa salire fino alla posizione giusta facendo “scendere” tutti padri B-alberi dizionari in memoria secondaria 10 B-Alberi n n n B-Alberi I B-Alberi sono una generalizzazione degli alberi binari di ricerca la principale differenza è che nei B-Alberi ogni nodo dell’albero può contenere n>2 chiavi i B-Alberi sono utilizzati per garantire l’efficienza delle operazioni su insiemi dinamici (ricerca, inserzione e cancellazione) di dati memorizzati su supporti secondari (dischi) In ogni istante è possibile mantenere in memoria primaria solo un numero limitato di pagine della memoria secondaria le operazioni eseguite sui B-Alberi garantiscono di poter essere eseguite conservando solo un numero costante di pagine in memoria principale (tante più pagine tanto più efficienti saranno le varie operazioni) in genere un nodo di un B-Albero e’ tanto grande quanto una pagina di memoria secondaria n n n Esempio n=3 Visualizzazione 127 14 M DH BC FG 128 145 QTX JKL NP RS VW YZ n n Dunque, K ´1 < K ´ 2 < … < K ´ n-2 < K ´n- 1 n n K1 P1 ... Ki Pi Ki+1 222 245 320 521 353 354 690 487 Notare che le foglie sono ordinate per chiave, da sinistra a destra n Per inserire la chiave v, si cerca la foglia dove v dovrebbe trovarsi: se c’e’ spazio, si inserisce Se no, si spezza la foglia in due, e si modifica il padre per prevedere i puntatori alle due foglie n 19 X 12 . . . Kn-1 Pn -- 14 17 15 19 X K´1 P´1 . . . 352 Inserzione Ogni nodo e' un sequence di coppie [puntatore, chiave] K1 < K 2 < … < Kn-2 < Kn- 1 P1 punta a un nodo contenente chiavi < K1 Pi punta a un nodo contenente chiavi in intervallo [Ki-1, Ki) Pn punta a un nodo contenente chiavi > Kn-1 n 189 221 n B-albero: esempio n 83 496 K´i P´i K´i+1 . . . K´n-1 P´n 12 14 15 17 Per inserire la chiave 15 • si spezza la foglia • nel padre, [0, 19) diventa [0, 15) e [15, 19) • se il padre e’ pieno, bisogna spezzarlo (in modo simile) • l’albero resta automaticamente bilanciato 11 Ricerca n n E’ un operazione simile alla ricerca sugli alberi binari di ricerca Con la differenza è che non ci sono solo due vie possibili ad ogni nodo Grafi Grafi Grafi n n Grafo diretto o orientato q n Insieme N di elementi detti nodi e insieme di archi. Un arco e’ una coppia ordinata (v,e) di nodi. n n Grafo non oreintato q Insieme N di elementi detti nodi e insieme di archi. Un arco e’ una coppia non ordinata (v,e) di nodi. n n Grafi Rappresentazione di un grafo n n n n Se due nodi V1 e V2 sono collegati da un arco si dice che V1 e’ adiacente a V2 Cammino di un grafo: la sequenza di nodi V1, V2,.. Vn tali che le coppie (V1,V2) ,(V2,V3),.. (Vn-1, Vn) sono adiacenti Lunghezza di un cammino: numeor di archi percorsi (o nuemro di nodi -1) I grafi sono strutture dati molto diffuse in informatica Vengono utilizzati per rappresentare reti e organizzazioni dati complesse e articolate Per elaborare i grafi in genere è necessario visitarne in modo ordinato i vertici Vedremo a questo proposito due modi fondamentali di visita: per ampiezza e per profondità Il tempo di esecuzione di un algoritmo su un grafo G=(V,E) viene dato in funzione del numero di vertici V e del numero di archi E Vi sono due modi per rappresentare un grafo: q q n n collezione di liste di adiacenza matrice di adiacenza si preferisce la rappresentazione tramite liste di adiacenza quando il grafo è sparso, cioè con E molto minore di V2 si preferisce la rappresentazione tramite matrice di adiacenza quando, al contrario, il grafo è denso o quando occorre alta efficienza nel rilevare se vi è un arco fra due vertici dati 12 Liste di adiacenza n n n n Si rappresenta un grafo G=(V,E) con un vettore Adj di liste, una lista per ogni vertice del grafo per ogni vertice u, Adj[u] contiene tutti i vertici v adiacenti a u, ovvero quei vertici v tali per cui esiste un arco (u,v)∈E in particolare questo insieme di vertici è memorizzato come una lista l’ordine dei vertici nella lista è arbitrario Rappresentazioni di grafi. Un grafo (a) rappresentato con una lista di adiacenze (b-c), Matrici di adiacenza n n Per la rappresentazione con matrici di adiacenza si assume che i vertici siano numerati in sequenza da 1 a V Si rappresenta un grafo G=(V,E) con una matrice A=(aij ) di dimensione VxV tale che: aij=1 se (i,j) ∈ E aij=0 altrimenti Rappresentazioni di grafi. Un grafo (a) rappresentato come una matrice di adiacenze (d) Visita di un Grafo Grafi pesati n n n In alcuni problemi si vuole poter associare una informazione (chiamata peso) ad ogni arco un grafo con archi con peso si dice grafo pesato si dice che esiste una funzione peso che associa ad un arco un valore Obiettivo: visitare una sola volta tutti i nodi del grafo. n q Es.: visitare un porzione del grafo del Web Difficoltà: n q q Presenza di cicli Presenza di nodi isolati w : E→ R n ovvero un arco (u,v) ha peso w(u,v) 13 Visita in profondità - DFS Implementazione della DFS n n n n n La visita procede da tutti gli archi uscenti da un nodo. Se tutti i nodi adiacenti sono stati visitati allora si torna al nodo “predecessore”. Una volta tornati al nodo di partenza si prosegue da un nodo qualsiasi non visitato. I nodi vengono rinumerati secondo l’ordine di visita. Visita in profondità - DFS n visita_profondità(G) q q n 1. Passo base se G = Ø f=> esci; 2. Passo di induzione visita il nodo G se non è stato visitato per ogni nodo adiacente G->adj visita_profondità(G->adj); n n n n I nodi sono inizialmente marcati con 0, i=0. Assumi la visita sia arrivata ad un nodo v. La visita prosegue da un nodo u adiacente a v se marcato 0. Se nessun nodo adiacente marcato 0 è disponibile torna al nodo da cui si è raggiunto v oppure termina se v è il nodo iniziale. Ogni volta che un nodo mai visitato è raggiunto, questo viene marcato con i++ Implementazione della DFS n n n L’implementazione iterativa usa una pila per memorizzare gli archi uscenti da un nodo visitato. Ad ogni passo si estrae l’arco (v,u) sulla cima della pila. La visita prosegue dal nodo adiacente u se marcato 0. Osservazione: la visita in profondità è strettamente legata alla politica di gestione di uno stack (come tutti gli algoritmiricorsivi); Esempio di applicazione dell’algoritmo depthFirstSearch ad un grafo L’algoritmo depthFirstSearch applicato ad un grafo orientato 14 Visita in profondità (C) void visita_profondita(struct nodo_grafo*nodo) { if (nodo == NULL) return; if (nodo->flag == 0) { printf ("%s\n", nodo->info); nodo->flag = 1; adj = nodo->l_adj; while(adj != NULL) { visita_profondita(adj->p_adj); adj = adj->next; } } return; } Visita in ampiezza - BFS n n n uso di una coda per memorizzare tutti gli archi incidenti nel nodo visitato I nodi vengono marcati. La visita quindi procede dall’arco (v,u) in testa alla coda. Visita in ampiezza - BFS n visita_ampiezza(G) per ogni nodo non visitato: q q q n si inserisce il nodo in una coda; si estrae dalla coda un nodo e si visita; si inseriscono in coda tutti gli elementi adiacenti al nodo corrente. Osservazione: la visita in ampiezza è strettamente legata alla politica di gestione di una coda; Un esempio di applicazione dell’algoritmo breadthFirstSearch ad un grafo Visita in ampiezza (C) Applicazione dell’algoritmo breadthFirstSearch ad un grafo orientato void visita_ampiezza(struct nodo_grafo *nodo) { struct punt_adj *adj; static struct p_coda *first_el; coda_ins (nodo); while(first_el != NULL) //finché ci sono elementi nella coda { nodo = coda_extr(); //Estrazione dalla coda di un nodo printf("nodo->info %c\n", nodo->info); //Visita del nodo nodo->flag = 1; adj = nodo->l_adj; while(adj != NULL) //Inserimento in coda dei nodi adiacenti { if (adj->nodo_adj->flag == 0) coda_ins (adj->p_adj); adj = adj->next; }}} 15