Argomenti trattati
Nella lezione precedente, abbiamo scritto una FUNCTION trova per individuare, in una lista ordinata, il puntatore ad un nodo contenente un valore dato. Chiaramente, usando la procedura di inserimento all'inizio, costruita in precedenza, la lista ottenuta non sarà in generale ordinata. Per la costruzione di liste ordinate è necessario disporre di una procedura d'inserimento differente, che dato il puntatore ad una lista ordinata e un intero x, inserisca x nella posizione opportuna della lista ordinata, al fine di ottenere una lista ancora ordinata. Ad esempio, data la lista contenente 11 15 20 25, inserendo 18 si dovrà ottenere la lista 11 15 18 20 25.
La procedura avrà la seguente intestazione:
PROCEDURE InserOrd (VAR l: tipolista; x: integer);Si noti che, dovendo effettuare modifiche, il puntatore alla lista viene passato per riferimento.
La procedura è composta da due parti fondamentali:
finito := false; p := l; WHILE (p <> NIL) AND NOT finito DO IF p^.info >= x THEN {la posizione a cui effettuare l'inserimento e' stata determinata} finito := true ELSE p := p^.prosAll'uscita da questo ciclo, la variabile p contiene un puntatore al nodo prima del quale effettuare l'inserimento (o NIL nel caso l'inserimento vada effettuato alla fine della lista).
finito := false; p := l; q := NIL; WHILE (p <> NIL) AND NOT finito DO IF p^.info >= x THEN {la posizione a cui effettuare l'inserimento e' stata determinata} finito := true ELSE BEGIN {spostamento in avanti dei puntatori} q := p; p := p^.pros ENDIn questo caso, dopo l'esecuzione del ciclo, si otterrà:
new(r); r^.info := x
r^.pros := p
);
q^.pros := r
)
q = NIL
) occorre invece fare in modo che il puntatore
iniziale della lista, cioè la variabile l, punti al nuovo
nodo (l := r).
{creazione del nodo} new(r); r^.info := x; {inserimento del nodo nella lista, tra i nodi puntati da q e p} r^.pros := p; IF q = NIL THEN {inserimento all'inizio lista} l := r ELSE {inserimento all'interno della lista, dopo il nodo q^} q^.pros := rLa procedura completa è:
PROCEDURE InserOrd (VAR l: tipolista; x: integer); {Inserisce il valore di x nella lista puntata da l ordinata in modo non decrescente} {versione iterativa} VAR finito: boolean; p, q, r: tipolista; BEGIN {InserOrd} {ricerca dalla posizione nella quale effettuare l'inserimento} {la ricerca viene effettuata in modo che, alla fine:} {p contenga il puntatore al nodo PRIMA del quale va effettuato l'inserimento} {q contenga il puntatore al nodo DOPO il quale va effettuato l'inserimento} finito := false; p := l; q := NIL; WHILE (p <> NIL) AND NOT finito DO IF p^.info >= x THEN {la posizione a cui effettuare l'inserimento e' stata determinata} finito := true ELSE BEGIN {spostamento in avanti dei puntatori} q := p; p := p^.pros END; {else} {creazione del nodo} new(r); r^.info := x; {inserimento del nodo nella lista, tra i nodi puntati da q e p} r^.pros := p; IF q = NIL THEN {inserimento all'inizio lista} l := r ELSE {inserimento all'interno della lista, dopo il nodo q^} q^.pros := r END; {InserOrd}
Osserviamo che una lista di elementi può essere definita in maniera ricorsiva come segue.
Una lista di elementi è:
I sottoprogrammi che abbiamo sviluppato per il trattamento delle liste possono essere riscritti in maniera ricorsiva, basandosi sulla definizione ricorsiva di lista. Per costruire un sottoprogramma ricorsivo consideriamo sempre due casi fondamentali:
PROCEDURE ScriviLista (l: tipolista); {Scrive in output il contenuto della lista puntata da l} {versione ricorsiva} BEGIN {ScriviLista} IF l <> NIL THEN BEGIN writeln(l^.info); ScriviLista(l^.pros) END {if} END; {Scrivilista}Volendo scrivere in output il contenuto di una lista partendo dall'ultimo elemento, è sufficiente osservare che, nel caso di lista non vuota, andrà scritta prima la parte di lista che inizia dopo il primo elemento, e poi il primo elemento. Dunque, è sufficiente scambiare la chiamata di writeln con la chiamata ricorsiva:
PROCEDURE ScriviAlContrario (l: tipolista); {Scrive in output il contenuto della lista puntata da l} BEGIN {ScriviAlContrario} IF l <> NIL THEN BEGIN ScriviAlContrario(l^.pros); writeln(l^.info) END {if} END; {ScriviAlContrario}Si noti che, mentre per la scrittura del contenuto di una lista, dal primo all'ultimo elemento, sia la soluzione iterativa che la soluzione ricorsiva sono estremamente semplici, per la scrittura al contrario, una soluzione iterativa risulterebbe estremamente complicata. Questo, dunque, è un esempio in cui l'uso della ricorsione è di fondamentale importanza per la scrittura di codice semplice.
Riscriviamo ora ricorsivamente la funzione per la ricerca di un
elemento x in una lista (non ordinata). Analizziamo di nuovo la
definizione ricorsiva.
FUNCTION trova (l: tipolista; x: integer): tipolista; {Restituisce il puntatore al primo nodo della lista l contenente il } {valore di x, se presente, oppure NIL} {versione ricorsiva} BEGIN {trova} IF l = NIL THEN trova := NIL ELSE IF l^.info = x THEN trova := l ELSE trova := trova(l^.pros, x) END; {trova}Nel caso la lista sia ordinata e si raggiunga un nodo contenente un valore superiore ad x, è inutile effettuare la chiamata ricorsiva, ma si può restituire immediatamente NIL. In tal caso, la FUNCTION diventa:
FUNCTION trova (l: tipolista; x: integer): tipolista; {Restituisce il puntatore al primo nodo della lista l contenente il} {valore di x, se presente, oppure NIL} {Si suppone che la lista sia ordinata in maniera non descrescente} {versione ricorsiva} VAR finito: boolean; BEGIN {trova} IF l = NIL THEN trova := NIL ELSE IF l^.info = x THEN trova := l ELSE IF l^.info < x THEN trova := trova(l^.pros, x) ELSE trova := NIL END; {trova}Sfruttando la ricorsione, il codice della procedura di inserimento in una lista ordinata può essere notevolmente semplificato.
PROCEDURE InserOrd (VAR l: tipolista; x: integer); {Inserisce il valore di x nella lista puntata da l ordinata in modo non decrescente} {versione ricorsiva} VAR p: tipolista; BEGIN {InserOrd} IF l = NIL THEN {se la lista e' vuota, crea il primo elemento} BEGIN new(l); l^.info := x; l^.pros := NIL END {se la lista non e' vuota...} ELSE IF x < l^.info THEN {se il x e' minore del primo elemento, inserisce x all'inizio} BEGIN new(p); p^.info := x; p^.pros := l; l := p END ELSE {altrimenti inserisce x nella parte di lista che segue il primo elemento} InserOrd(l^.pros, x) END; {InserOrd}Per evitare la duplicazione della parte di codice relativa alla creazione del nuovo nodo e al suo collegamento alla lista, possiamo introdurre una variabile di tipo boolean, che indichi quando sia necessario fare l'inserimento, e spostare l'operazione di inserimento alla fine. Il codice può essere riscritto come:
PROCEDURE InserOrd (VAR l: tipolista; x: integer); {Inserisce il valore di x nella lista puntata da l ordinata in modo non decrescente} {versione ricorsiva} VAR p: tipolista; inserire: boolean; {indica quando effettuare l'inserimento} BEGIN {InserOrd} inserire := false; IF l = NIL THEN inserire := true ELSE IF x < l^.info THEN inserire := true ELSE InserOrd(l^.pros, x); IF inserire THEN BEGIN new(p); p^.info := x; p^.pros := l; l := p END END; {InserOrd}
Dopo avere studiato l'inserimento, la ricerca e la scansione di una lista, consideriamo ora il problema della cancellazione di un elemento.
Vogliamo costruire una procedura cancella che riceva come parametro il puntatore ad una lista contenente numeri interi e un valore intero x, ed elimini dalla lista la prima occorrenza di x, se presente. Ad esempio, data la lista contente i valori 8 1 11 3 11, cancellando 11 il contenuto della lista diventerà 8 1 3 11.
La procedura avrà la seguente intestazione:
PROCEDURE cancella (VAR l: tipolista; x: integer);in cui, dovendo modificare la lista, il puntatore l è passato per riferimento.
Sviluppiamo prima di tutto la procedura in maniera iterativa. La struttura della procedura è simile a quella di inserimento in una lista ordinata:
cerca l'elemento da cancellare e determinane la posizione IF l'elemento e' stato trovato THEN eliminaloLa ricerca può essere effettuata utilizzando un puntantore ausiliario p e scrivendo:
trovato := false; p := l; WHILE (p <> NIL) AND NOT trovato DO IF p^.info = x THEN {la posizione a cui effettuare la cancellazione e' stata determinata} trovato := true ELSE p := p^.prosAl termine dell'esecuzione del ciclo si possono verificare le seguenti situazioni:
trovato := false; p := l; q := NIL; WHILE (p <> NIL) AND NOT trovato DO IF p^.info = x THEN {la posizione a cui effettuare la cancellazione e' stata determinata} trovato := true ELSE BEGIN {spostamento in avanti dei puntatori} q := p; p := p^.pros END {else}In questo caso, dopo l'esecuzione del codice, la situazione sarà quella rappresentata nella seguente figura:
q^.pros := p^.pros
dispose(p)che, oltre a rilasciare l'area di memoria, rende indefinito il valore del puntatore p:
p^.pros
contiene NIL, l'effetto
dell'assegnamento q^.pros := p^.pros
è di assegnare
NIL a q^.pros
, facendo dunque terminare la lista sul
nodo puntato da q.
l := l^.pros
:
IF trovato THEN BEGIN IF q = NIL THEN {se il nodo da cancellare e' il primo} l := l^.pros ELSE q^.pros := p^.pros; dispose(p) ENDIl testo completo della procedura è dunque:
PROCEDURE cancella (VAR l: tipolista; x: integer); {Cancella il primo nodo contenente x, se presente, dalla lista puntata da l} {versione iterativa} VAR p, q: tipolista; trovato: boolean; BEGIN {cancella} {ricerca della posizione nella quale effettuare la cancellazione} {la ricerca viene effettuata in modo che, alla fine:} {p punti al nodo SUCCESSIVO a quello da cancellare} {q punti al nodo da cancellare, o contenga NIL se va cancellata la radice} {trovato contenga false se il nodo non e' presente} trovato := false; p := l; q := NIL; WHILE (p <> NIL) AND NOT trovato DO IF p^.info = x THEN {la posizione a cui effettuare la cancellazione e' stata determinata} trovato := true ELSE BEGIN {spostamento in avanti dei puntatori} q := p; p := p^.pros END; {else} {elimina dalla lista l'elemento puntato da p} IF trovato THEN BEGIN IF q = NIL THEN {se il nodo da cancellare e' il primo} l := l^.pros ELSE q^.pros := p^.pros; dispose(p) END END; {cancella}
Sviluppiamo ora la procedura di cancellazione in maniera ricorsiva.
Nel caso di lista vuota non occorre effettuare alcuna operazione; nel caso di lista non vuota, cioè formata da un nodo seguito da una lista, si possono verificare le seguenti situazioni:
IF l non e' vuota THEN IF il primo nodo di l contiene x THEN elimina da l il primo nodo ELSE cancella x dalla lista l' che segue il primo elementoLa situazione in cui il contenuto del primo della lista è diverso dal valore da cancellare, è rappresentata nella seguente figura:
l^.pros
.
Nel caso in cui il nodo da cancellare sia il primo della lista, si ha la seguente situazione:
l^.pros
:
È immediato riscrivere le precedenti istruzioni in Pascal, ottenendo la seguente procedura ricorsiva:
PROCEDURE cancella (VAR l: tipolista; x: integer); {Cancella il primo nodo contenente x, se presente, dalla lista puntata da l} {versione ricorsiva} BEGIN {cancella} IF l <> NIL THEN IF l^.info = x THEN l := l^.pros ELSE cancella(l^.pros, x) END; {cancella}La procedura appena scritta elimina un elemento dalla lista, ma non rilascia l'area di memoria che era utilizzata dal nodo. Per effettuare questa operazione, introduciamo un puntatore ausiliario p. Prima di spostare il puntatore l, per eliminare il nodo dalla lista, si fa puntare p al nodo da eliminare, assegnando a p il valore di l:
PROCEDURE cancella (VAR l: tipolista; x: integer); {Cancella il primo nodo contenente x, se presente, dalla lista puntata da l} {versione ricorsiva} VAR p: tipolista; BEGIN {cancella} IF l <> NIL THEN IF l^.info = x THEN BEGIN p := l; l := l^.pros; dispose(p) END ELSE cancella(l^.pros, x) END; {cancella}
Nei programmi presentati negli esercizi seguenti si fa riferimento alle seguenti dichiarazioni di tipo:
TYPE tipolista = ^nodolista; {puntatore a un nodo della lista} nodolista = RECORD {nodo della lista} info: integer; {informazione contenuta nel nodo} pros: tipolista{puntatore all'elemento successivo della lista} END;
NIL
nel caso di lista vuota).
4 10 2 5
, evidenziando eventuali errori
o possibili errori.
PROCEDURE p1 (l: tipolista); BEGIN WHILE l <> NIL DO BEGIN writeln(l^.info); l := l^.pros END END;
PROCEDURE p2 (l: tipolista); BEGIN WHILE l <> NIL DO BEGIN l := l^.pros; writeln(l^.info) END END;
PROCEDURE p3 (l: tipolista); BEGIN IF l <> NIL THEN BEGIN writeln(l^.info); p3(l^.pros) END END;
PROCEDURE p4 (l: tipolista); BEGIN IF l <> NIL THEN BEGIN p4(l^.pros); writeln(l^.info) END END;
PROCEDURE p5 (l: tipolista); BEGIN IF l <> NIL THEN BEGIN writeln(l^.info); p5(l^.pros); writeln(l^.info) END END;
PROCEDURE p6 (l: tipolista); BEGIN IF l <> NIL THEN l^.pros := l; WHILE l <> NIL DO BEGIN writeln(l^.info); l := l^.pros END END;
4 10 2 5
. Descrivere poi brevemente la
funzionalità svolta dalla procedura.
PROCEDURE p1 (l: tipolista); VAR s: integer; BEGIN s := 0; WHILE l <> NIL DO BEGIN s := s + l^.info; l^.info := s - l^.info; l := l^.pros END END;
PROCEDURE p2 (l: tipolista); PROCEDURE a (l: tipolista; s: integer); BEGIN IF l <> NIL THEN BEGIN a(l^.pros, s + l^.info); l^.info := s END END; BEGIN a (l,0) END;
PROCEDURE p3 (VAR l: tipolista); VAR s: integer; BEGIN s := 0; WHILE l <> NIL DO BEGIN s := s + l^.info; l^.info := s - l^.info; l := l^.pros END END;
PROCEDURE p4 (VAR l: tipolista); PROCEDURE a (VAR l: tipolista; s: integer); BEGIN IF l <> NIL THEN BEGIN a (l^.pros, s + l^.info); l^.info:= s END END; BEGIN a (l,0) END;
PROCEDURE p5 (l: tipolista); PROCEDURE a (l: tipolista; s: integer); BEGIN IF l <> NIL THEN BEGIN l^.info := s; a(l^.pros, s + l^.info) END END; BEGIN a(l,0) END;
r := s; writeln(chr(s^));
q^.val := q <> NIL; q^.pros := q;
p[a]^ := a+1;
p^.b := NIL; p^.c := x[p^.a];
r := s; s^:= succ(r^);
y[q^] := q; q^ := chr(ord(q = NIL));
r.a := r.b; r.b := r.b + 1; r.c := chr(r.a);
p^.val := x[p <> NIL] > y;
q[p^.a[i]] := i;
©1999 Giovanni Pighizzini
Il contenuto di queste pagine è
protetto dalle leggi sul copyright e
dalle disposizioni dei trattati internazionali. Il titolo ed i copyright
relativi alle pagine sono di proprietà dell'autore.
Le pagine possono essere riprodotte ed utilizzate liberamente dagli
studenti, dagli istituti di ricerca, scolastici ed universitari
afferenti ai Ministeri
della Pubblica Istruzione e dell'Università e della
Ricerca Scientifica e Tecnologica
per scopi istituzionali, non a fine di lucro.
Ogni altro utilizzo o riproduzione (ivi incluse, ma non
limitatamente a, le riproduzioni a mezzo stampa, su supporti magnetici o
su reti di calcolatori) in toto o in parte è vietata, se non
esplicitamente autorizzata per iscritto, a priori, da parte dell'autore.
L'informazione contenuta in queste pagine è ritenuta essere
accurata alla data della pubblicazione. Essa è fornita per scopi
meramente didattici e non per essere utilizzata in progetti di impianti,
prodotti, ecc.
L'informazione contenuta in queste pagine è soggetta a cambiamenti
senza preavviso. L'autore non si assume alcuna responsabilità per il
contenuto di queste pagine (ivi incluse, ma non limitatamente a, la
correttezza, completezza, applicabilità ed aggiornamento
dell'informazione).
In ogni caso non può essere dichiarata conformità all'informazione
contenuta in queste pagine.
In ogni caso questa nota di copyright non deve mai essere rimossa e deve
essere riportata anche in utilizzi parziali.