DIPARTIMENTO DI
INFORMATICA Università di Torino | |
Lezione di Mercoledi' 20/02/02 - ultimo aggiornamento: 22/02/02Funzioni ricorsive che operano su liste linkate - liste doppiamente linkate e altre varianti delle liste linkateCHE COSA E' STATO FATTO A LEZIONE
Funzioni ricorsive che operano su liste linkate
Costruzione della lista che contiene i nodi nell'intervallo da 1 a N, genearando (cioe' facendo la newnode()) i singoli nodi nell'ordine da 1 a N.
Funzione ricorsiva lineareLa funzione "buildlis_n_rnf" in [NOTE1:pag. 42] puo' essere riscritta nel seguente modo: /* AI: AF: Restituisce la lista degli interi nell'intervallo da i a j (generando i nodi in ordine da i a j) */ node *buildlis_int_rn (int i, int j) { if (i > j) return NULL; else { node *p = newnode(); p->data = i; p->next = buildlis_int_rn (i+1,j) return p; } }La complessita' in tempo della funzione "buildlis_int_rn" e' lineare nella lunghezza della lista L prodotta (ovvero: costante se j > i, e lineare in j-i+1 altrimenti).
Funzione ricorsiva di coda (versione 1)La funzione "buildlis_int_rn" e' ricorsiva lineare. Trasformiamo tale funzione in una funzione lineare di coda (mantenendo la proprieta' che i nodi vengono generati in ordine da i a j) /* AI: AF: Restituisce la lista degli interi nell'intervallo da i a j (generando i nodi in ordine da i a j) */ node *buildlis_int (int i, int j) { return buildlis_int_rt (i, j, NULL); } /* AI: acc contiene una lista L AF: Restituisce la lista ottenuta aggiungento in coda alla lista acc gli interi nell'intervallo da i a j */ node *buildlis_int_rt (int i, int j, node *acc) { if (i > j) return acc; else { node *p = newnode(); p->data = i; p->next = NULL; return buildlis_int_rt (i+1 ,j, << LISTA OTTENUTA INSERENDO p IN CODA AD acc >>) } }Lo pseudo-codice << LISTA OTTENUTA INSERENDO p IN CODA AD acc >>)puo' essere raffinato in vari modi. Il modo piu' semplice e' il seguente: append(acc,p);dove "append e' la funzione: /* AI: lis1 contiene una lista L1, lis2 contiene una lista L2 AF: Restituisce la lista ottenuta aggiungento in coda alla lista L1 la lista L2 (non vengono creati nuovi nodi) */ node *append (node *lis1, node *lis2) { if (lis1 == NULL) return lis2; node *q = lis1; while (q->next != NULL) q = q->next; q->next = lis2; return lis1; }Tuttavia, siccome la funzione "append" ha complessita' in tempo lineare nella lunghezza della lista acc, la funzione "buildlis_int" risultante avrebbe complessita' quadratica nella lunghezza della lista prodotta. Questo non e' accettabile. Per questo motivo proponiamo una nuova versione della funzione, che usa come parametro acc una struttura (che chiameremo "list") che contiene un puntatore al primo nodo e un puntatore all'ultimo nodo della lista che "accumula" il risultato.
Funzione ricorsiva di coda (versione 2)
typed struct { node *first; node *tail; } list; /* AI: AF: Restituisce la lista degli interi nell'intervallo da i a j (generando i nodi in ordine da i a j) */ node *buildlis_int (int i, int j) { list acc = {NULL, NULL}; return buildlis_int_rt (i, j, acc); } /* AI: acc.head contiene una lista L, acc.tail punta all'ultimo nodo di L AF: Restituisce la lista ottenuta aggiungento in coda alla lista L gli interi nell'intervallo da i a j */ node *buildlis_int_rt (int i, int j, list acc) { if (i > j) return acc.head; else { node *p = newnode(); p->data = i; p->next = NULL; if (acc.head == NULL) acc.head = p; else acc.tail->next = p; acc.tail = p; return buildlis_int_rt (i+1 ,j, acc) } }La funzione cosi' ottenuta ha complessita' lineare nella lunghezza della lista generata. Un possibile miglioramento della funzione puo' essere ottenuto passando il parametro acc per referenza (nelle chiamate ricorsive, invece di copiare la struttura, si copierebbe solo il suo indirizzo). Nel paragrafo succesivo desciveremo un'altra possibilita'.
Funzione ricorsiva di coda (versione 3)La seguente funzione e' una piccola variante della funzione precedente, che tratta separatamente il caso in cui occorre generare una lista vuota. typed struct { node *first; node *tail; } list; /* AI: AF: Restituisce la lista degli interi nell'intervallo da i a j (generando i nodi in ordine da i a j) */ node *buildlis_int (int i, int j) { if (i > j) return NULL; list acc; acc.head = newnode(); acc.tail = acc.head; acc.head->data = i; acc.head->next = NULL; return buildlis_int_rt (i+1, j, acc); } /* AI: acc.head contiene una lista non vuota L, acc.tail punta all'ultimo nodo di L AF: Restituisce la lista ottenuta aggiungento in coda alla lista L gli interi nell'intervallo da i a j */ node *buildlis_int_rt (int i, int j, list acc) { if (i > j) return acc.head; else { acc.tail->next = newnode(); acc.tail = acc.tail->next; acc.tail->data = i; acc.tail->next = NULL; return buildlis_int_rt (i+1 ,j, acc) } }
Funzione ricorsiva di coda (versione 4)La seguente funzione e' un'ulteriore piccola variante della funzione precedente, che evita di usare la struttura "list". /* AI: AF: Restituisce la lista degli interi nell'intervallo da i a j (generando i nodi in ordine da i a j) */ node *buildlis_int (int i, int j) { if (i > j) return NULL; node *head = newnode(); head->data = i; head->next = NULL; buildlis_int_rt (i+1, j, head); return head; } /* AI: tail punta all'ultimo nodo di una lista non vuota L AF: Gli interi nell'intervallo da i a j sono stati aggiunti in coda alla lista L */ void buildlis_int_rt (int i, int j, node *tail) { if (i <= j) { tail->next = newnode(); tail = tail->next; tail->data = i; tail->next = NULL; buildlis_int_rt (i+1 ,j, tail) } }
Funzione iterativa (ottenuta dalla versione 4)La funzione "buildlis_int" rimane come prima. Applicando la trasformazione alla funzione "buildlis_aux" si ottiene la funzione:
/* AI: tail punta all'ultimo nodo di una lista non vuota L AF: Gli interi nell'intervallo da i a j sono stati aggiunti in coda alla lista L */ void buildlis_int_it (int i, int j, node *tail) { while (i <= j) { tail->next = newnode(); tail = tail->next; tail->data = i; tail->next = NULL; } } Ora e' possibile migliorare il codice, compattando le funzioni "buillis_int" e "buildlis_int_it" in un'unica funzione iterativa.
/* AI: AF: Restituisce la lista degli interi nell'intervallo da i a j (generando i nodi in ordine da i a j) */ node *buildlis_int (int i, int j) { if (i > j) return NULL; node *head = newnode(); head->data = i; node *tail = head; i++; while (i <= j) { tail->next = newnode(); tail = tail->next; tail->data = i; } tail->next = NULL; return head; }
Liste doppiamente linkate e altre varianti delle liste linkate
Liste doppiamente linkateLe liste doppiamente linkate sono liste linkate in cui ogni nodo contiene, oltre al puntatore al nodo successivo, anche un puntatore al nodo precedente. typedef struct nod2 { int data; struct nod2 *next; struct nod2 *prev; } node2;Una variante belle liste doppiamente linkate sono le liste doppiamente linkate circolari: il campo prev del primo nodo punta all'ultimo nodo, e il campo next dell'ultimo nodo punta al primo nodo (per capitre quando si e' arrivati alla "fine" della lista si usa il puntatore al primo nodo al posto di NULL).
Liste con nodo di testa fittizioL'inserimento/cancellazione in testa alla lista (operazione sul primo nodo della lista) richiede codice diverso da quello per gestire l'inserimento/cancellazione su altri nodi. Per ovviare a questo incoveniente si puo' decidere di lasciare sempre un nodo "vuoto" in testa alla lista: tale nodo non sara' usato per contenere informazioni. Questa tecnica introduce una sottile distinzione: data una variabile: node *l;si ha che (in base alla definizione data in precedenza) l contiene la lista vuota quando punta ad un nodo il cui campo next vale NULL. Se l vale NULL, allora l non contiene una lista!!! La tecnica del nodo di testa fittizio puo' essere applicata sia alle liste linkate semplici che alle liste doppiamente linkate (cirolari e non).
Liste che contengono il numero dei nodi, e altre informazioniIn numerose applicazioni puo' essere utile conoscere il numero dei nodi contenuti in una lista. Si puo' pensare allora di introdurre una dichiarazione: typedef struct { int length; node first; } lista;Data una variabile: list *l;si ha che (in base alla definizione data in precedenza) l contiene la lista vuota quando: il campo length contiene 0 e il campo length contiene NULL. Se, oltre al numero dei nodi, si vuole mantenere anche un puntatore all'ultimo nodo, si puo' dichiarare lista nel seguente modo: typedef struct { int length; node first; node last; } lista; Al solito le varie tecniche si possono combinare. Ad esempio: il nodo first potrebbe puntare ad una sequenza di nodi in cui c'e' un nodo di testa fittizio...
COSA SI DEVE LEGGERE
|
In generale tutte le funzioni ricorsive che operano su array presentate in [NOTE1: pag. 38-40] possono essere riscritte come funzioni ricorsive che operano su un intervallo a[first..last] dell'array. Ad esempio la funzione "palind_rn" in [NOTE1:pag. 39] puo' essere riscritta secondo la seguente specifica:
/* AI: first e last sono indici validi per a AF: Restituisce: 1 se a[first..last] contiene una sequenza palindroma di interi: 0 altrimenti */ int isPalind_rn (int a[], int first, int last)Dopo aver scritto tale funzione come una funzione ricorsiva lineare:
Last update: Feb 25, 2002 | |