Liste, pile e code
In questa pagina 5
Un ADT (vedi Problemi computazionali e algoritmiProblema computazionale come insieme di coppie (istanza, soluzione); algoritmo e modello di calcolo RAM; pseudocodice; taglia di un'istanza; ADT e struttura dati concreta; esempio svolto con ricerca lineare e binaria in un array ordinato.Problemi computazionali e algoritmi →) specifica tipo dei dati, operazioni e parametri, non l'implementazione. In Java si descrive con un'interfaccia (solo costanti e metodi senza corpo); in C non esistono costrutti per gli ADT, si usano struct e funzioni (vedi Liste concatenate in CLista singolarmente concatenata in C con nodo struct e testa passata per riferimento (Nodo **); addHead, addTail ricorsiva e iterativa, pop, inversione in loco, liberazione; coda con puntatori a testa e coda; ricorsione sulle liste; costi.Liste concatenate in C →).
Lista
Collezione di elementi con un ordine lineare: primo, secondo, ..., ultimo. Si accede a un elemento in due modi.
Lista index-based
L'elemento si identifica con l'indice = numero di elementi che lo precedono.
| Metodo | Effetto |
|---|---|
size(), isEmpty() |
numero di elementi, lista vuota |
get(i) |
elemento in posizione , senza toglierlo |
set(i, e) |
sostituisce l'elemento in con e e restituisce il vecchio |
add(i, e) |
inserisce e all'indice spostando a destra i successivi |
remove(i) |
toglie e restituisce l'elemento in , spostando a sinistra i successivi |
Si realizza con un array: get, set, size in ; add e remove in per gli spostamenti. Se l'array è pieno si alloca un array di taglia doppia e si copiano gli elementi: il costo della copia è ammortizzato dai inserimenti precedenti, quindi l'inserimento in fondo costa ammortizzato.
Lista position-based
L'elemento si identifica con la posizione (Position<E>, con getElement()): il contenitore, o nodo, che lo ospita. Metodi: first(), last(), before(p), after(p), addFirst(e), addLast(e), addBefore(p, e), addAfter(p, e), remove(p), set(p, e).
Si realizza con una lista doppiamente concatenata: ogni nodo punta a predecessore e successore, e due nodi sentinella (senza elemento) delimitano inizio e fine, così nessun inserimento o rimozione ha casi particolari. Tutti i metodi costano , ma la lista si scandisce solo in sequenza (arrivare al nodo costa ).
| array (index-based) | doppia lista (position-based) | |
|---|---|---|
| accesso per indice | ||
| inserire/togliere dato un nodo | ||
| spazio | compatto | un nodo con due puntatori per elemento |
Si può realizzare una lista index-based con una doppia lista o una position-based con un array, ma con pochi vantaggi.
Pila (stack)
Inserimenti e rimozioni secondo LIFO (last in, first out): push(e) inserisce in cima, top() legge la cima senza togliere, pop() toglie e restituisce la cima, più size() e isEmpty(). Con un array e un indice t di cima, o con una lista concatenata con inserimento e rimozione in testa, tutti i metodi sono (con array estendibile, ammortizzato). Esempi: stack delle chiamate di una funzione, annulla/ripeti, controllo delle parentesi.
Coda (queue)
Secondo FIFO (first in, first out): enqueue(e) inserisce in fondo, first() legge il primo, dequeue() toglie e restituisce il primo. Con una lista concatenata con puntatori a testa e coda, tutto in . Con un array si usa un buffer circolare: indice f del primo e size; enqueue scrive in (f + size) mod N, dequeue avanza f = (f + 1) mod N. Usata dalla visita in ampiezza dei grafi (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 →).
La deque (double-ended queue) permette inserimento e rimozione sia in testa sia in coda: unisce pila e coda.
Iteratori
Un iteratore (Iterator<E>) esamina gli elementi di una collezione uno alla volta: hasNext() dice se c'è un prossimo elemento, next() lo restituisce. Una collezione iterabile (Iterable<E>) ha un metodo iterator() che ne restituisce uno. L'ordine di restituzione non è fissato dall'ADT ma dall'implementazione dell'iteratore. È il meccanismo con cui, per esempio, si scorrono i vicini di un vertice (incidentEdges(v)).
Errori comuni
- Dimenticare che
add/removeper indice su array costano , non . - Confondere LIFO e FIFO: la pila restituisce l'ultimo inserito, la coda il primo.
- Dimenticare le sentinelle nella lista doppia (casi speciali agli estremi) o aggiornare solo uno dei due puntatori.
- Leggere (
top,first) o togliere da una pila o coda vuota.
Versione ripasso
- ADT = interfaccia (cosa); implementazione = come e a che costo (vedi Problemi computazionali e algoritmiProblema computazionale come insieme di coppie (istanza, soluzione); algoritmo e modello di calcolo RAM; pseudocodice; taglia di un'istanza; ADT e struttura dati concreta; esempio svolto con ricerca lineare e binaria in un array ordinato.Problemi computazionali e algoritmi →). In C:
structe funzioni (vedi Liste concatenate in CLista singolarmente concatenata in C con nodo struct e testa passata per riferimento (Nodo **); addHead, addTail ricorsiva e iterativa, pop, inversione in loco, liberazione; coda con puntatori a testa e coda; ricorsione sulle liste; costi.Liste concatenate in C →). - Lista index-based (
get,set,add(i,e),remove(i),size,isEmpty): array, tranneadd/remove; array pieno ⇒ si raddoppia, inserimento in fondo ammortizzato. - Lista position-based (
first,last,before,after,addBefore/After,remove(p)): doppia lista con due sentinelle, tutto ma scansione solo sequenziale. - Pila LIFO:
push,top,pop, tutti . Coda FIFO:enqueue,first,dequeue, con lista e puntatore alla coda o con array circolare ((f+size) mod N). Deque = pila + coda. - Iterator (
hasNext,next) e Iterable (iterator()); l'ordine dipende dall'implementazione. - Coda circolare su array di capacità :
enqueuescrive in ,dequeuepone ; array estendibile: pieno ⇒ nuovo array di taglia doppia e copia, ammortizzato in fondo. - Esempi: pila: stack delle chiamate, annulla/ripeti; coda: BFS dei grafi (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 →).
- Errori: costo di
add/removesu array; LIFO/FIFO scambiati; sentinelle o puntatori dimenticati; accesso a una struttura vuota.