Esercizio 28C, palindromi in una lista
In questa pagina 6
Testo (scritto del 05/02/2024, parte 3, in C). Palindromi. Implementiamo un algoritmo per determinare se la parola contenuta in una lista concatenata è palindroma (parole che restano uguali se lette al contrario: "anna" e "bob" sono palindromi, "ciao" e "bod" non lo sono). Esistono vari modi per farlo; noi implementiamo un semplice algoritmo che crea una copia in ordine invertito della lista da controllare e verifica se la lista di origine e quella invertita sono identiche. Si chiede di implementare le seguenti funzioni, con typedef struct nodo { char value; struct nodo *next; } Nodo;:
void add(Nodo **lis, char c);
int compareLists(Nodo *lis, Nodo *comp);
void copyReversed(Nodo *lis, Nodo **copy);
int checkPalindrome(Nodo *lis);addserve ad aggiungere un nodo in coda alla listaliscontenente il carattereccome valore.compareListsdeve restituire vero selisecompsono due liste identiche (stesso numero di nodi e contenuto dei nodi identico). Esistono più modi di eseguire questo controllo, ma si consiglia di usare la ricorsione.copyReversed(lis, copy): Input:lis(una lista),copy(una lista vuota). Result: nessun ritorno; side effect:copycontiene una lista di lunghezza uguale alis, in cui i nodi contengono gli stessi caratteri di quelli dilisma in ordine inverso. Pseudocodice: selisè una lista vuota, ritorna;copyReversed(lista a partire dal nodo successivo di lis, copy);add(copy, carattere contenuto nel primo nodo di lis).checkPalindrome(lis): Result: vero se la lista contiene una sequenza di caratteri palindroma, falso altrimenti. Pseudocodice:inv <- nuova lista vuota;copyReversed(lis, inv);return compareLists(lis, inv).
Non è permesso modificare le firme (parametri e tipi di ritorno) delle funzioni. Lo pseudocodice non è codice C completo (tipi, puntatori, operatore ->). Nota del testo: copyReversed ha la chiamata ricorsiva a sé stessa prima di effettuare la chiamata ad add; questo ordine è importante. (Perché?) Si consiglia di usare la funzione di stampa per verificare che la copia invertita funzioni prima di procedere con il resto.
Richiami
Liste concatenate in C, aggiunta in coda con Nodo ** e ricorsione sulle liste (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 →); passaggio per riferimento (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 →).
Soluzione
void add(Nodo **lista, char c) { /* aggiunge in coda */
while (*lista != NULL)
lista = &(*lista)->next; /* avanza il puntatore al puntatore */
Nodo *n = malloc(sizeof(Nodo));
n->value = c;
n->next = NULL;
*lista = n;
}
int compareLists(Nodo *a, Nodo *b) {
if (a == NULL && b == NULL) return 1; /* finite insieme: uguali */
if (a == NULL || b == NULL) return 0; /* lunghezze diverse */
if (a->value != b->value) return 0;
return compareLists(a->next, b->next);
}
void copyReversed(Nodo *src, Nodo **copy) {
if (src == NULL)
return;
copyReversed(src->next, copy); /* prima il resto della lista... */
add(copy, src->value); /* ...poi il primo carattere, in coda */
}
int checkPalindrome(Nodo *lista) {
Nodo *inv = NULL;
copyReversed(lista, &inv);
int r = compareLists(lista, inv);
while (inv != NULL) { /* libera la copia temporanea */
Nodo *t = inv->next;
free(inv);
inv = t;
}
return r;
}Perché la chiamata ricorsiva viene prima di add
add inserisce in coda. Per costruire la lista inversa il primo elemento da accodare deve essere l'ultimo della lista originale. Con la chiamata ricorsiva prima di add, la ricorsione scende fino al fondo e gli add avvengono risalendo: l'ultimo nodo viene accodato per primo, poi il penultimo, e così via fino al primo, che viene accodato per ultimo. Per "ciao": le chiamate scendono su c, i, a, o; risalendo si aggiungono o, a, i, c: la copia è oaic (verificato). Se add fosse prima della chiamata ricorsiva si otterrebbe una copia identica all'originale (ciao) e ogni parola risulterebbe palindroma.
Complessità
compareLists: , al più (una chiamata per coppia di nodi).add: se la lista ha già nodi (scorre fino alla coda). QuindicopyReversedesegueaddsu liste di lunghezza : .checkPalindrome: per la copia più per il confronto: in tempo, in spazio (la copia e lo stack della ricorsione).
Si può scendere a inserendo in testa (addHead, ): scorrendo la lista originale dall'inizio e inserendo ogni carattere in testa alla copia si ottiene l'ordine inverso senza ricorsione.
Prove eseguite
checkPalindrome restituisce per anna, bob, a, abcba, abccba e la lista vuota (vuota: due liste vuote sono uguali); per ciao, bod, ab, abcda.
Errori comuni
- Invertire l'ordine ricorsione/
addincopyReversed: la copia non è invertita. - In
compareListsconfrontare i valori senza controllare prima che nessuno dei due puntatori siaNULL(dereferenziaNULL), o dimenticare il caso di liste di lunghezza diversa ("ab"e"abc"). - In
addusareNodo *invece diNodo **: quando la lista è vuota la testa del chiamante non viene aggiornata. - Non inizializzare
n->next = NULLnel nuovo nodo. - Non liberare la copia temporanea (perdita di memoria).
Versione ripasso
Testo. Lista concatenata di caratteri (Nodo { char value; Nodo *next; }); add (in coda), compareLists, copyReversed, checkPalindrome (copia invertita e confronto). Perché in copyReversed la ricorsione precede add?
add(Nodo **lista, char c)(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 →): scorrelista = &(*lista)->nextfino aNULL, poi crea il nodo connext = NULL.compareLists: entrambeNULL⇒ ; una solaNULL⇒ ; valori diversi ⇒ ; altrimenti ricorsione sunext.copyReversed:NULL⇒ ritorno; primacopyReversed(src->next, copy), poiadd(copy, src->value): gliaddavvengono risalendo, quindi l'ultimo carattere entra per primo (ciao⇒oaic); invertendo l'ordine si avrebbe una copia identica.checkPalindrome: copia, confronto,freedella copia.- Costi:
compareLists;copyReversedperchéaddè ; conaddHeadsi arriva a . - Prove: palindrome
anna,bob,a,abcba,abccba, vuota; non palindromeciao,bod,ab. - Codice:
add:while (*lista != NULL) lista = &(*lista)->next;poimalloc,next = NULL,*lista = n;compareLists: dueNULL, una sola , valori diversi , altrimenti ricorsione;checkPalindrome:copyReversed(lista, &inv),compareLists(lista, inv),freedella copia. - Perché la ricorsione prima di
add: gliaddavvengono risalendo, quindi l'ultimo carattere entra per primo. - Errori: ordine ricorsione/
addscambiato;NULLnon controllato;Nodo *inadd;nextnon inizializzato; copia non liberata.