Indice generale 1.Introduzione .....................................................................................................................................2 2.Algoritmi e Informatica....................................................................................................................3 2.1.Che significa risolvere un problema?............................................................................................3 2.2.Algoritmi e Programmi..................................................................................................................4 3.Rappresentazione degli algoritmi.....................................................................................................7 3.1.La descrizione degli algoritmi – alcuni concetti............................................................................8 3.2.Algoritmi Diagrammi di flusso e pseudocodifica.......................................................................14 4.Strutture di controllo.......................................................................................................................17 5.Le strategie per la risoluzione di problemi.....................................................................................21 5.1.Interpretazione (comprensione del problema).............................................................................22 5.2.Il Modello (modellizzazione della situazione)............................................................................23 5.3.Procedimento risolutivo...............................................................................................................24 Conoscere l'argomento..............................................................................................................24 Sfruttare l'esperienza.................................................................................................................24 Procedere a ritroso....................................................................................................................25 Scomporre i problemi................................................................................................................25 5.4.Esecuzione...................................................................................................................................26 5.5.Verifica dei risultati.....................................................................................................................28 1 ALGORITMI La teoria è quando si sa tutto e niente funziona. La pratica è quando tutto funziona e nessuno sa il perchè. In questo caso abbiamo messo insieme la teoria e la pratica: non c'è niente che funziona...e nessuno sa il perchè! (Albert Einstein) 1. Introduzione La storia dell’informatica non riguarda solo lo sviluppo degli strumenti di calcolo e del moderno computer, ma include anche lo studio dei procedimenti di calcolo per risolvere problemi (sia quelli eseguiti dall’uomo che quelli eseguiti automaticamente dagli strumenti di calcolo). In informatica, i procedimenti di calcolo vengono comunemente denominati algoritmi. In modo informale possiamo dire che un algoritmo è una procedura di calcolo descritta in modo sufficientemente preciso atta a risolvere un determinato problema. Nel corso dei secoli la concezione e il modo di descrivere gli algoritmi è cambiato in modo significativo e oggi è possibile scrivere algoritmi che possono essere eseguiti automaticamente su una macchina. Utilizziamo algoritmi nella vita quotidiana tutte le volte che, ad es., seguiamo le istruzioni per il montaggio di una apparecchiatura, per impostare il ciclo di lavaggio di una lavastoviglie, per prelevare contante da uno sportello Bancomat, per la cena, per fare il bagno, per la sveglia, ecc. Esempio: Algoritmo Risveglio 1.Alzarsi dal letto 2.Togliersi il pigiama 3.Fare la doccia 4.Vestirsi… 5……. Quindi il concetto di algoritmo non è per forza legato ad azioni svolte da una macchina , ma ha un significato molto più ampio. Il calcolatore ha 60 anni… gli algoritmi hanno 4000 anni! Primi algoritmi babilonesi ed egiziani. (2000-1600 A.C.) Tavolette babilonesi forniscono ‘algoritmi’ nella forma di ripetuti esempi di sequenze di operazioni. Dalla sequenza di esempi si può inferire l’algoritmo. In questo caso si poteva trattare ad esempio di sequenza di operazioni matematiche per il calcolo di un risultato. 2 2. Algoritmi e Informatica Il concetto di algoritmo presuppone l’esistenza di un esecutore, cioè colui che deve effettivamente compiere le operazioni. L’esecutore è spesso, ma non sempre, il computer. Benché la definizione di algoritmo sia indipendente dall’esecutore, esso è spesso associato al calcolatore elettronico. Un algoritmo deve essere pertanto definito in modo che possa essere interpretato ed eseguito correttamente dal calcolatore. 2.1. Che significa risolvere un problema? A partire da un insieme di dati iniziali (Input) l'algoritmo compie una serie di azioni su di essi fino a produrre un risultato finale (output). Dati iniziali e finali (input e output):i dati di input sono i dati che vengono posti in ingresso(es da tastiera) e i dati di output sono inviati in uscita dal calcolatore verso l'esterno. L'elaborazione viene descritta dall'algoritmo ed eseguita da un esecutore. Un esecutore è il soggetto che compie le azioni descritte dall'algoritmo. Es: torta di carote Vogliamo essere capaci di specificare la strategia seguita dal passo di elaborazione in modo da farla eseguire ‘automaticamente’ dal computer quindi dobbiamo riuscire a descrivere accuratamente i vari passi della soluzione attraverso azioni che l'esecutore è in grado di effettuare e con un linguaggio che è in grado di comprendere. 3 2.2. Algoritmi e Programmi Poiché vogliamo trattare gli algoritmi in ambito informatico lo schema successivo descrive il procedimento e gli agenti coinvolti. Abbiamo già chiarito il significato di input e output. Sono i dati forniti forniti in ingresso al calcolatore. Esempio: se si tratta di fare la somma di due numeri, i dati di input saranno i due numeri chiamiamo a1 e a2 che voglio sommare. L'elaborazione è il procedimento risolutivo che deve prima essere pensato e poi essere eseguito. L'algoritmo viene descritto dunque da un agente umano. Per poter essere eseguito su una macchina deve essere codificato in un linguaggio comprensibile al calcolatore. Dunque un programma è la codifica in un linguaggio di programmazione del procedimento risolutivo. Il programma viene eseguito dal calcolatore. Diamo adesso le definizioni formali. Def. Algoritmo Per algoritmo si intende una sequenza finita di azioni elementari e non ambigue che definiscono una sequenza di operazioni che possono essere eseguite da un opportuno esecutore e mediante le quali si risolve una classe di problemi. Risolvere un problema significa individuare un procedimento che permetta di arrivare al risultato partendo da dati noti. Per istruzione elementare si intende una istruzione non ulteriormente scomponibile in relazione all'esecutore. Cioè una istruzione elementare per un determinato esecutore può non esserlo per un altro. Richiameremo questo concetto successivamente in qualche esempio pratico. Def. Programma. Il programma è la rappresentazione di un algoritmo utilizzando un linguaggio non ambiguo e direttamente comprensibile dal computer, cioè un linguaggio di programmazione. 4 (Def) Istruzione. Le singole azioni del procedimento risolutivo, codificate in linguaggio di programmazione prendono il nome di istruzioni E' possibile elencare alcune proprietà e caratteristiche che un algoritmo deve possedere. Un algoritmo deve essere: • generale: il metodo deve risolvere una classe di problemi e non un singolo problema (ad esempio deve essere in grado di calcolare l'area di tutti i triangoli e non solo quella di un particolare triangolo) • finito: le istruzioni che la compongono devono essere in numero finito. • completo: deve contemplare tutti i casi possibili del problema da risolvere (per esempio nel calcolo della divisione deve tenere conto anche dei casi 0/0 e a/0 e dire cosa succede in questi casi) • non ambiguo: ogni istruzione deve essere definita in modo preciso ed univoco, senza alcuna ambiguità sul significato dell’operazione. Ossia l'esecutore deve poter interpretare in modo univoco ogni singola azione • eseguibile (o realizzabile): deve esistere un agente di calcolo in grado di eseguire ogni istruzione in un tempo finito • limitato nel tempo: il numero di volte che ogni azione viene eseguita deve essere finito. Cioè deve avere durata limitata nel tempo. n.b. L'ultimo caso è un caso in cui spesso i principianti, ma a volte anche i veterani, cadono in errore. E' il caso in cui si generano algoritmi che vanno in “loop” cioè non terminano mai la loro l'esecuzione. Pertanto quando ci troveremo in questa situazione dovremo interrompere brutalmente e forzatamente l'esecuzione del programma. Esempio: supponiamo di voler calcolare la somma di 1+2+3+4+5+... fino a quando la somma è superiore a 50. Allora ripeto 1+2+3+4+5+6+7+8+9+10 e mi fermo qui. Supponiamo di cambiare leggermente il problema in:calcolare la somma di 1+2+3+4+5+... fino a quando la somma è inferiore a 0. Questa condizione non potrà mai avverarsi, pertanto in questo caso sommerei 1+2+3+4+.....all'infinito. Occorre quindi porre attenzione alla condizione di STOP di un procedimento. Così strutturato, un algoritmo permette di risolvere il problema per cui è stato pensato. Abbiamo visto che per esemplificare il concetto di algoritmo si usano gli esempi delle ricette di cucina o altri esempi del quotidiano, ma gli esempi più significativi si trovano in matematica e in informatica, dove sequenze anche molto complicate di azioni opportunamente codificate possono servire per comunicare tra studiosi particolari procedimenti oppure per far compiere ai 5 computer determinate azioni. Ma insomma, una ricetta è proprio un algoritmo? … NO, ovvero è molto simile ma con due importanti differenze: La sequenza di azioni contiene spesso degli elementi di ambiguità risolti da un esecutore intelligente es: spesso non si specificano gli strumenti da utilizzare, confidando che l’esecutore umano sbatta le uova nel posto giusto es: sale q.b. Non tutti i possibili casi vengono specificati es: è chiaro che se c’e’ puzza di bruciato conviene spegnere il forno, anche se la ricetta non lo specifica (si confida nelle capacità deduttive dell’esecutore) Esempi di algoritmi: addizione o moltiplicazione di più numeri a più cifre calcolo delle soluzioni di una equazione di primo grado calcolo del perimetro e dell'area di una figura geometrica etc Un'altra cosa importante da notare è che, anche nel nostro quotidiano, un problema può avere più soluzioni, alcune saranno migliori altre peggiori. Per esempio potrei risolvere il problema di andare da Roma a Milano seguendo diverse soluzioni (tragitti). Nello stesso modo un problema può essere risolto da più algoritmi. In questo caso diremo che Due algoritmi si dicono equivalenti se risolvono lo stesso problema. Oppure in modo più preciso: Due algoritmi si dicono equivalenti se in corrispondenza degli stessi dati di input producono gli stessi dati di output. 6 3. Rappresentazione degli algoritmi Ma come si scrive un algoritmo? In modo informale posso dettagliare i vari passi del procedimento, come le istruzioni per fare la torta o per montare un armadio o per andare da Roma a Milano. In informatica abbiamo però bisogno di eliminare le ambiguità che potrebbero derivare dall'uso del linguaggio naturale che è quello che usiamo ogni giorno. Per esempio considerando una ricetta di cucina, la frase “sale q.b” non è univocamente interpretabile e precisa. Potremmo dire che è abbastanza ambigua e soggettiva. Poiché abbiamo elencato la non-ambiguità come una delle caratteristiche che un algoritmo deve possedere occorre inventarsi un formalismo che consenta di scrivere un procedimento (algoritmo) in modo preciso e non ambiguo. Uno algoritmo può essere rappresentato in vari modi: formula, sequenza di istruzioni, disegno, a parole... etc. I formalismi fanno uso di una serie di simboli base e di regole per la combinazione di tali simboli. Esempio: Algoritmo Simboli base I metodi da usare per rappresentare in modo efficiente ed esaustivo gli algoritmi vengono detti formalismi di codifica poiché rappresentano l’algoritmo mediando tra il semplice linguaggio comune e il formale linguaggio matematico; ne esistono di diversi tipi con la caratteristica comune di essere ben definiti e non ambigui. Negli anni si sono sviluppate varie tecniche di rappresentazione, ne descriveremo due: i diagrammi di flusso o flow-chart e la pseudocodifica. 7 3.1. La descrizione degli algoritmi – alcuni concetti Come detto precedentemente è opportuno adottare, per descrivere un algoritmo, una forma ordinata e precisa di esposizione che ci metta al riparo dall’ambiguità del linguaggio naturale. Descrivere un algoritmo in linguaggio naturale ha il vantaggio di essere patrimonio comune ad un vasto numero di persone, ma ha lo svantaggio di essere: non preciso, complesso e inadeguato. Prima di passare alla descrizione (o rappresentazione degli algoritmi, cominciamo a formalizzare alcuni concetti. Variabili La variabile è un oggetto dotato di una identità (nome), di uno stato (contenuto o valore), e di un tipo (tipo di contenuto e possibili operazioni su di esso). 7 Contenuto SOMMA Nome • Il contenuto di una variabile può variare durante il processo di esecuzione • Il nome di una variabile è composto da una lettera seguita da un numero arbitrario di caratteri: a, b1, C24, pippo, x24. Una variabile può contenere solo valori appartenenti ad un unico tipo di dato: • variabili intere: solo valori interi (1, 50, 1002) • variabili reali: solo valori reali (1.5, 3.4, 2053.76) • variabili carattere: solo caratteri (‘a’, ‘c’, ‘1’) Costanti Una costante non si modifica durante il processo di esecuzione. Esempi di costanti sono: 1, 5, 3890 2.3, 3.5, 234.33 ‘a’, ‘f’, ‘3’ “ciao” costanti intere costanti reali costanti carattere costante stringa Operazione di assegnamento L’operazione di assegnamento permette di cambiare lo stato di una variabile, ovvero assegna un valore ad una variabile. La sintassi dell’operazione di assegnamento è la seguente: nome_variabile ← espressione Esempi di assegnamento validi sono i seguenti: a←1 a←a+1 b←c+3 c←’b’ pippo←4. 8 L’operazione di assegnamento funziona nel modo seguente: 1. Si valuta l’espressione a destra del simbolo ← 2. si assegna il valore trovato in (1) alla variabile che sta a sinistra del simbolo ← Un concetto importante da ricordare è che l’operazione di assegnamento è distruttiva: il valore contenuto nella variabile precedentemente ad una operazione di assegnamento è definitivamente perduto. Se la variabile x contiene 5 dopo x←7 del valore 5 non si ha più memoria. x←7 5 7 x x Problema: “date due variabili a e b scambiarne il contenuto” La soluzione immediata, ma sbagliata è la seguente: 1. a←b 2. b←a Infatti essendo l’assegnamento distruttivo il valore di viene irrimediabilmente perduto e alla fine delle due operazioni troveremo che sia a che b contengono lo stesso valore e cioè il valore iniziale di b. La soluzione corretta prevede l’uso di una terza variabile, detta variabile di comodo, dove salvare il contenuto della variabile a prima di assegnarli il valore di b: 1. aiuto←a 2. a←b 3. b←aiuto. Tutte le variabili prima di poter essere utilizzate in una espressione devono essere inizializzate, cioè devono contenere un valore. Una variabile non inizializzate è una variabile senza valore e utilizzarla è un errore, ad esempio la sequenza di istruzioni: 1. a←b+1 2. b←5 è errata poiché in (1) la variabile b non è inizializzata, è senza contenuto. Ingresso e Uscita Un algoritmo deve poter comunicare con il mondo esterno per ricevere i dati in ingresso e poter restituire i risultati dell’elaborazione in uscita. Le istruzioni di ingresso/uscita (input/output) sono usate per comunicare con il mondo esterno. 9 L’istruzione di ingresso è: Leggi nome_var1,nome_var2,.......,nome_varn dove nome_var1,nome_var2,.....,nome_varn è la lista delle variabili di ingresso. L’istruzione di uscita è: Scrivi espr1,espr2,......,esprn dove espr1,espr2,.....,esprn è la lista delle espressioni in uscita. Come si può notare per la Leggi utilizziamo solo variabili, mentre per la Scrivi utilizziamo delle espressioni, ciò è dovuto al fatto che l’operazione di scrittura non ha alcun effetto sulla lista dei parametri che utilizza, mentre l’operazione di lettura ha lo stesso effetto dell’operazione di assegnamento. 1. a←1 2. b←2 3. Scrivi a,b La sequenza di istruzioni sopra descritte permette di trasmettere all’esterno i valori di a e b (1 e 2), che rimangono invariati anche dopo la loro trasmissione. Lo stesso effetto non si ha quando utilizziamo le istruzioni di lettura: 1. 2. 3. 4. a←1 b←2 Leggi a,b Scrivi a,b I valori di a e b vengono modificati dalla istruzione di lettura, infatti il risultato in uscita sarà dato dai valori inseriti dall’utente. Ricordiamo infine che l’istruzione Leggi ha lo stesso effetto dell’operazione di assegnamento e quindi può essere utilizzata per inizializzare le variabili. Espressioni Tutti sanno che: (3X-Y)(X+2) è una espressione aritmetica dove X e Y sono le variabili e 3 e 2 sono le costanti. Sappiamo anche che fino a quando non si attribuiscono particolari valori alle variabili essa non rappresenta alcun valore e che, quando affermiamo che X vale 1 e Y vale 2 e operiamo un processo di valutazione l’espressione vale 3. In seguito supporremo che l’esecutore disponga di un valutatore che riceve un’espressione aritmetica, il suo ambiente di valutazione (i valori delle variabili) e restituisce il risultato. In informatica tutti gli operatori devono essere esplicitati, non possono essere sottintesi: (3X-Y)(X+2) NO (3*X-Y)*(X+2) SI 10 Gli operatori sono simboli speciali che rappresentano elaborazioni di tipo matematico, quali la somma e la moltiplicazione. I valori che l'operatore usa nei calcoli sono chiamati operandi. Esempi di operatori aritmetici che utilizzeremo sono: +(somma), -(differenza), *(prodotto), /(divisione(intera), %(modulo -cioè resto della divisione tra due numeri) etc... Un'espressione è una combinazione di operandi(valori, variabili) e operatori. Esempio di espressioni aritmetiche valide sono • • • • • • 3 X 3+Y X+Y*2 (3+X)/4 X+Y2 (Corretta se si suppone Y2 nome di una variabile) Dove la correttezza della espressione è data dalla giustapposizione di operandi e operatori come già la conosciamo dalla matematica. Espressioni non valide sono: • • • • + /+ 3X+Y X*+Y2 Mancano gli operatori Mancano gli operatori e non posso giustapporre due operandi Non posso giustapporre due operandi Non posso giustapporre due operatori Le espressioni devono essere scritte in questo modo per evitare delle ambiguità. Esercizio: • • X5 è una variabile o una espressione? 4Y è una espressione o un nome non valido di variabile? Espressioni logiche (Condizioni o Termini Booleani) Le condizioni vengono dette anche "termini (o espressioni) booleane", dal nome del matematico inglese Boole che per primo introdusse l'impiego di simboli per rappresentare gli operatori logici. Una condizione (o espressione logica o termine booleano) può essere più precisamente descritto come una formula (equazione, disequazione o altro tipo di relazione). Ese: a>3 4<=7+2 x=y sono esempio di espressioni logiche. Le espressioni logiche producono come risultato due soli valori {V,F} cioè (vero/falso; true/false; 0/1) Un'espressione logica è formata da operatori (variabili o valori costanti) e operatori relazionali. Gli operatori relazionali sono gli operatori di confronto che già conosciamo dalla matematica > (maggiore) >=(maggiore o uguale) <(minore) <=(minore o uguale) !=(diverso) = (uguale) 11 Le espressioni logiche semplici possono essere combinate per costruire espressioni logiche composte. La combinazione si realizza attraverso gli operatori logici che sono: • • • NOT AND OR (citiamo solo questi per il momento) L'operatore AND significa che l’espressione condizionale è vera se e solo se sono vere entrambe le condizioni. Es: 4>5 and 3>2 è falsa 5>4 and 3>2 è vera L'operatore OR significa che l’espressione condizionale è vera se condizioni è vera. Es: 4>5 and 3>2 è vera 5<4 and 3<2 è falsa almeno una delle due L'operatore NOT anteposto davanti ad una espressione logica nega il valore dell'espressione. Es: not(4>5) è falsa not (1>3) è vera Possiamo ricavare le tabelle di verità che descrivono quanto detto. Date due espressioni logiche x e y, le seguenti tabelle descrivono il valore dell'espressione finale in base ai valori delle espressioni di partenza (x e y). x FALSO FALSO VERO VERO y FALSO VERO FALSO VERO x and y FALSO FALSO FALSO VERO x FALSO FALSO VERO VERO y FALSO VERO FALSO VERO x or y FALSO VERO VERO VERO x VERO FALSO not x FALSO VERO 12 Precedenza tra gli operatori: Operatore Nome Not Not * / % Moltiplicazione Divisione Modulo + - Addizione Sottrazione < <= > >= Minore Minore Uguale Maggiore Maggiore Uguale = != Uguale Diverso AND AND OR OR <-- Assegnamento Esempio: a AND NOT b OR c L’esempio precedente è ambiguo perché potrebbe essere interpretato nei modi seguenti: ( a AND (NOT b)) OR c a AND ( (NOT b) OR c) a AND ( NOT ( b OR c)) Per risolvere l’ambiguità o si usano obbligatoriamente le parentesi oppure si assume un ordine di priorità degli operatori, quello usuale è NOT, AND, OR. Questo vuol dire che nel valutare una espressione prima si valutano i NOT poi gli AND e poi gli OR, questo corrisponde alla prima interpretazione riportata. Questo è anche l’ordine con cui si risolve comunemente l’ambiguità nelle espressioni algebriche numeriche con gli operatori -, × e + infatti l’espressione a × –b + c viene comunemente interpretata come ( a × ( - b ) ) + c. 13 3.2. Algoritmi Diagrammi di flusso e pseudocodifica Passiamo adesso a descrivere gli strumenti per rappresentare gli algoritmi. Vi sono vari strumenti per rappresentare gli algoritmi, il più noto, e anche il più vecchio, è il Diagramma a Blocchi. Negli anni ’60 erano praticamente l’unico strumento preso in considerazione per esprimere un algoritmo. Gli anni ’70 hanno notevolmente sminuito l’importanza della diagrammazione, sono infatti sorti nuovi strumenti che hanno contribuito ad evidenziare alcuni difetti di questa forma espressiva, soprattutto sotto la spinta di una nuova generazione di linguaggi di programmazione. Noi analizzeremo due strumenti di rappresentazione: • Diagrammi a Blocchi • Notazione Lineare Strutturata (o pseudocodifica) La differenza tra i due: flow-chart - strumento grafico, ha un maggiore impatto visivo pseudocodifica - utilizza un linguaggio descrittivo costituito da un insieme di parole (sottoinsieme del linguaggio naturale) e da regole sintattiche per costruire le frasi. Piu rapido per appuntare bozze di soluzioni. Semplice (…) passare dalla descrizione in pseudo codice alla effettiva descrizione in un linguaggio di programmazione Ricordiamo che un algoritmo è una procedura di calcolo costituita da un numero finito di passi elementari e che termina, producendo la soluzione del problema, dopo aver effettuato un numero finito di operazioni. Con “passi elementari” si intendono delle istruzioni che facciano diretto riferimento alle capacità di base dell’esecutore automatico a cui viene fornito l’algoritmo per risolvere il problema. Tale esecutore automatico, come è noto, oltre a saper eseguire le quattro operazioni aritmetiche (somma, sottrazione, divisione e moltiplicazione) è in grado di memorizzare i dati in alcune variabili di memoria identificate da un nome e di eseguire le seguenti sette istruzioni fondamentali che possiamo ritrovare in tutti i linguaggi di programmazione “imperativi/procedurali” (spiegheremo meglio il significato di imperativo/procedurale): begin (o inizio): inizia l’esecuzione dell’algoritmo. assegnamento: consente di attribuire ad una variabile un valore costante, il valore memorizzato in un’altra variabile o il risultato di un’espressione aritmetica tra valori costanti o valori contenuti in altre variabili (es.: “a = 3”, “a = b +3c”, ecc.); leggi (operazione di ingresso o input): consente all’esecutore di acquisire dall’esterno un’informazione e di memorizzarla in una variabile di memoria (es.: “leggi a”); scrivi (operazione di uscita o output): consente all’esecutore di visualizzare/stampare all’esterno un dato costante, un valore memorizzato in una variabile o il risultato di un’espressione aritmetica che coinvolga valori costanti o variabili (es.: “scrivi Sì”, “scrivi 15”, “scrivi a”, “scrivi a +7b”, ecc.); 14 se... allora... altrimenti: consente all’esecutore di valutare un’espressione booleana (una condizione logica) il cui valore può essere solo “vero” o “falso”; in base al risultato della valutazione della condizione, vengono eseguite delle istruzioni oppure, in caso contrario, delle altre (es.: “se a > 0 allora a = a −1 altrimenti a = 20”); ripeti...finchè (oppure esegui mentre...ripeti): consente all'esecutore di ripetere un insieme di azioni per un certo numero di volte in base al valore di una condizione logica. stop (o fine): termina l’esecuzione dell’algoritmo. Flow_chart Ad ognuna delle precedenti istruzioni elementari corrisponde un simbolo con cui è possibile costruire una “rappresentazione grafica” dell’algoritmo, ossia un diagramma di flusso. Ogni diagramma di flusso ha un unico punto di inizio ed un unico punto terminale: entrambe queste istruzioni (start e stop) sono rappresentate da un ellisse; nella pseudo-codifica di un algoritmo espressa come una sequenza di istruzioni elementari l’istruzione start è implicita visto che l’algoritmo inizia sempre dal primo passo della pseudo-codifica. Le istruzioni di assegnazione, in generale le più frequenti negli algoritmi, sono rappresentate all’interno di un rettangolo,mentre le operazioni di input e di output (leggi e scrivi) sono rappresentate con dei parallelogrammi. Le condizioni sono espresse all’interno di rombi ed infine le ripetizioni (ripetifinchè...) sono rappresentati con delle frecce che ritornano su blocchi precedenti. Notazione Lineare Strutturata (o pseudocodifica) Si utilizzano le seguenti parole chiave: INIZIO/FINE (o BEGIN/END): per iniziare e terminare l'algoritmo LEGGI/SCRIVI: per leggere da input e scrivere su output rispettivamente ← per l'assegnamento SE...ALLORA....ALTRIMENTI...FINE_SE: per eseguire delle azioni condizionate dal valore di verità di una espressione logica RIPETI FINCHE' (o MENTRE..ESEGUI): Per eseguire delle iterazioni in base al valore di una condizione logica. 15 Esempio:Calcolo della somma tra due numeri interi x, y Algoritmo somma1 0.INIZIO 1.LEGGI (x,y) 2.somma<--x+y 3.SCRIVI (somma) 4.FINE Algoritmo somma2 0.INIZIO 1.LEGGI (x,y) 2.SCRIVI (x+y) 3.FINE Esempio:Calcolo del massimo tra due numeri interi x, y Algoritmo massimo1 0.INIZIO 1. LEGGI (x,y) 2. SE x>y ALLORA 2.1. massimo<--x ALTRIMENTI 2.2. massimo<--y FINESE 3. SCRIVI(“il massimo è”, massimo) 4. FINE Algoritmo massimo2 0. INIZIO 1. LEGGI (x,y) 2. diff<--x-y 3. SE diff>0 ALLORA 3.1. massimo<--x ALTRIMENTI 3.2 massimo<--y FINESE 4. SCRIVI(“il massimo è”, massimo) 5. FINE 16 4. Strutture di controllo In un algoritmo sono normalmente presenti sequenze di passi in successione, ma anche passi decisionali che servono a controllare la particolare sequenza dinamica che deve essere eseguita dall’esecutore per risolvere uno specifico caso di ingresso. Le istruzioni di controllo servono per influenzare il flusso di esecuzione di un programma. Tramite le istruzioni di controllo è possibile “compiere scelte” all’interno di un programma La scelta avviene valutando il valore di verità su di una condizione. Si può dimostrare che per descrivere qualunque algoritmo sono sufficienti solo tre tipologie di strutture:L'istruzione di sequenza, l’istruzione di selezione e l’istruzione iterativa. Lo schema che alla fine è emerso, detto "programmazione strutturata", si propone di dare l'aspetto di un flusso ordinato tra un inizio ed una fine a programmi che di per sé sarebbero intricati. Il modello è il "programma sequenziale", nel quale si applicano le varie operazioni una di seguito all'altra, in modo ordinato, senza alternative possibili. Tuttavia (come si evince dagli esempi precedenti) la semplice sequenza non può esprimere tutta la potenza degli algoritmi poiché questa è essenzialmente contenuta nella capacità di scegliere tra due alternative. Come si possono dunque conciliare queste due esigenze? L'idea è semplicissima: basta imitare il linguaggio naturale. In un linguaggio naturale un algoritmo è già espresso in una forma strutturata che corrisponde allo "svolgimento temporale" di una particolare computazione, quello cioè che si chiama un "processo". In un linguaggio naturale le costruzioni utilizzate sono "fai questo ... , dopo fai quello ... ", "se ... fai questo ... altrimenti fai quello ... ", "finché ... fai così ... " oppure "ripeti 5 volte questo ... ", che sono proprio le costruzioni di "controllo del flusso", sequenza, alternativa e ciclo, che saranno al centro di tutta la nostra discussione. 17 Sequenza: Istruzioni semplici di Ingresso, Uscita, Assegnazione Selezione: Esprime la scelta tra due possibili azioni mutuamente esclusive Ciclo o Iterazione: Esprime la ripetizione di un’azione A questo risultato si arrivò nel 1966 con il teorema di Jacopini-Böhm: ogni programma (di Turing) può essere espresso con sequenze, alternative o cicli di blocchi di istruzioni. Il seguito chiariremo e approfondiremo i termini della questione soprattutto riguardo al fondamentale concetto di blocco. 18 SEQUENZA 0. INIZIO 1. Alzarsi dal letto 2. Togliersi il pigiama 3. Fare la doccia 4. Vestirsi 5. Fare colazione 6. Prendere il bus per andare a scuola 7. FINE ALTERNATIVA 0. INIZIO 1. Alzarsi dal letto 2. Togliersi il pigiama 3. Fare la doccia 4. Vestirsi 5. Fare colazione 6. SE piove ALLORA 6.1 Prendere ombrello 7. Prendere il bus per andare a scuola 8. FINE 0. INIZIO 1. Alzarsi dal letto 2. Togliersi il pigiama 3. Fare la doccia 4. Vestirsi 5. Fare colazione 6. SE sciopero mezzi pubblici ALLORA 6.1. Prendere macchina 7. ALTRIMENTI 7.1. Prendere il bus 8. FINE 19 RIPETIZIONE (ITERAZIONE) 0. INIZIO 1. Alzarsi dal letto 2. Togliersi il pigiama 3. Fare la doccia 4. Vestirsi 5. Fare colazione 6. MENTRE piove 6.1. Restare in casa 7. Prendere il bus per andare a scuola 8. FINE Le strutture di controllo si possono combinare tra loro in vari modi, in sequenza o annidando una struttura dentro l'altra. Esempio: Come si mette l’olio nell’auto apri il serbatoio dell'olio ripeti prendi una lattina; se chiusa allora aprila ripeti versa olio se cade allora pulisci finché c’è olio nella lattina finché il livello è sotto il minimo Fino a questo momento abbiamo visto come “spezzettare” la descrizione di un procedimento risolutivo in azioni semplici ed elementari. E ci siamo sforzati, con l'aiuto di esempi presi dal nostro quotidiano, di tradurre i nostri comportamenti come composizione di microazioni opportunamente combinate tra loro. Nei paragrafi successivi vedremo come affrontare invece la soluzione di problemi più complessi e ci concentreremo sulle tecniche per la soluzione dei problemi più complessi. La risoluzione di un problema complesso richiede un approccio metodologico altrimenti si rischia di andare “alla cieca” e tirare fuori soluzioni non ottimali o inefficienti. Un buon algoritmo è come un coltello affilato – fa esattamente ciò che si suppone debba fare, applicando una quantità minima di forza; mentre usare un algoritmo sbagliato per risolvere un problema è come tagliare una bistecca con un giravite: si può anche arrivare ad un risultato accettabile, ma facendo sicuramente uno sforzo ben maggiore di quanto non fosse necessario. Inoltre il risultato non si può certo definire elegante. 20 5. Le strategie per la risoluzione di problemi. Scrivere un algoritmo dunque significa scrivere un procedimento risolutivo. Per poter arrivare a risolvere un problema è necessario adottare alcune strategie per la risoluzione. Quando ci si accinge a risolvere un problema, per giungere rapidamente ad una soluzione sembrerebbe sufficiente conoscere l’argomento, saper sfruttare l’esperienza accumulata e possedere le opportune risorse. Spesso, però, ci si accorge che, pur possedendo una certa abilità e le competenze necessarie, non si riesce a raggiungere la meta. Quante volte ad esempio, vi è capitato in un compito in classe di non riuscire a risolvere un problema pur avendo a disposizione tutte le formule necessarie riportate sul fogliettino? Una considerazione che aiuta a ridurre le possibilità di insuccesso è sicuramente legata al metodo con cui si affronta il problema: analizzare in modo sistematico la situazione, infatti, anche se di per sé non porta alla soluzione, permette di isolare i nodi di difficoltà e di conseguenza favorisce, sfruttando al meglio le capacità del risolutore, quanto meno un avanzamento dei lavori. Anche se quando si affrontano problemi semplici molto spesso non ci si accorge dell'importanza di un approccio sistematico, poiché le prime fasi dell'analisi vengono svolte in modo inconscio e quasi automatico, quando il problema presenta un certo grado di complessità risulta quasi naturale affrontarlo con metodo. In ogni caso è molto importante effettuare un buon lavoro di preparazione. Naturalmente qualsiasi metodo sistematico di analisi non va considerato alla stregua di una camicia di forza in cui imbrigliare intuito e fantasia, ma solo come un valido strumento da affiancare ad essi. Descriviamo di seguito come affrontare la soluzione dei problemi. La nostra strategia si compone di varie fasi che sono descritte nel grafico seguente. 1. interpretare l’enunciato del problema e definire gli obiettivi da realizzare; 2. individuare i dati del problema e costruire un modello opportuno (modello è una 3. rappresentazione della realtà privata degli aspetti superflui alla soluzione del problema); descrivere il procedimento risolutivo individuando le operazioni da compiere sui dati iniziali per ottenere i risultati finali; 21 4. eseguire nell’ordine le operazioni descritte nel processo risolutivo (il risolutore in questa fase è detto esecutore); 5. verificare se i risultati ottenuti rispondono alle finalità del problema reale (attendibilità). Dettagliamo adesso ogni singola fase. 5.1. Interpretazione (comprensione del problema). Le semplicità dei problemi (algoritmi) affrontati nei paragrafi precedenti non mettere in risalto la fase chiamata interpretazione, poiché ci siamo concentrati sull'aspetto risolutivo e non sulla comprensione del testo del problema. Esempio: calcolo della somma tra due numeri non richiede alcuna particolare interpretazione. E' chiaro l'obiettico che si vuole raggiungere. I problemi vengono spesso presentati in modo confuso se non addirittura fuorviante. Purtroppo però i problemi confusi non sono solo relegati nelle pagine dei libri di enigmi, ma si riscontrano nella realtà della vita quotidiana dove la loro presenza non sarebbe affatto auspicabile. Di conseguenza, prima di lanciarsi alla ricerca della soluzione di un problema, è indispensabile formularne il testo, in modo da: • • • • precisare gli obiettivi evidenziare le regole e i dati impliciti ed espliciti eliminare ogni tipo di ambiguità eliminare i dettagli inutili 22 5.2. Un modello Il Modello (modellizzazione della situazione) è la rappresentazione schematizzata di un problema. Cioè sono delle semplificazioni della situazione reale, dove vengono messi in evidenza esclusivamente gli aspetti che si ritengono utili o significativi per la risoluzione e che sono il risultato della fase di comprensione del problema. I modelli possono essere diversi: • modello grafico • modello verbale • modello tabellare • modello simbolico Spesso il modello di solito è un modello grafico che grazie alla sua potenza espressiva, consente di rappresentare efficacemente un gran numero di situazioni. In altre occasioni invece un modello verbale o un modello tabellare risultano più significativi. In ogni caso il processo di modellizzazione porta sempre ad una astrazione della situazione reale, cioè una rappresentazione di carattere più generale che potrà essere riutilizzata in situazioni analoghe. Dunque un modello è una rappresentazione più semplice di un problema che contiene tutti e soli gli elementi effettivamente determinanti per la soluzione. 23 5.3. Procedimento risolutivo Mentre per la riformulazione del testo del problema è stato possibile fornire una serie di passi da seguire, la ricerca della soluzione è un'attività logico-intellettuale in cui conoscenze, intuizioni, tentativi e lavoro metodico si fondono per consentire di definire a priori in che ordine le varie attività devono essere disposte per facilitare il raggiungimento della meta. Di conseguenza non è possibile dare una ricetta sicura per risolvere qualsiasi problema (avremmo risolto tutti i mali del mondo!) , ma solo un elenco di suggerimenti da tener presente nell'attività risolutiva. Tuttavia si possono indicare alcune strategie che si possono applicare in molte situazioni. Le strategie sono: • • • • conoscere l'argomento sfruttare l'esperienza procedere a ritroso scomporre i problemi Conoscere l'argomento Sicuramente la conoscenza degli argomenti a cui la situazione fa riferimento è una condizione indispensabile per un coretto approccio alla risoluzione del problema. Per questo quando la conoscenza degli argomenti è scarsa, occorre procurarsi un minimo di documentazione. Sfruttare l'esperienza La ricerca della soluzione sarebbe un'attività lunga e faticosa se ogni problema dovesse essere affrontato come se fosse un caso isolato. Fortunatamente però i problemi sono spesso correlabili tra loro ed è quindi possibile, una volta individuatene la similarità, sfruttare il procedimento risolutivo di uno di essi per risolvere gli altri. Nella ricerca della soluzione ad un nuovo problema sarà dunque possibile sfruttare l'esperienza e utilizzare condotte risolutive già sperimentate con successo, apportandovi eventualmente piccole modifiche. Dunque maggiore è il numero e la tipologia dei problemi risolti e maggiore è l'esperienza accumulata e di conseguenza maggiore è la capacità di applicare i processi risolutivi a nuove situazioni. 24 Procedere a ritroso Viene spontaneo pensare che per scoprire la soluzione di un problema si debba sempre partire dalle ipotesi contenute nel testo ed avvicinarsi per passi all'obiettivo. Ciò invece non è sempre vero, poiché esistono casi in cui risulta più produttivo partire dall'obiettivo e procedere a ritroso fino a pervenire ad un punto in cui tutti i passi necessari divengono noti. A questo punto è sufficiente ricostruire il percorso all'inverso per esprimere chiaramente il processo risolutivo. Con questo metodo, detto metodo analitico, si avanza l'ipotesi che ciò che si chiede di ottenere sia già stato ottenuto e, attraverso un ragionamento regressivo, si risale ai dati iniziali, definendo di volta in volta i passi necessari per passare da uno stato all'altro. Scomporre i problemi Aspettarsi di risolvere i problemi in un colpo solo non è realistico, e per questo nella ricerca della soluzione conviene articolare un problema in sottoproblemi, in modo tale che, risolvendo questi ultimi indipendentemente, si possa giungere alla soluzione del problema originale. Il livello di scomposizione dipende dalla personale interpretazione del concetto di problema primitivo. Fig. 2 Tecnica del top-down Quando dobbiamo trattare un problema di una certa complessità risulta conveniente suddividere il problema in sottoproblemi di più facile soluzione. Ciò si realizza attraverso processi di affinamento sempre più dettagliati. Si tratta di scomporre un problema complesso in sottoproblemi più semplici, utilizzando la tecnica del top-down: il problema viene esaminato nelle direttrici generali; scomposto in sottoproblemi; di ciascun sottoproblema si determinano le operazioni specifiche; esso viene scomposto in ulteriori sottoproblemi, fino a giungere alle operazioni elementari. 25 5.4. Esecuzione L'omino esecutore A questo punto, dopo avere definito i formalismi per rappresentare il linguaggio di descrizione dei processi risolutivi, possiamo presentare un primo modello di sistema di esecuzione imperativo. Questo modello rappresenta l'ambiente di esecuzione ed è formato da una camera isolata acusticamente nella quale stanno un omino e alcuni dispositiv: • • • • • • due finestre, una attraverso la quale entrano dati e istruzioni e l'altra che consente all'omino di trasmettere all'esterno i risultati dell'elaborazione. due pulsanti posti al'esterno della camera, collegati all'interno con due luci di diverso colore, le quali avvertono l'omino che sta per ricevere un nuovo programma, oppure che deve eseguire l'ultimo programma consegnatogli. una calcolatrice che permette di eseguire le operazioni aritmetiche di base una lavagna sul cui angolo superiore sinistro viene fissato il foglio contenente le istruzioni(programma). Il resto della lavagna viene fissato dall'esecutore per memorizzare dati e risultati un blocco note per uso personale l'occorrente per scrivere e cancellare Dal punto di vista operativo il nostro omino non prende mai nessuna iniziativa e fa solamente quello che gli viene comandato, o per iscritto tramite un programma, o attraverso i vari pulsanti e le relative lampadine; egli inoltre sa compiere solo le seguenti operazioni: • • interpretare il linguaggio formalizzato che abbiamo presentato nei paragrafi precedenti ed eseguire le operazioni descritte nel programma utilizzando i dispositivi a sua disposizione riconoscere, attraverso i codici di luce, i comandi che un utente esterno gli invia. Esempio. Scrivere un algoritmo che consenta di esaminare un numero imprecisato di terne di valori numerici e comunichi, di volta in volta, se la terna esaminata può rappresentare i lati di un triangolo rettangolo. Per semplicità si suppone che l'utente ogni volta inserisca i tre valori A,B,C in ordine crescente. L'esecutore dovrà ricevere la terna dall'esterno, verificare se può rappresentare le misure di un triangolo rettangolo, comunicare il risultato della verifica, ripetere queste operazioni finchè non sono finite le terne. Pertanto per poter definire la condotta risolutiva, risulta necessario affrontare i seguenti problemi: definire la modalità con cui l'esecutore può verificare se i tre numeri sono le misure dei cateti e dell'ipotenusa di un triangolo rettangolo(potrebbe non conoscere la geometria) definire la modalità con cui l'esecutore può capire se le terne da analizzare sono terminate (l'omino comunica con l'esterno solo con le finestre). Il primo problema può essere risolto abbastanza facilmente ricordando che per il terema di Pitagora se i tre valori sono le misure di un triangolo rettangolo vale la relazione a 2+b2=c2 . Di conseguenza, poiché l'omino non conosce la geometria ma sa effettuare le quattro operazioni 26 ed i confronti tra valori numerici, sarà sufficiente ordinargli di verificare se è valida l'uguaglianza a2+b2=c2 . Anche la soluzione al secondo problema è abbastanza semplice. Infatti sarà sufficiente fare in modo che l'omino, dopo aver comunicato con un opportuno messaggio il risultato del suo lavoro su una terna, ponga all'utente una domanda del tipo: “Sono finite le terne?” e decida in base alla risposta se ripetere le operazioni per una nuova terna o terminare l'esecuzione del programma. Formalizzando nel linguaggio dell'omino i singoli passi: Esecutore reale L'esecutore reale è il calcolatore. Parleremo successivamente e più dettagliatamente di come funziona l'esecutore reale. 27 5.5. Verifica dei risultati La verifica dei risultati discende dal passo precedente. Durante l'esecuzione ed alla fine del processo risolutivo, occorre verificare che i risultati ottenuti siano quelli attesi. In caso contrario occorre apportare qualche modifica al procedimento risolutivo evidentemente errato. Tabelle di traccia Uso delle tabelle di traccia: prendiamo in esame il flow-chart relativo all'esercizio (scambio dei valori contenuti in due variabili) La tabella di traccia si costruisce ponendo sulle colonne le variabili di cui si vuole studiare il comportamento e sulle righe i valori che assumono tali variabili ogni volta che avanza il processo. Ad esempio abbiamo supposto che alla partenza x contenga il valore 5 e y il valore 7; la tabella di traccia sarà Variabile a x y - 5 7 _____ Inizializzazione 5 5 7 dopo la 1. istruzione 5 7 7 dopo la 2. istruzione 5 7 5 dopo la 3. istruzione Conviene sempre compilare una tabella di traccia per ogni algoritmo sviluppato (quando ciò è possibile) in modo da verificare la correttezza della soluzione trovata . 28