Fabrizio Messina, Marzio Pennisi {messina,mpennisi}@dmi.unict.it Argomenti trattati Algoritmo: definizione Analisi e programmazione: attività preliminari atte a risolvere problemi utilizzando un elaboratore, dalla formulazione del problema fino alla predisposizione dell’elaboratore. ALGORITMO: un elenco finito di istruzioni che specificano una serie di operazioni, eseguendo le quali è possibile risolvere ogni problema di un dato tipo (classe di problemi). Un algoritmo può essere utilizzato per risolvere un particolare problema. ● In questo caso l'uomo può eseguire le istruzioni indicate nell'algoritmo sui dati che caratterizzano il particolare problema da risolvere. ● Un algoritmo non può essere eseguito direttamente da un elaboratore. Programma: definizione • Scopo della programmazione è quello di definire un programma. • Un programma è la descrizione di cosa deve fare un elaboratore (insieme univoco di istruzioni) per risolvere ogni problema di un dato tipo. • Programma -> descrizione di un algoritmo in una forma “comprensibile” per l’elaboratore. Linguaggio di programmazione: definizione • Un linguaggio di programmazione è rappresentato da un insieme di regole formali per la stesura di un programma comprensibile per un calcolatore. • Tramite un linguaggio di programmazione, è possibile descrivere un algoritmo in modo che esso diventi un programma. • La soluzione di un particolare problema di un dato tipo si ottiene eseguendo il programma formulato per la soluzione di quel tipo di problemi e specificando i dati caratteristici del problema particolare. • I risultati ottenuti dalla esecuzione del programma rappresentano la soluzione richiesta. • L’esecuzione di un programma è anche detta elaborazione. Analogie tra uomo ed elaboratore Analisi e programmazione Primi esempi di algoritmo • Esempio: Ordinamento di un mazzo di 40 carte • Problema: Si supponga di avere un mazzo da 40 carte mescolato. Si vuole ordinare il mazzo di carte in modo che le carte con il seme cuori siano poste per prime, seguite dalle carte con seme quadri, fiori e picche. Le carte di uno stesso seme sono ordinate dall'asso al re. • Algoritmo (possibile): 1. inizio dell’algoritmo; 2. Suddividere il mazzo in 4 mazzetti; ciascun mazzetto è costituito da tutte le carte dello stesso seme; 3. Ordinare le carte di ciascun mazzetto dall’asso al re; 4. Prendere nell’ordine il mazzetto dei cuori, dei quadri, dei fiori e delle picche; 5. fine dell'algoritmo. Primi esempi di algoritmo (segue) • Esempio: Calcolo delle radici delle equazioni di secondo grado. • Problema: Determinare le radici x1, x2, dell’equazione di secondo grado ax2 +bx+ c=0. Il procedimento prevede il calcolo del determinante Δ =b2 - 4ac. Se Δ > 0 si hanno due radici reali e distinte x1 e x2 = [-b ± √(Δ) ] /2a. Se Δ = 0 si hanno due radici reali coincidenti x1 e x2 = -b/2a. Se Δ< 0 non si hanno radici nel campo dei numeri reali. • Algoritmo (possibile): 1. inizio dell’algoritmo; 2. Introdurre i valori di a, b e c; 3. Calcolare Δ =b2 - 4ac; 4. Se Δ > 0 porre x1=[-b + √(Δ) ] /2a e x2=[-b - √(Δ) ] /2a e poi passare all’istruzione 7; 5. Se Δ = 0 porre x1=x2= -b /2a e poi passare all’istruzione 7; 6. Se Δ < 0 restituire il messaggio “non esistono radici reali” e poi passare all’istruzione 8; 7. Restituire x1 e x2; 8. Fine dell’algoritmo. Algoritmo: istruzioni e dati Fino ad adesso sono state utilizzate delle proposizioni per descrivere un algoritmo. Le proposizioni sono composte di due parti: • Operazioni da eseguire (ISTRUZIONI) • Oggetti sui quali eseguire le operazioni al fine di ottenere i risultati (DATI). Esempi sugli algoritmi appena visti: ISTRUZIONI DATI calcola b2 - 4ac porre….x1=x2 suddividere in quattro mazzetti 4, a, b, c, x1 e x2 mazzo di carte I DATI: Costanti e variabili Gli oggetti (dati) sui quali si opera per mezzo di un algoritmo sono solitamente di 2 tipi: Costante: tipo di dato che rimane invariato durante l’esecuzione dell’algoritmo. Variabile: Una variabile si può definire come una coppia <nome,valore>. Essa si può paragonare ad un contenitore con un’etichetta. Questa scatola conterrà, in generale, differenti valori durante l’esecuzione dell’algoritmo. valore nome Costanti e variabili • Data una variabile <x,v> si dice che x è il nome della variabile e che v è il valore attuale della variabile di nome x (oppure, più semplicemente, che v è il valore di x). • Un dato variabile risulta indeterminato al momento della definizione dell'algoritmo; • il dato però corrisponde a un valore ben preciso durante ogni esecuzione dell’ algoritmo. • Esempio: Radici di una equazione di 2° grado. • Si consideri il passo 3 "Δ =b2 - 4ac" i dati a, b, c non corrispondono a nessun valore finché non si esegue l'algoritmo per una ben precisa equazione di 2°grado, ad esempio x2 - 9x - 4 = 0. • Solo durante l’esecuzione dell’algoritmo (in particolare al passo 2) è possibile stabilire che i nomi a, b, c corrispondono, rispettivamente, ai numeri 1; - 9; - 4! L’istruzione di assegnamento L’istruzione di assegnamento (assegnazione) permette di definire il valore attuale v di una variabile x. Quel valore resta inalterato finché una successiva assegnazione non modifica il valore stesso. L'assegnazione si può rappresentare con la seguente istruzione: x ← espressione e si legge: assegna a x il valore di “espressione”. L'espressione a destra di ← sarà un’opportuna espressione che include l’uso di costanti, nomi di variabili e/o simboli di operazione. Assegnazione (segue) Ad esempio,se abbiamo b c dopo l’operazione di assegnazione a ← b + c 6 a b c Utilità delle assegnazioni Gli algoritmi si possono descrivere in modo conciso utilizzando variabili e assegnazioni. Ad esempio, una operazione come "calcolare il volume di un parallelepipedo per mezzo del prodotto dei valori delle tre dimensioni" può essere espressa come: volume ← lunghezza X larghezza X altezza I nomi delle variabili sono arbitrari; È di solito opportuno scegliere il nome di una variabile in modo che ricordi il significato del valore associato. In un algoritmo è sempre necessario rispettare la regola dell’ordinamento: • Se una variabile compare a destra di una operazione di assegnazione è necessario che a quella variabile sia stato precedentemente associato un valore. Algoritmo: Tipi di “istruzione” a. istruzioni operative: istruzioni che effettivamente producono dei risultati, se eseguite (e.g. istruzioni di assegnamento). b. istruzioni di controllo: istruzioni che controllano il verificarsi o meno di condizioni specificate. ○ In base al risultato del controllo determinano l’esecuzione di alcune istruzioni anziché altre. c. Istruzioni di salto: istruzioni che alterano il normale ordine di esecuzione dell’ algoritmo, specificando esplicitamente quale sia la successiva istruzione da eseguire. ○ Salto condizionato: l'effettiva esecuzione del salto sia vincolata al verificarsi di una condizione specificata (include al suo interno una istruzione di controllo). ○ Salto incondizionato: che il salto viene eseguito tutte le volte che l'istruzione è eseguita; Algoritmo: tipi di “istruzione” (segue) d. inizio esecuzione: indica l’inizio dell’esecuzione dell'algoritmo; e. fine esecuzione: indica la fine dell'esecuzione dell'algoritmo; f. I/O (o trasmissione): indica come la trasmissione di dati o messaggi debba avvenire tra l’algoritmo e l’ambiente esterno. Possono essere: • di ingresso o input (di lettura): i valori vengono trasmessi da un ambiente esterno all'interno dell'algoritmo oppure istruzioni • di uscita o output (di scrittura): i valori debbono essere trasmessi dall'algoritmo verso l'ambiente esterno. Esempio tipo di ''istruzione'' (algoritmo radici equazione secondo grado) Istruzione di inizio esecuzione 1. Inizio dell’algoritmo; Istruzioni di controllo 2. Introdurre i valori di a, b e c; Istruzione di input 3. Calcolare Δ ← b2 - 4ac; Istruzione operativa 4. Se Δ > 0 porre x1 ←[-b + √(Δ) ] /2a e x2 ←[-b - √(Δ) ] /2a e poi passare all’istruzione 7; 5. Se Δ = 0 porre x1 ← x2 ← -b /2a e poi passare all’istruzione 7; Istruzione di salto 6. Se Δ < 0 restituire il messaggio “non esistono radici reali” e poi passare all’istruzione 8; Istruzione di salto 7. Restituire x1 e x2; 8. Fine dell’algoritmo. Istruzione di output Istruzione di fine esecuzione Proposizioni e predicati Una proposizione è un costrutto di verità). Ad esempio: linguistico del quale si può dire se è vero o falso (valore Roma è la capitale della Francia; 3 è un numero intero; 1 è un numero dispari; Viceversa le seguenti non sono proporzioni Luigi, leggi quel libro! arrivederci a tutti. Una proposizione è un predicato se in essa appaiono delle variabili, e il valore di tali variabili determina il valore di verità della proposizione stessa. Esempio: la variabile età è minore di 30; la variabile base ha un valore maggiore di quello della variabile altezza Valutazione di un predicato Valutazione del predicato. Operazione che permette di determinare se il predicato è vero o falso s sostituendo alle variabili i loro valori attuali. ● i due valori vero e falso si dicono valori logici o booleani. età < 30 base > altezza Per la scrittura dei predicati è possibile utilizzare i seguenti simboli, detti operatori relazionali oppure operatori di confronto: = uguale > maggiore ≥ maggiore o uguale ≠ diverso < minore ≤ minore o uguale I predicati che contengono un solo operatore relazionale sono detti predicati semplici. Predicati composti È inoltre possibile utilizzare all’interno di un predicato i seguenti operatori booleani: • AND (indicato anche con ^ ) • OR ( indicato anche con v ) • NOT (indicato anche con il simbolo di soprasegnato) I predicati nei quali compare almeno un simbolo NOT, AND, OR sono detti predicati composti. La tavola di verità di un predicato composto è un modo di descrivere il significato un predicato. Essa consiste nello specificare il valore del predicato per ognuna delle possibili combinazioni degli argomenti del predicato. Le tavole di verità dei predicati p AND q e p OR q sono le seguenti: p AND q p q p OR q p q vero vero vero vero vero vero falso falso falso vero falso falso falso vero falso vero vero falso vero falso falso falso vero falso Costruzione di algoritmi mediante analisi Un algoritmo si ottiene come risultato di un procedimento di analisi che può essere suddiviso in tre fasi: a) definizione del problema: non si deve riferire ad un caso concreto, ma casi generali. b) scelta del metodo di soluzione: si definisce il metodo da seguire per risolvere il problema; c) descrizione delle operazioni o sviluppo dell’algoritmo: una volta scelto il metodo si descrivono le operazioni che devono essere eseguite per risolvere il problema scelto. Costruzione di algoritmi mediante analisi: metodo delle decomposizioni successive. 1. 2. 3. 4. Definizione del problema. Scomposizione del problema in sottoproblemi. Per ognuno dei sottoproblemi si sceglie il metodo di soluzione. Fornire una descrizione dettagliata di ciascun sottoproblema, in modo da ottenere una così successive scomposizioni del problema, fino ad ottenere una scomposizione finale, che contenga solo descrizioni di operazioni direttamente eseguibili. 5. La successione di operazioni direttamente eseguibili è l’algoritmo. Nei problemi particolarmente semplici, la prima scomposizione è sufficiente a trovare per la definizione dell’algoritmo. Il metodo delle scomposizioni successive risulta particolarmente utile quando i problemi da risolvere sono complicati. Gli esempi successivi sono introdotti soprattutto a scopo illustrativo. Costruzione di algoritmi mediante analisi (segue) Esempio: Calcolare la somma delle prime n potenze del 2. È noto che: Pertanto questa notazione algebrica fornisce il metodo per la soluzione del problema posto. In questo caso la scelta del metodo coincide con la descrizione delle operazioni direttamente eseguibili. L'algoritmo, ottenuto direttamente come prima scomposizione del problema, è: 1) Inizio dell’algoritmo 2) acquisire dall’esterno il valore di n; 3) a ←2n 4) b ← a - 1 5) c ← a + b 6) Restituire c 7) Fine dell’algoritmo Costruzione di algoritmi mediante analisi (segue) Metodologia di soluzione dei problemi • La scomposizione del problema in sotto-problemi più piccoli (e quindi più semplici da risolvere) è denominato approccio top-down, oppure divide et impera o divide and conquer. • Un altro approccio di che più essere utilizzato è l’ approccio bottom up, che parte dal caso base (semplice) e costruisce via via una soluzione generale. Flowcharts (diagrammi di flusso o a blocchi) • Un diagramma di flusso o flowchart permette di realizzare una descrizione grafica di un algoritmo. - • E’ detto diagramma di flusso in quanto permette di rappresentare graficamente il flusso (cioè la sequenza) delle operazioni di cui è composto l’algoritmo. Flowcharts (segue) • Ogni istruzione viene rappresentata da un blocco elementare (o più semplicemente blocco). • La forma grafica dei blocchi è determinata dal tipo dell’ istruzione. • I blocchi sono etichettati con le istruzioni stesse. • I blocchi sono collegati tra loro da frecce che indicano gli schemi di flusso, cioè il susseguirsi delle azioni elementari. Flowcharts (segue) La struttura generale di un diagramma a blocchi è la seguente: a) blocco iniziale; b) blocco finale; c) numero finito n (n ≥ 1) di blocchi di azione e/ o di lettura-scrittura d) numero finito m (m ≥ 0) di blocchi di controllo Flowcharts (segue) Flowcharts (segue) Esempio: Diagramma a blocchi dell'algoritmo per il calcolo di M.C.D. (a,b) Flowcharts (segue). Si noti come l'operazione "scambiare i valori di a e b" è stata realizzata con la sequenza di assegnazioni E’ stata usata una variabile di appoggio (in questo caso x) in quanto l'assegnazione è distruttiva nel senso che quando si assegna un nuovo valore ad una variabile non si ha memoria del suo valore precedente. Pertanto se le variabili a e b fossero: una sequenza assegnazioni di avrebbe come effetto Flowcharts: calcolo (segue) I Cicli (loop) Un ciclo rappresenta essenzialmente uno schema di iterazione. Un ciclo è definito quando si conosce a priori il numero di volte che deve essere eseguito. Un ciclo definito è detto anche ciclo enumerativo. ● ● In questo caso si usa la tecnica del contatore: una variabile, detta contatore del ciclo, che conta quante volte istruzioni del ciclo sono state eseguite. Il contatore del ciclo può essere utilizzato in due modi: ○ Incremento del contatore: il contatore viene inizializzato ad un valore minimo, ad esempio 0, e incrementato di uno tutte le volte che il ciclo è eseguito. Si ha una uscita dal ciclo quando il valore del contatore è uguale al numero prefissato di volte che il ciclo deve essere eseguito (vedi esempio precedente). ○ Decremento del contatore: il contatore viene inizializzato con un valore uguale al numero di volte che il ciclo deve essere eseguito e decrementato di uno ogni volta che il ciclo è eseguito. Si ha una uscita quando il valore del contatore raggiunge il valore 0. Un ciclo è indefinito quando non è possibile conoscere a priori quante volte deve essere eseguito. In questo caso la condizione di fine ciclo viene fatta controllando valore di una o più variabili. I valori di queste variabili sono modificati da istruzioni che fanno parte della iterazione. Algoritmi iterativi Accade molto spesso che, per risolvere un problema, lo stesso insieme di operazioni debba essere eseguito un numero opportuno di volte. Consideriamo ad esempio il seguente problema: Calcolare la somma dei cubi dei tre numeri interi successivi ad un valore dato n. (n+1)3+(n+2)3+(n+3)3 Un possibile algoritmo è il seguente: Algoritmi iterativi (segue) Il risultato è ottenuto usando la variabile somma come un contenitore di somme parziali finché non si ottiene la somma totale richiesta. Questo risultato viene raggiunto eseguendo delle azioni "simili" per un numero opportuno di volte. Lo stesso risultato può essere ottenuto modificando leggermente le istruzioni così che l’ algoritmo sia composto da un numero prefissato di azioni uguali: Algoritmi iterativi (segue) In questo modo il valore finale di somma è ottenuto mediante l’esecuzione, per una sola volta, della sequenza di istruzioni E dalla esecuzione per tre volte della sequenza di istruzioni Algoritmi iterativi (segue) Algoritmi ricorsivi Gli algoritmi ricorsivi sono algoritmi che “richiamano se stessi” per la risoluzione di un problema. Molto semplici da scrivere e pensare, ma meno efficienti sul calcolatore degli algoritmi iterativi. L'algoritmo include una sequenza di chiamate all’algoritmo stesso che ha termine al verificarsi di una condizione particolare che viene chiamata condizione di terminazione, che in genere si ha con particolari valori di input. Esempio: Calcolo del fattoriale n!. Esso è definito come segue: 0!=1, 1!=1, n! = n *(n-1)! NB: Ogni algoritmo ricorsivo può essere convertito in un algoritmo iterativo. Vettori o array. Un vettore o array è una variabile che può contenere un numero di elementi maggiore di uno. Un array è di fatto una zona di memoria, all’interno del calcolatore, come una singola variabile. La dimensione della zona di memoria allocata dal calcolatore per un array, è uguale alla quantità di memoria necessaria per contenere una variabile singola moltiplicata per il numero di elementi dell’array stesso. Ciascuno “scomparto” si dice componente o elemento del vettore, la dimensione di vettore è il numero dei suoi elementi. un La notazione comune per identificare un elemento di un array, è quello di usare il nome del vettore stesso, seguito da una coppia di parentesi (tonde o quadre) che racchiudono il numero che individua lo scomparto: v(1) oppure v[4] I vettori sono particolarmente utili quando è necessario effettuare lo stesso tipo di operazionisu un insieme di dati. Ogni volta che un vettore è usato in un algoritmo è necessario dichiararne le caratteristiche nell'elenco delle variabili usate. Vettori (segue) allora il vettore somma e è dato da (somma per posizione a(1)+b(1), a(2)+b(2) etc ) Vettori.