Liste concatenate in C
In questa pagina 5
Una lista concatenata è una sequenza di nodi allocati nello heap, ciascuno con un valore e un puntatore al successivo; la lista è identificata dal puntatore alla testa (NULL = lista vuota). Premessa: Puntatori, struct e memoria dinamica in CPuntatori e passaggio per riferimento in C, array e aritmetica dei puntatori, stringhe, struct e typedef con l'operatore ->, malloc e free, puntatore a puntatore per modificare una testa; compilazione con Makefile; errori tipici (puntatori pendenti, perdite di memoria, off-by-one).Puntatori, struct e memoria dinamica in C →. È l'implementazione concreta degli ADT di Liste, pile e codeRipasso degli ADT elementari: lista index-based (array) e position-based (lista doppiamente concatenata con sentinelle), pila LIFO, coda FIFO, deque, iteratori; interfacce, implementazioni e costi; array estendibile e coda circolare.Liste, pile e code →.
typedef struct nodo {
int val;
struct nodo *next;
} Nodo;Operazioni
Ogni funzione che può cambiare la testa riceve un Nodo **l (puntatore alla variabile testa del chiamante).
void addHead(Nodo **l, int n) { /* O(1) */
Nodo *aux = malloc(sizeof(Nodo));
aux->val = n;
aux->next = *l;
*l = aux;
}
void addTail(Nodo **l, int n) { /* O(n): scende fino al NULL finale */
if (*l == NULL) {
Nodo *aux = malloc(sizeof(Nodo));
aux->val = n;
aux->next = NULL;
*l = aux;
} else
addTail(&(*l)->next, n);
}
int pop(Nodo **l) { /* O(1), precondizione: lista non vuota */
Nodo *aux = *l;
int res = aux->val;
*l = aux->next;
free(aux);
return res;
}- In
addTailil puntatore passato alla chiamata ricorsiva è&(*l)->next, cioè l'indirizzo del camponextdel nodo corrente: così quando si arriva a*l == NULLsi scrive direttamente nelnextdell'ultimo nodo. Versione iterativa equivalente:while (*l != NULL) l = &(*l)->next;e poi si inserisce in*l. - Con
addHeadepopsi ottiene una pila (LIFO); conaddTailepopuna coda (FIFO). popdeve farefreedel nodo dopo aver lettovalenext.
Funzioni di sola lettura si scrivono con const Nodo *l e senza * doppio:
int lunghezza(const Nodo *l) { return l == NULL ? 0 : 1 + lunghezza(l->next); }
void stampa(const Nodo *l) {
for (; l != NULL; l = l->next) printf("%d ", l->val);
printf("\n");
}Inversione in loco, e senza nodi nuovi: si ritorcono i puntatori scorrendo la lista.
void inverti(Nodo **l) {
Nodo *prec = NULL, *cur = *l;
while (cur != NULL) {
Nodo *succ = cur->next;
cur->next = prec;
prec = cur;
cur = succ;
}
*l = prec;
}Liberare tutta la lista: while (*l != NULL) pop(l);.
Prova eseguita: dopo addHead di , addTail(10), addTail(20) la lista è 3 2 1 10 20 (lunghezza ); dopo inverti è 20 10 1 2 3; pop restituisce e lascia 10 1 2 3.
Coda con due puntatori
Per rendere anche l'inserimento in fondo si tengono due puntatori, in una struct:
typedef struct { Nodo *testa; Nodo *coda; } Coda; /* estrae da testa, inserisce in coda */
void enqueue(Coda *q, int v) {
Nodo *n = malloc(sizeof(Nodo));
n->val = v; n->next = NULL;
if (q->coda == NULL) q->testa = n; /* coda vuota */
else q->coda->next = n;
q->coda = n;
}
int dequeue(Coda *q) { /* precondizione: non vuota */
Nodo *n = q->testa;
int v = n->val;
q->testa = n->next;
if (q->testa == NULL) q->coda = NULL; /* si e' svuotata */
free(n);
return v;
}
int isEmpty(const Coda *q) { return q->testa == NULL; }Prova: inseriti , due dequeue danno ; dopo enqueue(99) si estrae (ordine FIFO). Con una lista doppiamente concatenata (campo prev in più) si può anche togliere dal fondo in .
Ricorsione sulle liste
Molti esercizi d'esame si risolvono con la ricorsione sul resto della lista. Schema: caso base l == NULL, poi si tratta il primo nodo e si richiama sul next. Esempi: contare i nodi con una proprietà, riempire un array di conteggi, copiare una lista in ordine inverso (la chiamata ricorsiva va prima dell'aggiunta in coda: gli elementi vengono accodati risalendo, quindi dall'ultimo al primo), confrontare due liste.
Costi
| Operazione | Lista semplice | Con puntatore alla coda |
|---|---|---|
addHead, pop |
||
addTail |
||
| ricerca di un valore | ||
| accesso al nodo -esimo |
Errori comuni
- Passare
Nodo *linvece diNodo **la una funzione che cambia la testa: la modifica si perde. popsu lista vuota (dereferenziaNULL), ofreedel nodo prima di salvarenext.enqueue/dequeueche dimenticano di aggiornare entrambi i puntatori quando la coda era o diventa vuota.- Non inizializzare
nextaNULLnel nuovo nodo (la lista non termina mai). - Dimenticare
freedi tutti i nodi a fine uso.
Versione ripasso
- Nodo:
typedef struct nodo { int val; struct nodo *next; } Nodo;; lista = puntatore alla testa,NULL= vuota. Funzioni che cambiano la testa:Nodo **l(vedi Puntatori, struct e memoria dinamica in CPuntatori e passaggio per riferimento in C, array e aritmetica dei puntatori, stringhe, struct e typedef con l'operatore ->, malloc e free, puntatore a puntatore per modificare una testa; compilazione con Makefile; errori tipici (puntatori pendenti, perdite di memoria, off-by-one).Puntatori, struct e memoria dinamica in C →). - addHead :
aux->next = *l; *l = aux;. addTail : ricorsiva su&(*l)->nextfino a*l == NULL. pop : salvarevalenext, poifree. - Pila =
addHead+pop; coda =addTail+pop(vedi Liste, pile e codeRipasso degli ADT elementari: lista index-based (array) e position-based (lista doppiamente concatenata con sentinelle), pila LIFO, coda FIFO, deque, iteratori; interfacce, implementazioni e costi; array estendibile e coda circolare.Liste, pile e code →). - Inversione in loco: tre puntatori
prec,cur,succ;cur->next = prec; alla fine*l = prec; . - Coda con due puntatori
testa,coda:enqueueedequeue; se la coda è vuota o si svuota, aggiornare entrambi. - Ricorsione sulle liste: base
NULL; per copiare in ordine inverso la chiamata ricorsiva precedeadd. - Codice essenziale:
addHead:aux->next = *l; *l = aux;.addTailricorsiva:if (*l == NULL) { crea; *l = aux; } else addTail(&(*l)->next, n);.pop:aux = *l; res = aux->val; *l = aux->next; free(aux);. - Coda a due puntatori:
enqueue:n->next = NULL; seq->coda == NULLalloraq->testa = naltrimentiq->coda->next = n;q->coda = n.dequeue: se dopo l'estrazioneq->testa == NULLalloraq->coda = NULL. - Errori:
Nodo *al posto diNodo **;popsu lista vuota;freeprima di leggerenext;nextnon inizializzato; nodi non liberati.