1.26k likes | 1.38k Views
CAPITOLO 15. Top. Top. STACK. STACK. Le operazioni fondamentali che si fanno sugli stack sono: riempimento e svuotamento. Questo implica che durante lo svolgimento del programma il numero di oggetti nello stack può cambiare.
E N D
Top Top STACK
STACK Le operazioni fondamentali che si fanno sugli stack sono: riempimento e svuotamento. Questo implica che durante lo svolgimento del programma il numero di oggetti nello stack può cambiare. Per descrivere uno stack è sufficiente sapere quali sono gli oggetti (Items) nello stack e il loro numero (Top).
OPERAZIONI SUGLI STACK In uno stack l’elemento inserito per ultimo viene estratto per primo (LIFO - Last In First Out). In uno stack occorrerrà solo conoscere il numero di oggetti (Top). Aggiungere ed eliminare oggetti. Items[ ] è un array in cui si collocano gli oggetti, Top il numero di oggetti, l’operazione di aggiungere oggetti si chiama push e quella di eliminare oggetti pop. Quando Top=0 allora lo stack è vuoto.
OPERAZIONI SUGLI STACK AGGIUNGERE Top Top + 1 Items[Top] Item ELIMINARE Top Top - 1
DIMOSTRAZIONI PER INDUZIONE • La dimostrazione per induzione è una tecnica per provare che un asserto S(n) vale per tutti gli interi n maggiori di un certo limite inferiore. • Supposto vero l’asserto la dimostrazione consiste in: • individuare un caso base, il minimo valore di n, diciamo k, per cui si dimostra l’asserto S(k) • dimostrare il passo induttivo, cioè che per ogni n k , dove S(k) è la base induttiva, S(n) implica S(n+1) o equivalentemente supposto vero S(n) dimostrare che è vero S(n+1). Esempio Vogliamo dimostrare che: caso base Poniamo n=0 avremo che è quindi dimostrato vero
a) b) b) passo induttivo Dobbiamo ora dimostrare che Supposto sia vero Il membro sinistro può essere riscritto come Avendo supposto vero l’asserto Sostituiamo b) in a) c.v.d.
RICORSIVITA’ Algoritmo ricorsivo Un algoritmo è ricorsivo quando per trovare la soluzione ad un dato problema fa uso della soluzione trovata per lo stesso problema presentato in una versione più ridotta.
ESEMPIO Problema del Massimo Comun Divisore (MCD o GCD) Dati due numeri interi m ed n trovare il più grande intero positivo che divide sia m che n. Soluzioni possibili Scomposizione in fattori primi Algoritmo di Euclide
ALGORITMO DI EUCLIDE PER IL MASSIMO COMUN DIVISORE (300 A.C.) Siano m ed n due numeri naturali e supponiamo sia m>n • 1 Si divide m per n • 2 Se il resto è zero allora n è il MCD tra m e n. • 3 Se il resto è diverso da zero torna al primo passo scambiando m con n e n con r (cioè dividi n per r)
M N R R R’ R’ R’ ALGORITMO DI EUCLIDE MCD=
Pseudo codice IF M o N sono valori che rappresentano una soluzione valida THEN GCD valore della soluzione (M o N) ELSE GCD GCD(N, M MOD N)
FUNCTION GCD(M,N:integer):integer; BEGIN IF N=0 THEN GCD:=M ELSE GCD:=GCD(N,M MOD N) END; writeln('Il GCD e'':= ',GCD(M,N)); PROGRAM Euclide; VAR M,N:integer; BEGIN writeln('Assegna M e N '); read(M);read(N); writeln('Il MCD e'':= ',MCD(M,N)); readln END. FUNCTION MCD(M,N:integer):integer; VAR Resto:integer; BEGIN Resto:=M MOD N; WHILE Resto<>0 DO BEGIN M:=N; N:=Resto; Resto:=M MOD N END; MCD:=N END;
GCD:=GCD(N, M MOD N) No No No No N=0? N=0? N=0? N=0? N=0? Si GCD GCD GCD GCD GCD 6 GCD(66,48) GCD(48,18) GCD(18,12) GCD(12,6) GCD(6,0) GCD(48,18) GCD(18,12) GCD(12,6) GCD(6,0) FUNCTION GCD(M,N:integer):integer; BEGIN IF N=0 THEN GCD:=M ELSE BEGIN writeln(’**push** GCD(',M,',',N,')'); writeln; GCD:=GCD(N,M MOD N); writeln(’**pop** N= ',M MOD N) END END;
GCD:=GCD(N, M MOD N) No No No N=0? N=0? N=0? N=0? Si GCD GCD GCD GCD 6 GCD(30,18) GCD(18,12) GCD(12,6) GCD(6,0) GCD(18,12) GCD(12,6) GCD(6,0) FUNCTION GCD(M,N:integer):integer; BEGIN IF N=0 THEN GCD:=M ELSE GCD:=GCD(N,M MOD N); END; =6 GCD(6,0) =6 GCD(12,6) GCD(18,12) =6 GCD(30,18) =6
Ogni algoritmo ricorsivo deve essere tale che durante i passi della ricorsione, cioè della riduzione del problema, si giunge sempre ad una soluzione. Questa soluzione è detta caso base o STOPPING CASE. Nel caso del GCD il caso base è il caso in cui N=0. Caso base : è una soluzione per un algoritmo ricorsivo per la quale non sono necessarie ulteriori chiamate ricorsive. Ogni algoritmo ricorsivo, per essere valido, richiede almeno un caso base. Il meccanismo di gestione delle chiamate ricorsive è quello dello stack, si fa cioè ricorso alla tecnica LIFO ed è chiamato Run-time stack.
Un esempio di ricorsione per accumulazione Problema Si vuole calcolare la potenza Nma del numero reale Xre. Soluzione Base Case Ricorsione FUNCTION PotenzaN(Xre:real,N:integer):real; BEGIN IF N=0 THEN PotenzaN :=1 ELSE PotenzaN:= Xre*PotenzaN(Xre,N-1); END;
No No No Si N=0? N=0? N=0? N=0? PotenzaN 0.9* PotenzaN 0.9* PotenzaN 0.9* PotenzaN(0.9,2) PotenzaN(0.9,1) PotenzaN(0.9,0) PotenzaN 1 =0.9*1 =0.9*0.9 =0.9*0.9*0.9 PotenzaN(0.9,3) PotenzaN(0.9,3) PotenzaN(0.9,2) PotenzaN(0.9,1) PotenzaN(0.9,0) =1 0.9*PotenzaN(0.9,0) FUNCTION PotenzaN(Xre:real,N:integer):real; BEGIN IF N=0 THEN PotenzaN :=1 ELSE PotenzaN:= Xre*PotenzaN(Xre,N-1); END; 0.9*PotenzaN(0.9,1) 0.9*PotenzaN(0.9,2) 0.9*PotenzaN(0.9,3)
caso base Poniamo n=1 avremo che è quindi dimostrato vero passo induttivo Dobbiamo ora dimostrare che b) q.e.d. DIMOSTRAZIONE PER INDUZIONE Vogliamo dimostrare che S(n): a) Avendo supposto vero l’asserto sostituiamo in b) il valore che si ottiene da a)
caso base ? NO allora applica PROCEDURE(p1 ,….,pk ) caso base ? NO allora applica caso base ? NO allora applica Applica la soluzione a PROCEDURE(p’1 ,….,p’k ) Applica la soluzione a PROCEDURE(p”1 ,….,p”k ) PROCEDURE(p*1 ,….,p*k ) caso base ? SI allora applica la soluzione a COME FUNZIONA LA RICORSIVITA’ Dove (pi1 ,….,pik ) sono problemi ridotti del problema precedente
IF i parametri fanno riferimento a un caso base THEN risolvi il problema ELSE usa i valori dei parametri per un problema ridotto CHIAMA LA PROCEDURA O FUNZIONE PER RISOLVERE IL PROBLEMA RIDOTTO Possiamo dire che è stato applicato il metodo del DIVIDE ET IMPERA
Un algoritmo iterativo consiste in un unico processo che ripete le stesse identiche operazioni molte volte. Un algoritmo ricorsivo consiste in un numero finito di processi aperti uno dopo l’altro e posti in uno stack. Non appena si chiude un processo subito si scende nello stack e si chiude il processo immediatemente seguente e così via di seguito.
Per scrivere un algoritmo ricorsivo bisogna soddisfare le seguenti condizioni: 1. Esiste almeno un caso base la cui soluzione è banale 2. Tutti i sottoproblemi devono poter essere risolti in termini di versioni ridotte di uno stesso problema 3. Le azioni applicate per la soluzione di un problema ridotto portano sempre alla soluzione di un problema più grande 4. In funzione di quanto sia grande il problema iniziale deve essere sempre possibile trovare almeno un caso base nel corso della elaborazione del problema originale.
Sum =1 Sum =3 Sum =6 Sum =10 Sommatoria dei primi N interi positivi 1. La somma dei primi 0 interi positivi vale 0. 2. La somma dei primi N interi positivi è uguale alla somma dei primi N-1 interi più N. FUNCTION Sum(N:integer):integer; BEGIN IF N=0 THEN Sum :=0 ELSE Sum :=N+ Sum(N-1) END; 1+ Sum(0) 2+Sum(1) 3+ Sum(2) 4+ Sum(3) Sia N=5 Sum =15 5+ Sum(4)
caso base Poniamo n=1 avremo che è quindi dimostrato vero passo induttivo Dobbiamo ora dimostrare che b) Possiamo scrivere DIMOSTRAZIONE PER INDUZIONE Vogliamo dimostrare che S(n): a) q.e.d.
Quando si applica un processo ricorsivo tipo quello della accumulazione bisogna assicurarsi che i valori accumulati nelle relative variabili siano correttamente passati da un processo all’altro. Inoltre il valore assunto da una variabile in un processo ricorsivo non deve essere distrutto dal lancio di un altro processo ricorsivo. Di qui la necessità di passare le variabili utilizzando la chiamata per VAR. Esempio: Fare la somma dei primi N interi positivi e mostrare le somme parziali ogni M passi. Pseudo-codice IF N=0 THEN scrivi un messaggio Sum 0 ELSE ShowSums(N-1, M, Sum) Sum Sum +N IF N MOD M=0 THEN scrivi N e Sum
PROGRAM SommaRicorsiva(input,output); VAR Nin,Min,Sumout:integer; PROCEDURE ShowSums(N,M:integer; VAR Sum:integer); VAR Temp:integer; BEGIN IF N=0 THEN BEGIN Sum:=0 END ELSE BEGIN ShowSums(N-1,M,Sum); Sum:=Sum+N; Temp:=N MOD M; IF N MOD M=0 THEN writeln('La somma dei primi ',N:1,' numeri e'' = ',Sum:1,'.') END END; BEGIN writeln(' Assegna N e M '); readln(Nin); readln(Min); writeln(' N M Sum'); ShowSums (Nin,Min,Sumout); writeln('La somma dei primi ',Nin:1,' numeri e'' = ', Sumout:1,'.'); END.
Assegna N e M 7 2 N M Sum push** 7 2 0 push** 6 2 0 push** 5 2 0 push** 4 2 0 push** 3 2 0 push** 2 2 0 push** 1 2 0 Somme parziali pop** 1 2 0 pop** 2 2 1 La somma dei primi 2 numeri e' = 3. pop** 3 2 3 pop** 4 2 6 La somma dei primi 4 numeri e' = 10. pop** 5 2 10 pop** 6 2 15 La somma dei primi 6 numeri e' = 21. pop** 7 2 21 La somma dei primi 7 numeri e' = 28.
ESERCIZIO Sia dato un vettore A di interi di dimensione N, scrivere una funzione ricorsiva che restituisca la somma di tutti gli elementi di A. Sia dato un vettore A di interi di dimensione N, scrivere una procedura ricorsiva che restituisca la somma di tutti gli elementi pari di A e la somma di tutti gli elementi dispari di A.
Un algoritmo ricorsivo deve essere completo, deve cioè sempre esistere una soluzione qualunque sia l’input. La completezza dipende dal dominio su cui si definisce l’algoritmo. Esempio: PotenzaN se definito sugli interi non è completo perché non funziona per gli interi negativi (infatti N-1 per N negativo non raggiunge mai lo 0). Diventa completo se il domino di definizione è quello dei numeri interi positivi. Stack Infinito Si genera quando per un qualche input di un algoritmo ricorsivo non si raggiunge mai il caso base.
Esempio Dato un testo scritto su un file e formato da più righe leggerlo e riscriverlo in ordine inverso rispetto alle righe. Es. La Vispa Teresa avea tra l’erbetta a volo sorpresa gentil farfalletta gentil farfalletta a volo sorpresa avea tra l’erbetta La Vispa Teresa Pseudo-codice reset(Teresa) InvertiRighe (Teresa)
PROGRAM InvertiRighe(Teresa,output); VAR Teresa:text; PROCEDURE StoreDisplay(VAR Teresa:text); {procedura ricorsiva:la prima linea letta è l’ultima mostrata a video} BEGIN ……….. END; PROCEDURE MostraRigheInvertite (VAR Teresa:text); BEGIN StoreDisplay(Teresa) END; { BODY } BEGIN reset(Teresa); MostraRigheInvertite END.
PROGRAM InvertiRighe(output,FInput); CONST LungMax=80; {massima lunghezza permessa alle stringhe } TYPE Stringa=STRING[LungMax]; VAR FInput: text; PROCEDURE StoreDisplay(VAR FInput:text); VAR Rigo: Stringa; BEGIN IF NOT eof(FInput) THEN BEGIN readln(FInput,Rigo); writeln('*push* ',Rigo); StoreDisplay(FInput); writeln(' *pop* ',Rigo) END END; {BODY } BEGIN assign(FInput,'C:\TP\ESEMPI\TERESA.TXT'); reset(FInput); StoreDisplay(FInput); readln END. Caso base *push* La vispa Teresa *push* avea tra l'erbetta *push* a volo sorpresa *push* gentil Farfalletta *pop* gentil Farfalletta *pop* a volo sorpresa *pop* avea tra l'erbetta *pop* La vispa Teresa
GCD:=GCD(N, M MOD N) GCD(30,18) GCD(18,12) GCD(12,6) GCD(6,0) No No No N=0? N=0? N=0? N=0? Si GCD GCD GCD GCD 6 GCD(18,12) GCD(12,6) GCD(6,0) =6 GCD(6,0) =6 GCD(12,6) FUNCTION GCD(M,N:integer):integer; BEGIN IF N=0 THEN GCD:=M ELSE GCD:=GCD(N,M MOD N); END; GCD(18,12) =6 GCD(30,18) =6
DIMOSTRAZIONE PER INDUZIONE DELLA CORRETTEZZA DELL’ALGORTIMO RICORSIVO PER IL GCD caso base Poniamo GCD(M,0)=M passo induttivo Dobbiamo ora dimostrare che GCD(M’,N’)=GCD(N, M MOD N) Supponiamo GCD(M’,N’)=X essendo N’=M MOD N questo implica per definizione che M=kN+N’ dove k è un intero allora M’=hX e N’=zX dove h e z sono interi ma N’=zX M=khX+zX=(kh+z)X=wX essendo allora sia M che N divisibili per X questo è il GCD(M,N) allora M=kN+zX inoltre N=M’=hX quindi
ESERCIZIO Sia dato un vettore A di interi di dimensione N, scrivere una funzione ricorsiva che restituisca la somma di tutti gli elementi di A. Sia dato un vettore A di interi di dimensione N, scrivere una procedura ricorsiva che restituisca la somma di tutti gli elementi pari di A e la somma di tutti gli elementi dispari di A. esercizi
FATTORIALE ALTRI ESEMPI Supponiamo di avere 3 lettere a b c . Vogliamo sapere quante permutazioni si possono fare con questi 3 caratteri. - ci sono 3 maniere per scegliere quale lettera mettere in terza posizione (genericamente n) abc acb cba - per ognuna delle 3 scelte precedenti ci sono 2 maniere diverse per scegliere la lettere da mettere in seconda posizione in totale 3*2 (genericamente n*(n-1)) abc bac acb cab cba bca - per ognuna delle 6 scelte precedenti c’è 1 sola maniera per scegliere la lettere da mettere in prima posizione in totale 3*2*1 (genericamente n*(n-1)…..*1) abc bac acb cab cba bca
FUNCTION Fattoriale(N:integer):integer; VAR Count, Product: integer; BEGIN Product:=1; FOR Count:=2 TO N DO Product:=Product*Count; Fattoriale:=Product END; FUNCTION Fattoriale (N:integer):integer; BEGIN IF N=0 THEN Fattoriale:=1 ELSE Fattoriale:=N*Fattoriale(N-1) END;
Dato un vettore di interi scrivere una procedura ricorsiva che trovi il valore del numero più grande in esso contenuto esercizi
PROGRAM Diagonale(input,output); { Assegnata una matrice MxM determinare, con una procedura ricorsiva, il valore massimo in ciascuna delle due diagonali principali.} TYPE Arrayx=Array[1..10,1..10] of integer; VAR A:arrayx; N, M1,M2:integer; PROCEDURE Diagona(Niniz,N1:integer;VAR A1:arrayx;VAR MD1,MD2:integer); {Scorri a partire dal basso ricorsivamente la matrice. Seleziona nelle due diagonali gli elementi più grandi} VAR N2,N3:integer; BEGIN IF N1=0 THEN BEGIN MD1:=0; MD2:=0; END ELSE BEGIN Diagona(Niniz,N1-1,A1,MD1,MD2); IF A1[N1,N1]>MD1 THEN MD1:= A1[N1,N1]; IF A1[N1,Niniz-N1+1]>MD2 THEN MD2:= A1[N1,Niniz-N1+1]; END; END; {******** MAIN************} N:=5; ScriviMatrice(A,N); Diagona(N,N,A,M1,M2); writeln(' I Massimi sono ', M1,' e ',M2); readln END.
FUNCTION Diagona(Niniz,N1:integer;VAR A1:arrayx;VAR MD1,MD2:integer):integer; {Scorri a partire dal basso ricorsivamente la matrice. Seleziona nelle due diagonali gli elementi più grandi e di questi stampa il maggiore} VAR N2,N3:integer; BEGIN IF N1=0 THEN BEGIN MD1:=0; MD2:=0; END ELSE BEGIN Diagona:=Diagona(Niniz,N1-1,A1,MD1,MD2); IF A1[N1,N1]>MD1 THEN MD1:= A1[N1,N1]; IF A1[N1,Niniz-N1+1]>MD2 THEN MD2:= A1[N1,Niniz-N1+1]; END; Diagona:=MD1; IF MD1 < MD2 Then Diagona:=MD2; END; .
Esempio con due casi base Problema: Assegnare agli elementi dell’Array di interi Ints, dei numeri compresi nell’intervallo 1..TotaleAssegnato. Ogni numero viene dato da tastiera. Il processo di lettura cessa o quando si introducono tutti i numeri concessi (MaxElements) oppure quando si introduce un numero negativo. Subito dopo si effettua l’assegnazione. • In questo caso i casi base possibili sono due: • abbiamo letto il massimo numero possibile di valori • abbiamo letto un numero negativo • In entrambi i casi la lettura deve terminare e si effettua l’assegnazione
TotaleAssegnato = 0 Temp< = 0 TotaleAssegnato = TotaleAssegnato + 1 Ints[TotaleAssegnato ] Temp Pseudo-Codice Inserisci(Left,TotaleAssegnato,Ints) {Indichiamo con Left quanti numeri positivi è ancora possibile assegnare e con Temp il valore letto } IF Left = 0 THEN gestisci il caso base N°1 ELSE read(Temp) IF Temp<=0 THEN gestisci il caso base N°2 ELSE Inserisci(Left-1,TotaleAssegnato,Ints) istruzioni dopo la ricorsione
PROGRAM CaseBase2(input,output); CONST MaxElements=4; TYPE IntsArray=ARRAY[1..MaxElements] OF integer; VAR Ints:IntsArray;Left, TotaleAssegnato, I:integer; PROCEDURE Inserisci(Left:integer; VAR TotaleAssegnato:integer; VAR Ints:IntsArray); VAR Temp: integer; BEGIN IF Left=0 THEN BEGIN readln; writeln('Non possono essere letti altri valori. '); TotaleAssegnato:=0; END ELSE BEGIN read(Temp); IF Temp <=0 THEN BEGIN writeln('E'' stato introdotto un numero negativo. '); TotaleAssegnato:=0; END ELSE BEGIN Inserisci(Left-1, TotaleAssegnato,Ints); TotaleAssegnato:= TotaleAssegnato+1; Ints[TotaleAssegnato]:=Temp END END END;
{BODY} BEGIN writeln('Inizia inserzione dati max= ',MaxElements); writeln; Inserisci(MaxElements,TotaleAssegnato,Ints); writeln(' ARRAY '); FOR I:=1 TO TotaleAssegnato DO writeln(Ints[I]); readln END. Inizia inserzione dati max= 4 Left TotaleAssegnato 1 push** 4 0 2 push** 3 0 3 push** 2 0 4 push** 1 0 Non possono essere letti altri valori. pop** 1 0 pop** 2 1 pop** 3 2 pop** 4 3 ARRAY 4 3 2 1 Inizia inserzione dati max= 4 Left TotaleAssegnato 11 push** 4 0 12 push** 3 0 -2 E' stato introdotto un numero negativo. pop** 3 0 pop** 4 1 ARRAY 12 11
Alcuni suggerimenti • Fatti importanti • come si passano i valori dei parametri • a- usare una chiamata per valore per determinare se il CASE BASE è verificato • b- se il processo ricorsivo è di tipo per accumulazione usare la chiamata per VAR per la variabile di accumulazione • c- usare una variabile locale se il suo valore è istanziato all’interno del processo ricorsivo per cui ad ogni passo della ricorsione riprende il valore di partenza PROCEDUREInserisci(Left:integer; VAR TotaleAssegnato:integer; VAR Ints:IntsArray); VAR Temp: integer;
BEGIN read(Temp); IF Temp <=0 THEN BEGIN writeln('E'' stato introdotto un numero negativo. '); TotaleAssegnato:=0; END ELSE BEGIN Inserisci(Left-1, TotaleAssegnato,Ints); TotaleAssegnato:= TotaleAssegnato+1; Ints[TotaleAssegnato]:=Temp END END ElaboraTesto(Rigo) Pseudo codice IF Not eof(InFile) THEN readln(Finput,Rigo) ElaboraTesto(Rigo) • l’ordine con cui le istruzioni vengono eseguite, se prima o dopo la chiamata ricorsiva • A- se una o più istruzioni riducono la dimensione del problema esse devono precedere la chiamata ricorsiva • B- se una o più istruzioni necessitano del risultato della ricorsione vanno poste dopo la chiamata ricorsiva
PROCEDURE Inserisci2(Left:integer; VAR Ints:IntsArray); VAR Temp: integer; BEGIN IF Left<>0 THEN BEGIN Inserisci(Left-1,Ints); read(Temp); Ints[Left]:=Temp END END END; Inserisci2 push** 5 push** 4 push** 3 push** 2 push** 1 pop** 1 10 pop** 2 20 pop** 3 30 pop** 4 40 pop** 5 50 ARRAY 10 20 30 40 50 Inserisci3 push** 5 10 push** 4 20 push** 3 30 push** 2 40 push** 1 50 pop** 1 pop** 2 pop** 3 pop** 4 ARRAY 50 40 30 20 10 PROCEDURE Inserisci3(Left:integer; VAR Ints:IntsArray); VAR Temp: integer; BEGIN IF Left<>0 THEN BEGIN read(Temp); Inserisci(Left-1,Ints); Ints[Left]:=Temp END END;