Programmazione: Metodi, Problemi, Linguaggi Nicola Galesi Dipar;mento di Informa;ca Sapienza Università Roma OII Olimpiadi Italiane di Informa;ca Sirmione 11‐16 OGobre 2010 Programmazione • Strumen; – Sequenzialità e Flusso di Controllo – Selezione – Iterazione – Ricorsione • StruGura – Informazione e suo flusso – Scatole nere/Funzioni – Input e Output – StruGurare con metodo • Da; – Variabili – Da; Scalari – Da; struGura; • VeGori • Matrici • Aggregazioni • Metodo – Dal problema al programma – Informazione e pseudo‐codice – CorreGezza • Esercizi Selezioni Scolas;che – Problema Programma – Programma Problema Dati: Variabili I linguaggi impera;vi sono basa; sull’uso dell’istruzione di assegnamento e sul conceGo di variabile. Una variabile puó pensarsi come una “scatola” con quaGro caraGeris;che che la iden;ficano in un programma: Un nome Un ;po Un contenuto somma Un indirizzo intero 25 001001001 Variabili In un programma C possiamo usare variabili per salvare dei valori che vogliamo usare nel programma. Prima di usare una variabile, cioè prima di assegnarle un valore, dobbiamo DICHIARARLA, vale a dire dobbiamo fare in modo che il programma sappia che quella variabile esiste. Per dichiararla dobbiamo specificarne il nome e il ;po. In un programma C ogni dato (in par;colare le variabili) deve essere PRIMA DICHIARATO e POI USATO Esempio /* Lunghezza in km di una maratona */ #include <stdio.h> int main(void) { int miles,yards; float km; questo è un commento questa è una dichiarazione miles = 26; yards = 385; km = 1.609 *(miles + yards / 1760.0); questa è una istruzione di printf(“Una maratona e’ di %f km\n”,km); assegnamento return 0; } questa è una espressione Analisi: int miles,yards; float km; int miles, yards; è una dicharazione di variabili. In questa dichiarazione s;amo dichiarando due variabili di ;po intero. Per poter dichiarare più di una variabile nella stessa dichiarazione è sufficiente separarare i nomi di variabile con una virgola. int è la parola chiave riservata dal C al ;po intero. Analogamente float km; è la dichiarazione di una variable di ;po float (reale); Parole Chiave int e float sono parole chiave. Sono cioè parole riservate dal C e che quindi non è possibile u;lizzare altrimen;. Per esempio non è possibile usarle come nomi di variabili. Il C possiede un certo numero di parole chiavi, e parole che non conviene usare perchè sono nomi di funzioni di uso frequente in C. Per esempio prind e un nome riservato ad una funzione di una libreria standrd e non deve essere usato come nome per una varaibile. Strumenti: Sequenzialità e Flusso di Controllo Istruzioni di assegnamento miles = 26; è una istruzione di assegnamento il cui effeGo è quello di dare il valore 26 alla variabile miles. yards = 385; è una istruzione di assegnamento il cui effeGo è quello di dare il valore 26 alla variabile miles km = 1.609 *(miles + yards / 1760.0); è un istruzione di assegnamento che da alla variabile km risultato dell’espressione 1.609 *(miles + yards / 1760.0) dove le variabile miles e yards hanno i valri appena calcola; Flusso di controllo Che differenza c`è tra un programma in cui le tre istruzioni sono nelle sequenza miles = 26; yards = 385; km = 1.609 *(miles + yards / 1760.0); piuGosto che nella sequenza km = 1.609 *(miles + yards / 1760.0); miles = 26; yards = 385; Flusso di controllo Quando eseguiamo un programma le sue istruzioni vengono eseguite in sequenza a par;re dalla prima. miles = 26; yards = 385; km = 1.609 *(miles + yards / 1760.0); Flusso di controllo miles = 26; yards = 385; km = 1.609 *(miles + yards / 1760.0); miles yards km miles =26; 26 --- --- yards =385; 26 385 --- km = 1.609 *(miles + yards / 1760.0); 26 385 42.185 Flusso di controllo km = 1.609 *(miles + yards / 1760.0); miles = 26; yards = 385; miles yards km km = 1.609 *(miles + yards / 1760.0); --- --- --- yards =385; -- 385 --- miles =26; 26 385 --- Leggere e Scrivere Come abbiamo visto per scrivere risulta; sullo schermo il C ci permeGe di usare la funzione prind. Tecnicamante prind e’ una funzione non del linguaggio C ma del sistema C, è cioè presente in ogni sistema che usa il C. In stdio.h vi è il proto;po di prind cioà la dichiarazione della funzione Prima di usare una funzione dobbiamo sempre definirne il proto;po. Questo vuol dire che in ogni qualvolta programma in cui abbiamo bisogno di usare prind, dobbiamo inserire la direhve del preprocessore #include <stdio.h> Leggere e Scrivere Come possiamo introdurre dei da; in un programma ?. Cioè, come fa un programma a leggere dei da; dall’esterno ? Un modo ci è fornito dalla funzione scanf. EsaGamente come per prind, scanf riceve una lista di parametri formata ncome segue: stringa di controllo lista parametri dove stringa di controllo e’ una stringa (“...”) che può contenere delle specifiche di conversione deh anche forma; e solo per prind la lista dei parametri è una lista di espressioni (variabili, costan; o espressioni stesse) che verrano formaGa; nell’ordine specificato dai forma; della stringa di controllo. Esempio prind prind(“abc”); prind(“%s”,”abc”); int x = 5; prind(“x = %d”,x+4); prind(“%c%c%c”,’a’,’b’,’c’); CaraGere Formato di stampa su schermo c caraGere d intero notazione decimale e reale in notazione scien;fica f numero reale s stringa scanf Se vogliamo leggere un dato dall esterno ed usarlo in programma dobbiamo “salvarlo” da aualche parte. Le variabili (e piu in generale le struGure da;) in un programma, servono anche a questo scopo. avere un oggeGo nel programm che noi sappiamo contenre il valore (qualsiasi) che abbiamo introdoGo dall’esterno. stringa di controllo lista parametri la lista dei parametri per scanf non e’ una lista di espressioni, ma una lista di indirizzi di memoria !. Cioè dobbiamo fornire a scanf gli indirizzi di memoria delle variabili nelle quali vogliamo salvare i valori leh dall’input. scanf Esempio int x; scanf(“%d”, &x); Il formato %d viene associato all’espressione &x, facendo si che scanf interpre; i caraGeri in input come un intero in notaione decimale e ne memorizzi il valore all’indirizzo delle variabile x. & è un operatore che, applicato ad una variabile, conesente di estrapolarne l’indirizzo in memoria x scanf(“%d”,&x) 20 Parole chiave Vi sono delle parole che non possono essere usate in C come iden;ficatori. Queste sono le parole chiavi cioè parole riservate Esempio Tipi e Da;: int, double, float, char, void, long, struct, enum, typedef,union, extern, unsigned, register StruGure di controllo: do, if, for, while, else, switch, goto,case, return, break, con;nue, default Qualificatori: const,vola;le, auto,sta;c Operatori Per scrivere espressioni è necessario avere un modo di definire gli operatori aritme;ci,logici e relazionali. Gli operatori usualmente si dividono nelle tre categorie: operatori aritme;ci operatori logici operatori relazionali In C per la sua natura flessibile, si aggiungono altre due categorie di operatori: operatori di incremento e decremento operatori di assegnamento Tuh gli operatori si dividono a seconda del numero di operandi a cui si applicano in: operatori unari operatori binari Operatori Aritme;ci Gli operatori aritme;ci sono operatori per i quali operandi e risulta; sono numeri. Si suddividono a loro volta in addi;vi: + (somma), ‐ (soGrazione binaria) , ‐, + (cambio segno unario) mol;plica;vi: * (prodoGo), / (divisione), % (modulo) Nota. L’operatore di divisione / è una divisione intera se i suoi due operandi sono interi, e reale nel caso uno dei due operandi sia reale 7/2 = 3 7.0/2=3.5 7/2.0=3.5 L’operazione di modulo resituisce il resto della divisione intera Priorità Siamo sicuri di come viene valutata un espressione aritme;ca ? Esempio Che risultato da la seguente espressione in C ? 3 + 4 – 5 % 6 * 7 Esistono norme, deGa di priorità, per stabilire accuratamente come il C calcola le espressioni, vale a dire come inserirebbe le parentesi. priorità tra ;pi di operatori a parità di priorità, associa;vità In ordine dal maggiore al minore la priorità per gli aritme;ci e` Unari + Mol;plica;vi Addi;vi ‐ Associa;vità Supponiamo di avere l’espressione 5‐4+5. Gli operatori hanno tuh la stessa priorità. Come inserisce le parentesi il C ? Associa;vità Addi;vi: da sinistra a destra 5‐4+5 (5‐4)+5 ((5‐4)+5) Associa;vità Mol;plica;vi: da sinistra a destra 5%3%4 (5%3)%4 ((5%3)%4) Associa;vità Unari: da destra a sinistra + ‐ + 4 +‐(+4) +(‐(+4)) (+(‐(+4))) Operatori Relazionali Gli operatori relazionali sono operatori che permeGono comparare il valore di espressioni. Resituiscono un valore booleano (vero o falso). Esempio 34 < 45 è vero, < (minore di) è un operatore relazionale Operatori Relazionali: < (minore), <= (minore o uguale), > (maggiore) >=(maggiore o uguale), == (uguale) , !=(diverso) Valori booleani Falso e Vero in C: In C gli operatori relazionali res;tuiscono un int. 0 se falso e 1 se vero In generale qualunque valore differente da 0 è considerato vero. Qualunque valore nullo viene considerato come falso, Es 0,0.0, null, ‘\0’, mentre ogni altro valore rappresenta il vero Es 0<0 è 0, 0 < 1 è 1, 1< 0 è 0. + Priorità Unari Mol;plica;vi Addi;vi ‐ Associa;vità: da sinistra a destra Relazionali (<,<=,>,>=) Associa;vità: da sinistra a destra Relazionali (==, !=) La valutazione di una espressione come a <op_relaz> b viene valutata a livello di macchina stabilendo se a‐b <op_relaz> 0. valore di a-b a<b a<=b a>b a>=b a==b a!=b positivo 0 1 0 1 0 1 zero 0 0 1 1 1 0 negativo 1 0 1 0 0 1 AGenzione: come viene valutata 4<7<6 in C? per associa;vità come (4<7)<6 quindi come 1<6 cioè 1 Operatori Logici Gli operatori logici sono operatori che (in teoria) sono applica; a valori booleani (vero/ falso) e res;tuiscono valori booleani. In C vedremo che sono generalizza; Logica a b a∧b a∨b ¬a 0 0 0 0 1 0 1 0 1 1 1 0 0 1 0 1 1 1 1 0 Linguaggio C unari : ! (negazione) addi;vi: || (disgiunzione) mol;plica;vi: && (congiunzione) a b a&&b a||b !a 0 0 0 0 1 0 ≠0 0 1 1 ≠0 0 0 1 0 ≠0 ≠0 1 1 0 + Unari, ! Priorità & Esempi Associa;vità: da destra a sinistra Mol;plica;vi Addi;vi Relazionali (<,<=,>,>=) Relazionali (==, !=) ‐ && Associa;vità: da sinistra a destra || Associa;vità: da sinistra a destra int a=1, b=2 c= 3; Espres. Espres. equiv. Valore 1. a > b && c < d (a > b) && (c < d) 0 2. a < ! b || ! ! a (a< (!b)) || (! (! a)) 1 3. a+b < ! c + c (a + b) < ((!c) +c) 0 Esercizi: tra logica e programmazione Scrivere espresioni booleane per le seguen; specifiche. 1. Da; due coppie di interi (a,b) e (c,d) che rappresentano il ver;ce superiori sinistro e inferiore destro di un reGangolo in un sistema cartesiano, determinare la condizione per cui un terzo punto (x,y) è dentro del reGangolo. 2. Determinare se un numero rappresenta un anno bises;le. Un anno è bises;le se è mul;plo di 4 escludendo i mul;pli di 100 che no lo sono di 400.. Es 1964,2004 e 2400 sono bises;li, pero 1977 e 2100 no. 3. Determinare se due intervallli [a,b] e [c,d] si intersecano. Che succede se gli intervali sono aper; ? 4. Deteminare se un naturale di al più 4 cifre è palindromo. (Cioe`può essere leGo da sinistra a destra o da destra a sinistra indifferentemente.) 5. Determinare se tre pun; del piano con coordinate (a,b), (c,d), (e,f), tuGe intere, giacciono sulla stessa reGa. Strumenti: Assegnamento + Selezione Assegnamento L’assegnazione è una istruzione che permeGe di cambiare il contenuto o valore di una variabile Esempio int a,b,c; a = 5; /* assegna ad a il valore 5 */ b=a; /* cambia il contenuto di b assegnandogli il contenuto di a*/ c= b‐a /* meGe in c il contenuto di b meno il contenuto di a*/ Nota. L’operazione di assegnamento non è una uguaglianza matema;ca. In par;colare a=b è diverso da b=a. In realtà un assegnazione può essere pensata come un istruzione che implica un flusso di da; che va da destra a sinistra a b Assegnamento In un assegnamento la parte sinistra è sempre una variabile il cui contenuto deve essere modificato, e la parte destra è un espressione il cui valore calcolato verrà copiato come contenuto della variabile nella parte sinistra /* un programma per scambiare i valori * * di due variabili a ,e b */ int main(void){ int a,b,aux; a=5; b=3; aux = a; a=b; b=aux; } a b aux a=5 5 -- -- b=3 5 3 -- aux = a 5 3 5 a= b 3 3 5 b=aux 3 5 5 Assegnamento in C: nuovi operatori In un linguaggio di programmazione è possibile modificare il valore di una variabile in funzione del suo valore precedente. Un assegnamento del ;po k = k+2 somma 2 al valore contenuto in k e lo memorizza come nuovo valore in k. In C tali espressioni si abbreviano con l’uso di nuovi operatori k += 2; Tali operatori sono: +=, ‐=, *=, /= Nota. Si faccia aGenzione che un’espressione del ;po k *= j+2 è equivalente a k = k * (j+2) e non a k = (k*j) +2 incremento & decremento Una delle potenzialità del C è quella di offrire degli operatori che permeGono l’incremento e il decremento di variabili. Tali operatori sono ++ e –‐ e possono essere u;lizza; sia in notazione prefissa che posdissa int i=0; ++i; /* (prefisso) e`una espressione valida che incrementa il valore di i */ i‐‐; /* (posdisso) e`un espressione valida che decrementa il valore di i */ Gli operatori di incremento e decremento possono essere applica; solo a variabili e sono equivalen; a le espressioni i =i+1 e i=i‐1. Che differenza c`è tra l’uso prefisso e posdisso di un operatore di incremento. ?? i= 5; i=5; c = i ++; c= ++i; /* l’ouptut è c = 5, i=6 */ /* l’output è c = 6, i = 6 */ Istruzioni di selezione Eseguire solo istruzioni di assegnamento in un progamma è piuGosto limitato.Tuh i linguaggi impera;vi meGono a disposizione del programmatore dei costruh che permeGono di svolgere determinate azioni solo in determina; casi. L’idea e’ di valutare un certa proprietà o condizione e sulla base della verità o falsità di tale condizione eseguire o meno certe altre istruzioni. Il costruGo in generale usato si chiama if‐then‐else. In C esistono due costruh il costruGo if e il costruGo if‐else. CostruGo if La forma generale del costruGo if è la seguente: if (expr) I dove I e’ una macro istruzione (cioè una o più istruzioni) La seman;ca è la seguente: se (expr) viene valutata differente da 0 allora si eseguono le istruzioni in I altrimen; si procede oltre I e le istruzioni in I non vengono eseguite Esempi Generalmente (expr) è un’ espressione relazionale o logica, ma in generale in C potrebbe essere qualunque espressione if (x ==0.0) x /= y; if (c != ‘y’) { ++blank; if (blank == (x+y)) y++; } Una sola istruzione non ha bisogno di parentesi {,} Se ci sono più istruzioni c’è bisogno di parentesi {,} Dopo la “}” non va il “;” Istruzione if‐else La forma generale del costruGo if‐else è la seguente: if (expr) I else J dove I,J sono macro istruzioni (cioè una o più istruzioni) La seman;ca e`la seguente: se (expr) viene valutata differente da 0 allora si eseguono le istruzioni in I altrimen; le istruzioni in I non vengono eseguite e si eseguno le istruzioni in J. Esempi Minimo tra due interi x e y Errore di sintassi !! int x,y,min; int i,j; if (x<y) min =x; else min = y; if (i == j){ i++; j+=2; }; else i‐=j; if (c >=‘a’ && c <= ‘z’) ++lc_cnt; else{ ++other_cnt; prind(“%c non è minuscolo”,c); } If indenta; Abbiamo visto che è possibile avere degli if indenta; if (c != ‘y’) { ++blank; if (blank == (x+y)) y++; } Supponiamo di aver il seguente codice if (expr1) if (expr2) Esempio I if (a==1) else J if (b==0) prind(“ Doppio if”) else prind(“ .......”); Metodo: Correttezza e Verifica assegnamento+selezione Pre‐ e Post‐condizione Supponiamo di avere un programma con variabili: int a,b,c; e un certo numero di istruzioni che le riguardano. a = E1; b = E2; c = E3; dove E1,E2,E3 sono 3 espressioni. Ogni istruzione è definita da una precondizione “Pre”ed una postcondizione “Post” che rappresentano esplicitamente lo stato della memoria del programma rispehvamente prima e dopo l’esecuzione della istruzione. [Pre: a=A,b=B,c=C] a =E1; [Post: a = E1, b=B, c=C] Cenni su Verifica Supponiamo di avere le istruzioni che consentono di intercambiare il valore di due variabili Proprietà int a,b,aux; [Pre: a=A, b=B] { { aux = a; aux = a; a=b; a=b; b=aux; b=aux } } [Post: a=B, b=A] Verifica [Pre: a=A, b=B] { [a=A,b=B] aux = a; [a=A,aux=A,b=B] a=b; [a=B,aux=A,b=B] b=aux; [a=B,aux=A,b=A] } [Post a=B, b=A] CorreGezza di un If‐else Supponiamo di avere una istruzione if‐else che ci consente di passare da una precondizione P ad una postcondizione Q. [Pre: P] if (expr) I else J [Post: Q] Esempio [Pre: x=X, y=Y] if (x<y) min = x; else min =y; [Post: min = MIN(X,Y)] VERIFICA CORRETTEZZA Per verificare che il codice scriGo sia correGo dobbiamo verificare che che partendo dalla precondizione P arriviamo alla postcondizione Q dopo aver eseguito l’istruzione if‐else Verifica di un If‐else [Pre: P] if (expr) I else J [Post: Q] [Pre: P ∧ expr !=0] I [Post: Q] È CORRETTO QUANDO SONO CORRETTE ENTRAMBI ..... [Pre: P ∧ expr == 0] J [Post: Q] Esempio Verifica min [Pre: x=X ∧ y=Y] if (x<y) min = x; else min =y; [Post: min = MIN(X,Y)] Per verificarne la correGezza devo verificare che ........ [x=X ∧ y=Y ∧ x<y ] min = x; [min = MIN(X,Y)] [x=X ∧ y=Y ∧ x<y ] min = x; [min = X ∧ y=Y ∧ x<y ] [min = MIN(X,Y)] /* perché min = X e x<y */ [x=X ∧ y=Y ∧ x >= y ] min = y; [min = MIN(X,Y)] Dati Tipi di Da; Per tipo di dato si intende un insieme di valori e un insieme di operazioni che si possono applicare ad esso. Ogni tipo di dato ha una propria rappresentazione in memoria, cioè viene rappresentato attraverso un’opportuna codifica sfruttando un certo numero di celle di memoria. In C ed in altri linguaggi ogni variabile ha un tipo associato. Ciò permette di: associare un insieme di valori ammissibili per quella variabile determinazione a priori della quantità di memoria necessaria per la memorizzazione di quella variabile. Classificazione di ;pi di da; E’ chiaro che vi è un numero potenzialmente infinito di da; che è possibile definire. I ;pi di da; si classificano nel seguente modo: ;pi predefini; (già esisten; nel linguaggio) ;pi defini; dall’utente. Sulla base della loro struGura i ;pi si possono suddividere in: ;pi semplici ;pi struGura; I ;pi predefini; in C CARATTERI INTERO char short signed char int unsigned char long unsigned short unsigned unsigned long REALI float double long double Vedremo che in realtà il ;po caraGeri è traGato come un intero Tipo intero Gli interi vengono traGa; dal C mediante l’uso di differen; ;pi: short, int, long, e char. Il ;po standard per il traGamento degli interi e l’int. I ;pi interi sono i numeri ....... ‐2,.‐1,0,1,2,3................... Chiaramente su una macchina non possiamo pensare di memorizzare tuh ques; infini; numeri. Potremo memorizzarne solo un porzione. La quan;tà di numeri memorizzabili in un itpo dipende dalla memoria assegnata a quel ;po. Al ;po int di solito è assegnato una memoria di 16 bit (2 byte) o 32 bit (4 byte). Tipo intero Quindi su una macchina con int di 4 byte possiamo memorizzare i numeri mentre in una macchina con interi a 16 bit, solo: Cioè gli interi rappresentabili vanno da 32768 a 32767. Altri ;pi interi IL C fornisce altri predefini; per lavorare con gli interi char (1 byte) da ‐128 a 127 short (2 byte = int in macchine dove ha int = 2byte) SHRT_MAX = 215 ‐1, SHRT_MIN= ‐215 long 4 byte (= int dove int hanno 4 byte) unsigned stessa dimensione di un int, ma senza segno quindi da int = 2 byte (UINT_MAX = 216 ‐1 ) se int = 4 byte UINT_MAX = 232 ‐1. se ;po UNSIGNED Le variabili di ;po unsigned vengono manipolate con l’aritme;ca modulo MAX_U Esempio unsigned b; b = UINT_MAX; prind(“%u\n”,b+1); /* stamperà 1 */ prind(“%u\n”, b+b); /+ stamperà b */ Strumen;: Iterazione Iterazione Gli strumen; vis; fino ad ora (assegnamento e istruzione if‐then‐ else) non consentono di poter implementare tuh gli algoritmi immaginabili e sono di potenza limitata. I linguaggi di programmazione meGono a disposizione un altro costruGo, che consente di poter ripetere ciclicamente una sequenza di istruzioni, un numero di volte che può dipendere anche da un valore dato in input e non definito all’interno del programma. Tali costruh prendono il nome di costruh itera;vi e come vedremo ogni linguaggio ne offre differen; ;pi. CostruGo While in C La sintassi del costruGo while in C è la seguente: while (expr) I La seman;ca di tale costruGo è la seguente: Ripe; l’istruzione I mentre expr assume valore diverso da 0 (cioè vero). L’esecuzione dell’istruzione while avviene nel seguente modo: viene valutata expr. Se il suo valore è diverso da 0 allora si esegue I. Tali operazioni vengono ripetute fin quando expr non assume il valore 0. ProdoGo di due numeri > 0 int x,y; /* Input */ int prod; /* Output*/ Leggi x; Leggi y; prod = 0; while (y != 0)} prod+=x; --y; } restituisci prod; Metodo: dal problema al programma Primo Passo: Iden;ficare: da; di input & risulta; in output int x,y; /* dati in input */ int prod; /* risultato in Output*/ \Pre: x,y >0 *\ ………………… \* Post: prod = x*y *\ € Secondo Passo: Sviluppare un’idea risolu;va (itera;va) x * y = x + x + x + ........+ x y volte Terzo Passo: estrapolare il processo itera;vo Inizializzazione € Proseguimento € 0 volte x + x + x + ........+ x = 0 i volte x + x + x + ........+ x i volte x +x + x + ........+ x +x (i+1) volte Terminazione € x + x + x + ........+ x = x * y y volte Quarto Passo: Definire il processo itera;vo (0) Iden;ficare le variabili coinvolte nel ciclo (1) Capire il valore iniziale di tali variabili (INIZIALIZZIONE del CICLO) (2) Come a par;re dall’ul;mo valore oGengo il seguente. (PROSEGUIMENTO) (3) Assicurarmi che qualche quan;tà cambi valore (in genere decrementata o incrementata) (4) Decidere quando ho raggiunto l’obiehvo: condizione di uscita dal ciclo o di terminazione. (CONDIZIONE di TERMINAZIONE DEL CICLO) (5) Verificare che la condizione di terminazione si verifica prima o poi come conseguenza di (3) (VERIFICA DELLA TERMINAZIONE DEL CICLO) Prima Soluzione Introduco una variabile i che conta il numero di iterazioni faGe. Inizializzazione: i = 0 (all’inizio ho faGo 0 iterazioni) prod = 0 Proseguimento: sommo x al precedente valore di prod (prod =prod +x) e incremento i di uno (++i) Condizione Terminazion/Uscita: Noto che dopo aver calcolato incremento i = y e quindi voglio terminare. La Condizione di Uscita è i = y è raggiunta i=0; prod = 0; while (i != y){ la condizione di entrata è la negazione della prod += x; condizione di terminazione ++i; } Seconda Soluzione Osservazione: i ed y contengono la stessa informazione Inizializzazione: prod = 0 Proseguimento: sommo x al precedente valore di prod (prod =prod +x) e decremento y di uno (‐‐y) Condizione Terminazion/Uscita: Noto che dopo non appena y=0 voglio terminare. La Condizione di Uscita è y == 0. prod = 0; while (y != 0){ prod += x; ‐‐y; } la condizione di entrata è la negazione della condizione di terminazione Verifica Terminazione \Pre: x,y >0 *\ prod = 0; while (y != 0){ prod += x; —y; } \* Post: prod = x*y *\ Verifica del raggiungimento della condizione di terminazione: ‐ Dalle precondiz. Sappiamo che >0. ‐ Ad ogni passo dell’iterazione y si decrementa di 1. ‐ Prima poi y arriverà ad essere 0. ‐ E quindi la condizione di terminzione y==0 è verificata. IL CICLO TERMINA Esempio: resto & quoziente Scrivere una algoritmo che da; due interi posi;vi x ed y, calcoli il resto e il quoziente della divisione di x per y int x,y; /* da; in input */ int q,r; /*da; in output*/ / * [Pre: x= X, X>0, y =Y, Y>0] */ / * [Post x=q*Y+r, r<Y] */ IDEA per una STRATEGIA: q lo possiamo calcolare soGraendo itera;vamente y da x fino ad oGenere un resto r < di y resto & quoziente: pseudo‐codice q lo possiamo vedere come il numero di volte che dobbiamo soGrarre y da x per oGenere un resto r<y. q = numero delle soGrazioni effeGuate r= resto di x dopo q soGrazioni Inizializzazione: q=0 (abbiamo effeGuato 0 soGrazioni) r=x (senza soGrazioni il resto è =x) Proseguimento: q=q+1 (soGraiamo una volta in più) r=r‐y (da ciò che resta di x soGraiamo y) Condizione di Terminazione: r<y (Non possiamo più soGrarre) Verifica del raggiungimento della Condizione di Terminazione: Ad ogni iterazione soGraggo y da r. Primo a poi r arriva ad Essere un numero < di y resto & quoziente: implementazione #include <stdio.h> int main(void){ int x,y; /* input */ int q,r; /*output*/ / * [Pre: x= X, X>0, y =Y, Y>0] * * [Post x=q*Y+r, r<Y] */ printf(“Immetti x e y”); scanf(“%d%d”,&x,&y); q=0; r=x; while (r>=y){ ++q; r-=y; } printf(“Resto = %d”,Quoz. =%d\n”,r,q); } Esempio: faGoriale Scrivere un programma itera;vo per calcolare il faGoriale di un numero naturale (intero >=0). n! = 1*2*3*.....*n. (ricordare che 0!=1) int n; /* input*/ int f; /* output */ /* [Pre n=N, N>=0] */ /* [Post f = N!] */ € faGoriale: algoritmo Calcoliamo la f per iterazioni successive 1× 2 × 3........× i 1× 2 × 3........× i × (i + 1) Usiamo una variabile i intera per contare da 0 a n e usiamo f per salvare valori delle fi € Inizializzazione: i=0 (0 è il più piccolo valore per n) f=1 (0!=1) Proseguimento: i=i+1 (passo al seguente i) f=f*i (calcolo la nuova f a par;re dal valore di i) Terminazione: ho calcolato n! quando i=n. Pertanto: Condizione di terminazione i=n faGoriale: implementazione #include <stdio.h> int main(void){ int n; /* input */ int f; /*output*/ int i; /* var ausiliare */ / * [Pre: n=N, N>=0] * * [Post f=N!] */ printf(“Immetti n”); scanf(“%d”,&n); i=0; f=1; while (i<n)} ++i; f*=i; } printf(“n = %d”,fatt=%d\n”,n,f); } Differenze ..... Cosa succede se cambio l’ordine nelle istruzioni nel ciclo while ? i=0; f=1; while (i<n)} f*=i; ++i; } i=0; f=1; while (i<n)} ++i; f*=i; } Il valore di f dipende dal valore calcolato della variabile !! Input n= 4; 1 Ciclo 0 ª iterazione [i=0,f=1] 2 Ciclo [i=0,f=1] 1ª iterazione [i=1,f=1] [i=1,f=0] 2ª iterazione [i=2,f=2] 3ª iterazione [i=3,f=6] 4ª iterazione [i=4,f=24] [i=2,f=0] [i=3,f=0] [i=4,f=0] massimo comune divisore Scrivere un programma itera;vo per calcolare il massimo comune divisore fra due interi posi;vi X e Y, usando la seguente proprietà del mcd: se X>Y allora MCD(X,Y)=MCD(X‐Y,Y) se Y>X allora MCD(X,Y) = MCD(X,Y‐X) se X=Y allora MCD(X,Y)=X=Y int x,y; /* input*/ int mcd; /* output */ /* [Pre x=X,y=Y, X,Y>0] */ /* [Post mcd = MCD(X,Y)] */ massimo comune divisore: algoritmo IDEA algoritmo: Se ad ogni passo dell’iterazione soGraggo y da x se x>y oppure soGraggo x da y se y>x, arriverò ad un momento in cui x=y. !! In entrambi i casi per la proprietà precedente non sto modificando il valore del massimo comune divisore di x ed y iniziali. Quando termino res;tuisco il valore trovato per cui x=y Inizializzazione: x e y hanno i loro valori originali. Proseguimento: x= x‐y se x>y oppure y =y‐x se y>x Terminazione: Non voglio con;nuare il ciclo non appena x=y. Condizione di terminazione:x=y massimo implementazione comune divisore: #include <stdio.h> int main(void){ int x,y; /* input */ int mcd; /*output*/ int i; /* var ausiliare */ / * [Pre: x=x,y=Y, X,Y>0] * [Post mcd= MCD(X,Y)] */ printf(“Immetti x ed y”); scanf(“%d%d”,&x,&y); while (x!=y)} if (x<y) x-=y; else y-=x;; } mcd =x; printf(“MCD =%d\n”,mcd); } * Il ciclo for Il ciclo for è analogo al ciclo while, serve cioè per ripetere del codice itera;vamente, specialmente in quei casi in cui il numero di iterazioni può essere indicizzato mediante una variabile intera. Il costruGo e’ definito dalla seguente sintassi for(expr1; expr2;expr3) I dove I è una istruzione composta. Il ciclo for è equivalente al while essendo esprimibile come un ciclo while della seguente forma: expr1; while (expr2) I expr3; quando expr2 è presente e non vi è una con;nue nel ciclo for. Il ciclo for La seman;ca può essere spiegata usando il ciclo while for(expr1; expr2;expr3) I Expr1: rappresenta le istruzioni di Inizializzazione Expr2: e’ la condizione di entrata nel ciclo Expr3: sono le istruzioni che in un ciclo while si occupano di incrementare o decrementare la variabile contatore for(i=1; i<=10; i++) sum +=i; i=1 while(i<=10){ sum +=i; ++i; } Metodo: Correttezza e Verifica Iterazione Cenni su verifica di correGezza dei cicli while Consideriamo l’esempio del prodoGo di due numeri. int x,y; prod =0; while(y !=0) prod +=x; --y; } Per verificare che questo ciclo sia correGo, studiamo come evolve la situazione della variabile prod. Cosa devo verificare ?. (1) Che inizialmente, cioè che dopo le istruzioni di inizializzazione, il valore di prod sia correGo (2) che da una iterazione all’altra il valore di prod cambia correGamente (3) che dopo aver concluso l’iterazione prod con;ene il valore che mi aGendo, cioè il prodoGo dei due numeri. Cenni su verifica di correGezza dei cicli while Per poter verificare il punto (2), non mi posso servire di valori concre; di prod, come potrei invece fare per verificare il suo valore dopo la inizializzazione. Devo trovare una proprietà che prod deve verificare AD OGNI ITERAZIONE e che mi garan;sca la correGezza del ciclo in qualunque istante: all’inizio, da un iterazione all’altra e alla fine dopo esserne uscito Tale propriètà per questa caraGeris;ca viene deGa INVARIANTE. verifica di correGezza dei cicli while Supponiamo di avere un ciclo della forma [Pre: P] expr1 while (expr2) I [Post: Q] Il ciclo è correGo se dalla precondizione P mi porta alla postcondizione Q. Cosa devo verificare per garan;rmi che il ciclo sia correGo? Data la proprietà Invariante, che chiamo Inv, devo verificare la correGezza delle seguen; tre specifiche: verifica di correGezza dei cicli while (1) Dopo l’inzializzazione l’invariante è vera [Pre: P] expr1 [Post: Inv] (2) se prima di entrare nel ciclo l’invariante si verifica , allora dop essere entrato ed aver eseguito le istruzioni I del ciclo l’invariante con;nua a valere [Pre: Inv, expr2 == vera] I [Post: Inv] (3) Non appena esco dal ciclo l’invarante implica la postcondizione Q del ciclo [Pre: Inv , expr2 == falsa] → [Post: Q] Struttura: Funzioni Programmazione struGurata La scriGura di un programma di grandi dimensioni, si basa sulla possibilità che offre un linguaggio di programmazione di implementare facilmente la scomposizione di problemi in soGoproblemi la possibilità di suddividere un programma su più files MODULARIZZAZIONE Il C è soGo entrambi ques; aspeh uno dei linguaggi più flessibile. In par;colare la scomposizione di problemi in soGoproblemi, viene implementata in C scomponendo un problema in piu funzioni, ognuna delle quali implementa un par;colare soGoproblema. Definizione e dichiarazione di funzione Per poter usare una funzione dobbiamo definire la funzione, cioè specificarne le istruzioni di C che definiscono ciò che la funzione deve eseguire, dichiarare la funzione, perchè le altre funzioni del programma “siano a conscenza” della presenza di quella funzione. L’uso di una funzione avviene aGraverso la chiamata/invocazione ad una funzione. Mentre in genere è bene tenere separate la definizione e la dichiarazione di una funzione, in cer; s;li di programmazione e possibile far coincidere la dichiarazione con la definizione. Definizione di funzione Esempio int faGoriale (int n){ int i=1, product =1; for (i=2;i<=n,++i) product*=i; return product; } Il primo int indica che la il valore res;tuito dalla funzione viene conver;to a int prima di essere passato. La lista dei parametri in questo caso è formata da int n che specifica che la funzione riceve in input un valore intero. Il parametro formale n può essere usato come una normale variabile all’interno del corpo della funzione. Scatole Nere e Flusso di Informazioni Le funzioni possono essere pensate come delle scatole nere, che eseguono determinate operazioni e che comunicano con l’esterno aGraverso i parametri e il valore res;tuito in output. par1 par2 parn f output Quando un’altra funzione ha bisogno di usare la funzione f, esegue una chiamata a f passandole in input i parametri ai quali vuole applicare la funzione f, nello stesso ordine in cui sono specificate nella definizione delle funzione f. /* calcola i faGoriali da 1 a MAX e li stampa /* for(i=1;i<=MAX,++i){ faGoriale(i); prind(“%d”,i); } Proto;pi di funzioni Per poter usare una funzione f in una altra funzione g è necessario che g sia “a conoscenza” dell’esistenza di f. In C ciò avviene mediante la dichiarazione della funzione mediante ciò che viene chiama; normalmente il proto;po della funzione. EsaGamente come per la variabili è buona regola dichiarare le funzioni prima di usarle. Ciò avviene sempre aGraverso i proto;pi delle funzioni. In un proto;po specifichiamo il nome della funzione Il ;po di dato res;tuito dalla funzione i ;pi dei parametri nell’ordine esaGo in cui compaiono nella definizione della funzione Parametri formali e aGuali Come abbiamo visto le funzioni prevedono in fase di definizione la specifica dei parametri. Tali parametri sono deh parametri formali in quanto specificano l’ordine con cui i parametri sono passa; all’aGo di una chiamata e perché sono usa; all’interno della definizione di una funzione. I parametri usa; all’interno di una chiamata di funzione invece sono deh parametri aGuali in quanto indicano i valori aGuali sulla quale una funzione è chiamata a lavorare. parametro aGuale in una chiamata ad una funzione possono essere un’ espressione e non sempre sono solo variabili. Al contrario i parametri formali sono solo variabili Istruzione return L’istruzione return si usa nelle funzioni per res;tuire il controllo all’ambiente chiamante per permeGere alla funzione di res;tuire l’output. Esempio . Una funzione cha calcola il valore assoluto double val_ass (double x){ if (x>=0.0) return x; else return –x; } Non ha bisogno di parentesi Chiamata di funzione e ordine dei parametri Supponiamo di avere una funzione cmp che riceve due interi e stampa un messaggio i errore se la radice quadrata del primo e maggiore del secondo. #include<stdio.h> proto;po void cmp(int, int); int main(void){ int n,m; ........./* legge m,n e li modifica */ cmp(m,n); cmp(n,m); } void cmp(int a, int b){ if sqrt(a) > b prind(“ERRORE”); } prima chiamata seconda chiamata definizione Esempio Scrivere una funzione che dato un intero n calcoli e stampi la radice quadrata di tuh gli interi da 1 a n usando l’algoritmo visto prima. #include<stdio.h> double rad_quad(int); /* prototipo della funzione rad_quad*/ int main(void){ int n; /*input*/ printf(“Immetti n:”); scanf(“%d”); /* legge l’input */ for (i=1;i<=n,++i) printf(“La radice quadrata di %d e`%f”,i,rad_quad(i)); } Esempio double rad_quad(int a){ /* [Pre: a=A] */ /* [Post:rad_quad = radice quadrata di a secondo l’algoritmo Newton Raphson */ double x0,x1; x0 = 1; // inizializzo x0 x1 = 0.5*(x0+(a/x0)); // inizializzo x1 while (x0 != x1){ // calcola la radice x0 = x1; // quadrata x1 = 0.5*(x0+(a/x0)); } return x0; } Precondizione e Postcondizione Ogni volta che scriviamo e implemen;amo una funzione dobbiamo specificarne l’interfaccia con l’esterno. Vuol dire specificare precondizione sui parametri e postcondizione sull’output. Cio e’ bene per una verifica di correGezza, per la leggibilità e sopraGuGo anche per la ges;one degli errori. Esempio int sum (int n) { /* [Pre: n>0] i2 * [Post: sum = ] ∑ i=1n } if (n<=0) prind(“Error”); else ........ /* calcola la somma dei quadra; */ € Passaggio dei parametri per indirizzo A parte il passaggio dei parametri per valore esiste un’altra metodologia deGa: passaggio dei parametri per indirizzo o referenza. L’idea è quella di permeGere la modifica del valore del parametro aGuale. Alla funzione vine passato non il valore del parametro aGuale, ma il suo l’indirizzo in memoria. Parametro AGuale A 243 Parametro Formale X 243 per valore ind(A)= 2034 B 243 ind(B)= 1089 Y 1089 per indirizzo Passaggio dei parametri per indirizzo In C la modalità di passaggio dei parametri è sempre quella per valore. È possibile realizzare però l’effeGo di un passaggio per indirizzo nel seguente modo: u;lizzando il costruGore di ;po puntatore per la definizione dei parametri formali, che vedremo più avan;. usando l’operatore di dereferenziazione di puntatore all’interno del corpo della funzione (* o ‐>) passando al momento della chiamata della funzione come parametro aGuale, un indirizzo di variabile (usando eventualmente l’operatore di indirizzo &) Visibilità delle variabili Ambien;: Ambiente globale è l’insieme dei da; dichiara; nella parte dichiara;va globale Ambiente locale di una funzione è insieme di elemen; dichiara; nella parte dichiara;ve e nell’intestazione Ambiente di blocco l’insieme dichiara; nella parte dichiara;va di un blocco Il conceGo di ambiente permeGe di usare gli iden;ficatori con molta flessibilità. Infah è permesso usare gli stessi iden;ficatori anche on significa; diversi purché siano in ambien; diversi Ambiente ed esecuzioni di funzioni Abbiamo visto che la conseguenza di una chiamata di funzione è l’ahvazione di un ambiente di memoria o stato della funzione (e di una corrispondente allocazione di memoria) che viene rilasciata nel momento in cui la funzione res;tuisce il valore dovuto con return oppure termina la sua esecuzione. ambiente main int sum (int, int); int main(void){ int x,y,z; x= 4; y = 6; z = sum(x,y); } int sum (int x, int y){ int f; f =5; return x+y+f; } x y ambiente sum z x y 4 4 4 6 4 6 4 15 f 6 6 5 Strumen;: Ricorsione Funzioni che chiamano se stesse Abbiamo visto che una volta dichiarata una funzione può essere chiamata da qualunque altra funzione. In par;colare dunque una funzione potrebbe chiamare se stessa..... Chiamata direGa int f( int a){ int z; z = f(a); return z; } Chiamata indireGa int f( int a){ int z; z = g(a); return z; } int g (int b){ int z = f(b); } Che succede ........ ? A prima vista la cosa può sembrare sorprendente perché implicitamente è come se stessimo dicendo che per calcolare la funzione f dobbiamo calcolare la funzione f stessa, oppure che per calcolare f dobbiamo calcolare g che a sua volta ha bisogno di calcolare f per essere calcolata.......... È possibile ??? Si . La ricorsione è uno strumento, usato non solo nella Programmazione, la cui base razionale sono le seguen; due regole: La soluzione di un caso generico di un problema può essere oGenuta a par;re dalla soluzione di un caso più semplice dello stesso problema. Vi sono dei casi semplici o base che si sanno risolvere molto facilmente senza dover ricorrere allo stesso problema. Formulazione ricorsiva di problemi e algoritmi Un analisi aGenta mostra che la ricorsione si nasconde dietro mol;ssimi conceh, specialmente quelli a caraGere matema;co. Esempi Supponiamo di sapere solo sommare 1 ad un intero , S(x) = x+1. Definiamo la somma di due numeri naturali x e y. +(x,y) = SUCC(+(x,y‐1)) +(x,0) = x Supponiamo di sapere sommare due numeri +(x,y) Definiamo il prodoGo di due numeri naturali x e y. *(x,y) = +(*(x,y‐1),x) *(x,0) = 0 caso ricorsivo caso base Formulazione ricorsiva di problemi e algoritmi Esempi Supponendo di disporre del prodoGo di due numeri naturali, Definiamo il faGoriale di un numero n. n! = (n‐1)!*n 0!=1; Supponendo di disporre della somma tra due numeri, definiamo la somma di n numeri a1,...,an somma(a1,....,an)=an+ somma(a1,....,a(n‐1)) somma(a1)=a1; Supponendo di disporre del prodoGo cartesiano tra due insiemi, e dell’unione tra due insiemi, definire l’insieme delle par;zioni di una lista di n elemen; a1,...,an. perm(a1,...,an)= perm(a1)={a1} Metodo: dal problema al programma FaGoriale #include <stdio.h> int fatt(int) int main(void){ int x,y; printf(“Immetti x ”); scanf(“%d”,&x); printf(“Il fattoriale di %d è:”,x,f(x)); } int fatt(int n){ /* [Pre: n>=0] [Post: fatt = n!] */ int ris; if (n>0) ris = n*fatt(n-1); else ris = 1; return ris } Programmazione ricorsiva 1. 2. 3. 4. 5. 6. Formulare un’idea risolu;va ricorsiva del problema. Scrivere il proto;po delle funzione specificando precisamente 1. parametri 2. pre condizione e 3. post‐condizione Iden;ficare il parametro(i) della ricorsione, ovvero la variabile(i) dove contenuta l’informazione che si semplifica ad ogni chiamata Formulare un caso ricorsivo o induhvo, che permeGa di risolvere il problema per un caso generico in funzione di un caso “più semplice” 1. Per quali valori del parametro della ricorsione 2. Cosa fare Un caso base, che permeGa di risolvere il problema in maniera direGa, senza dover cioè ricorrere alla soluzione di un caso più semplice dello stesso problema 1. Per quali valori del parametro ricorsione (complemento del CR) 2. Cosa fare Terminazione. Assicurarci che effehvamente via sia una quan;tà che si s;a “semplificando” (in genere riducendo) nella definizione del caso ricorsivo e che tale semplificazione conduce al caso base e quindi la ricorsione termina. NOTA IMPORTANTE. Per poter implementare una funzione ricorsiva dobbiamo definire una funzione nuova. Non possiamo usare il main. ProdoGo Il prodoGo di due numeri naturali. 1. Idea: *(x,y) = *(x,y‐1) + x *(x,0) = 0 2. 3. 4. Proto;po funzione Int prod(int x, int y) /* Pre: x,y >0 */ /* Post: prod(x,y)= x*y */ Param ricorsione: y caso ricorsivo o induhvo. 1. Cosa fare : prod(x,y) = ris = prod(x,y‐1)+x. 2. Quando: y > 0. ProdoGo 5. Un caso base *(x,0) = 0. 1. Quando: y = 0. 2. Cosa Fare: ris = prod(x,0)=0 6. Verifica della Terminazione. 1. A ad ogni chiamata ricorsiva y decresce di un’unità. 2. All’inizio (vedi Pre:) è >0/ 3. Per cui ci sarà una chiamata in cui arriverà ad essere 0. 4. In quel caso sapremo risolvere il prodoGo direGamente come specificato nel caso base. NOTA IMPORTANTE. In una funzione ricorsiva vi sarà sempre una selezione (if‐then‐else) per poter dis;nguere tra caso base e casi ricorsivo. ProdoGo:implementazione #include <stdio.h> int f(int, int) int main(void){ int x,y; printf(“Immetti x ed y”); scanf(“%d%d”,&x,&y); printf(“Il prodotto di %d e %d è %d:”,x,y,f(x,y)); } int f(int x, int y){ /* [Pre: x,y >=0] [Post: f = x*y] */ if (y>0) ris = x+f(x,y-1)); else ris = 0; return ris; } FaGoriale Il prodoGo di due numeri naturali. 1. Idea: n! = n* (n‐1)! 0! = 1 2. 3. 4. Proto;po funzione Int fatt(int n) /* Pre: n >= 0 */ /* Post: fatt(n)= n! */ Param ricorsione: n caso ricorsivo o induhvo. 1. Cosa fare : [faG(n) = ] ris = faG(n‐1)*n. 2. Quando: n > 0. FaGoriale 5. Un caso base 0! = 1. 1. Quando: n = = 0. 2. Cosa Fare: [faG(0)=]ris=1; 6. Verifica della Terminazione. 1. A ad ogni chiamata ricorsiva n decresce di un’unità. 2. All’inizio (vedi Pre:) è >0/ 3. Per cui ci sarà una chiamata in cui arriverà ad essere 0. 4. In quel caso sapremo risolvere il prodoGo direGamente come specificato nel caso base. FaGoriale #include <stdio.h> int fatt(int) int main(void){ int x,y; printf(“Immetti x ”); scanf(“%d”,&x); printf(“Il fattoriale di %d è:”,x,f(x)); } int fatt(int n){ /* [Pre: n>=0] [Post: fatt = n!] */ int ris; if (n>0) ris = n*fatt(n-1); else ris = 1; return ris } SVILUPPARE UN’IDEA RICORSIVA LA POSTCONDIZIONE AIUTA A SVILUPPARE UN’IDEA RICORSIVA ! PERCHE ? DICE PRECISAMENTE COSA RESTITUISCE LA CHIAMATA AD UNA FUNZIONE SENZA CONOSCERE LA FUNZIONE NELLA RICORSIONE DOBBIAMO USARE UNA CHIAMATA ALLA FUNZIONE PER DEFINIRE IL CASO RICORSIVO E QUINDI SENZA SAPERE COME È FATTA LA FUNZIONE Somma‐cifre Si vuole scrivere una funzione ricorsiva che sommi le cifre di un numero naturale decimale n dato. int sommacifre(int n) /* pre: n >=0 */ /* Post: se n è aka(k‐1)….a0, allora sommacifre(n)=a0+…+ak */ IDEA per una definizione ricorsiva di sommacifre: ‐ ‐ Supponiamo di avere il nostro n = aka(k‐1)…. a1a0 Noto che se chiamo sommacifre su m = aka(k‐1)…. a1 allora sommacifre(n) = a0+ a1+………….+ak a0+ sommacifre(m) ‐ Cosa sono a0 e m in funzione di n ? ‐ a0 = n % 10 ‐ m = n /10 Somma‐cifre Il prodoGo di due numeri naturali. 1. Idea: sommacifre(n) = n % 10 + sommacifre(n/10) sommacifre(0)=0 2. 3. 4. Proto;po funzione Int sommacifre (int n) /* Pre: n >= 0 */ /* Post: se n è aka(k-1)….a0, allora, sommacifre(n)=a0+…+ak */ Param ricorsione: n caso ricorsivo o induhvo. 1. Cosa fare : [sommacifre(n) = ] ris = sommacifre(n/10)+n/10. 2. Quando: n > 0. Somma‐cifre 5. Un caso base sommacifre(0) = 0. 1. Quando: n = = 0. 2. Cosa Fare: [sommacifre(0)=]ris=0; 6. Verifica della Terminazione. 1. A ad ogni chiamata ricorsiva n viene divisa per 10 2. All’inizio (vedi Pre:) è >=0/ 3. Per cui ci sarà una chiamata in cui arriverà ad essere 0. 4. In quel caso entriamo nel caso base. Somma cifre:implementazione #include <stdio.h> int sommacifre(int) int main(void){ int x,y; printf(“Immetti x ”); scanf(“%d”,&x); printf(“La somma delle cifre di %d è:”,x,sommacifre(x)); } int sommacifre(int n){ /* [Pre: n>=0] [Post: sommacifre = soma delle cifre di n]*/ int ris; if (n>0) ris = (n%10)+sommacifre(n/10)); else ris = 0; return ris; } Quasi‐primi Si vuole scrivere una funzione ricorsiva che sommi dica se un numero naturale n È divisibile unicamente per 2, 3 e 5. Int qprimo(int n) /* pre: n >=0 */ /* Post: qprimo(n)=1 sse n è divisibile solo per 2,3 e 5 */ IDEA per una definizione ricorsiva di sommacifre: ‐ Se n è divisibile per 2, allora qprimo(n) = qprimo (n/2) ‐ Se n è divisibile per 3, allora qprimo(n) = qprimo (n/3) ‐ Se n è divisibile per 5, allora qprimo(n) = qprimo (n/5) Se n=2 opure 3 oppure 5, allora qprimo(n) = 1 Quasi‐primi Il prodoGo di due numeri naturali. 1. Idea: qprimi(n) = qprimi(n/) sommacifre(0)=0 2. Proto;po funzione Int qprimo(int n) /* Pre: n >= 0 */ /* Post: qprimo(n)=1 sse n è divisibile solo per 2,3 e 5 */ 3. Param ricorsione: n 4. caso ricorsivo o induhvo. 1. Cosa fare : se n è divisibile per 2, ris = qprimi(n/2) se n è divisibile per 3, ris = qprimi(n/3) se n è divisibile per 5, ris = qprimi(n/5) 1. Quando: n non 2,3, 5. Quasi‐primi 5. Un caso base 1. Quando: n = = 2,3,5. 2. Cosa Fare: [sommacifre(0)=]ris=1; 3. Altrimen; ris =0 6. Verifica della Terminazione. 1. A ad ogni chiamata ricorsiva n viene divisa per 2,3,5 2. All’inizio (vedi Pre:) è >=0/ 3. Per cui ci sarà una chiamata in cui arriverà ad essere 0 oppure 2,3,5. 4. In quei casi entriamo nel caso base. Dati: Vettori Vettori Molte volte i programmi fanno uso di dati omogenei,tali cioè da dover essere trattati nello stesso modo. Esempio. Supponiamo di dover usare un dato per contenere i voti degli studenti di un corso. Non sappiamo in anticipo il numero di studenti. Non possiamo dichiarare un numero variabile di variabili scalari. I linguaggi mettono a disposizione del programmatore strutture dette array che accorpano dati omogenei e la cui dimensione può essere decisa dal programmatore. Vettori Gli array o vettori possono essere pensati com un’unica variabile Di tipo vettore la cui dimensione può essere specificata. La dichiarazione di un array che contiene interi il cui nome è voto e di dimensione 3 è: int voto[3]; dimensione del veGore Nome del veGore Tipo dei da; contenu; Vettori float distanze[10]; 2.34 2.0 posizioni 0 1 34.2 54.0 2.34 2 3 4 .98 5 2.34 2.42 6.6 6 Per accedere ad un elemento di un array dobbiamo specificare la posizione. Esempio Il primo elemento degli array è in posizione 0. distanze[0] (=2.34) L’ul;mo elemento dell’array distanze è in posizione 9. distanze[9] (=3.4) Limite inferiore = 0 Limite superiore = lunghezza - 1 Lunghezza = limite superiore +1 7 8 3.4 9 Vettori Se prova è un array di N elemen; int prova[N]; allora l’espressione prova[expr] permeGe di accedere ad un singolo elemento dell’array prova. Prima cosa viene valutata expr. Quindi si accede all’elemento del veGore in posizione (expr ‐1). Si tenga presente che se la valutazione di expr produce un valore inferiore a 0 o superiore a N‐1, allora viene segnalato un Errore in fase di esecuzione del programma (e non in compilazione !!) E’ compito del programmatore assicurarsi che gli indici assumano valori Nell’intervallo correGo. Somma elementi array Sia tempi un vettore di 100 elementi interi. Scriviamo il codice Per sommare tutti i tempi in un’unica variabile sum. Idea iterativa: ad ogni iterazione sommiamo in sum un elemento dell’array tempi in maniera incrementale (alla 1ª iterazione il 1° elemento, alla 2ª iterazione il 2 ° elemento…) Inizializzazione: sum = 0 (inizialmente non abbiamo ancora sommato nulla) i = 0 I è un variabile indice che serve per trattare tutto l’array e deve iniziare dal primo elemento, quello in posizione 0. Proseguimento: Aggiorno sum sommandogli l’elemento corrente di tempi sum = sum + tempi[i] incremento i di 1 per passare alla prossimo elemento del vettore i = i+1 Terminazione: Quando i = 99 sommo l’ultimo elemento di tempi. Incremento i a 100 e ho terminato. Devo quindi uscire dal ciclo. Pertanto i == 100 è la condizione di uscita Verifica Terminazione: Incremento i ad ogni iterazione iniziando da 0. Primo poi raggiungo i=100 e termino Somma elementi array Per poter elaborare tuh gli elemen; di un array è buona norma usare un ciclo for. Esempio: Somma degli elemen; di un array #define dim int tempi[dim]; int i; int sum; 100 /* vettore di 100 interi */ /* variabile indice per scorrere l’array */ /* variabile per la somma degli elementi degli array */ sum =0; for(sum=0,i=0; i<dim; i++) sum + = tempi[i]; /* soluzione con il while */ i=0; /* inizializzazione dell’indice */ sum = 0; /* inizializzazione della variabile sum */ while (i<dim) /* Invariante: 0<=i<=dim, sum += tempi[i++]; */ Tipi definiti dal programmatore Il C mette a disposizione una parola chiave per permetterci di definire nuovi tipi. La parola chiave usata è typedef e la sua sintassi è la seguente typedef <tipo esistente> <nuovotipo> Esempio typedef int gradi; gradi a,b,c,d; Per definire un nuovo tipo sugli array si usa la seguente dichiarazione: typedef int VotiCanale2[100]; /* VotiCanale2 è un tipo array di 100 elementi interi */ VotiCanale2 Appello1, Appello2, Appello3; /* ho dichiarato tre vettori di 100 interi */ Esempi sull’uso di typedef con Array 1. Quando scrivo typedef float NuovaLista[20]; definisco il ;po NuovaLista, come un array di 20 elemen; di ;po float in cui l’indice può assumere valori da 0 a 19. 2. La dichiarazione di un ;po array può essere implicita nalla dichiarazione di una variabile. SOno equivalente le seguen; due dichiarazioni: int lista[20]; /* lista è un vettore di 20 interi */ typedef int List[20]; /* List è il tipo array di 20 interi */ List lista; /* lista è una variabile di tipo List */ Esempi sull’uso di typedef con Array 2. Mentre non conviene usare il typedef per la seguente dichiarazione typedef float VettorediReali[100]; VettorediReali v1,v2,v3; e semplificarlo come float v1[100],v2[100],v3[100]; Conviene invece usare il typedef in un esempio come questo typedef float PioggeMensili[12]; typedef float IndiciBorsa[12]; PioggeMensili Piogge02, Piogge03,Piogge04; IndiciBorsa Indici02, Indici03, Indici04; Dati: Puntatori Puntatori Finora siamo abituati a pensare ad una variabile come ad un nome di una zona di memoria. Al contenuto di una variabile accediamo attraverso l’uso del suo nome : int x,a=0; x = a; significa preleva il valore contenuto nella cella il cui nome è a e copialo nella cella il cui nome è x. Introduciamo ora le variabili di tipo puntatore. Tali variabili consentono di immagazzinare indirizzi di memoria. Esempio int *p; dichiara una variabile p di tipo puntatore ad interi.I valori ammessi per questa variabile sono interi positivi interpretati come indirizzi fisici di memoria assegnata al C nel sistema. Puntatori Esempio int y; int *p; Valori ammessi per p sono ad esempio: p = 0; p= NULL; /* equivalente a p =0 */ p = &y; /* p punta a y */ p = (int *) 1776; /* indirizzo assoluto in memoria */ Lo * è di fatto un operatore unario detto operatore di indirizzamento indiretto o dereferenziazione. È quindi soggetto alle stesse regole di priorità ed associatività degli altri operatori. Norma int *p; /* p assume come valori indirizzi di memoria */ /* *p contiene il valore contenuto all’indirizzo assunto da p */ Puntatori Esempio int a=1,b=2,*p; 1 2 a b p p = &a; 1 2 a b = *p; /è quindi equivalente a b = a; */ b 1 1 a b p p Tipo puntatore Per definire un tipo puntatore, possiamo usare il typedef. Esempio typdef int *TipoPuntatoreIntero /* definisce un tipo per puntatori a interi */ int x,y; TipoPuntatoreIntero p,q; /* equivalente a int *p,*q; */ Cosa succede con le istruzioni ? p= &x; /* p punta a x */ q= &y; /* q punta a y */ p=q; /* p punta all’indirizzo dove punta q*/ p q p x y q x y Valore NULL Se un variabile puntatore ha valore NULL allora *P è indefinito. In altre parole P = NULL, significa che P non punta ad alcuna informazione significativa. Graficamente P= NULL viene denotato come P Chiamata per indirizzo in C Il passaggio dei parametri per indirizzo permette in altri linguaggi di cambiare il valore dei parametri attuali all’interno della funzione dove vengono passati. In C non esiste una modalità di passaggio dei parametri per indirizzo. Ma è possibile modificare i valori di variabili all’interno di una funzione usando i puntatori. Esempio int main(void){ int i=3, j=5; swap(&i,&j); printf(“i=%d,j=“d\n,i,j); } void swap(int *p, int *q){ int aux; aux = *p; *p = *q; *q = aux; } Chiamata per indirizzo:simulazione Nel main i 3 j p 5 q aux = *p; i aux j 3 p i aux j 5 p i q 3 aux j 5 3 5 q *q = aux; p 5 q *p = *q; In Swap 3 aux 3 Chiamata per indirizzo in C L’effetto di una chiamata per indirizzo in C è ottenuto: 1. 2. 3. Dichiarando una parametro della funzione come puntatore Utilizzando il puntatore deferenziato all’interno della funzione. Passando come parametro attuale nella chiamata alla funzione l’indirizzo della variabili che si vuole passare “per indirizzo” Dati: Vettori e Puntatori Relazione tra puntatori ed array Il nome di un array è di per sé un indirizzo. int a[100]; /* a è l’indirizzo di inizio delle celle di memoria assegnate all’array a cioè a = &a[0]*/ Quindi di fatto a è completamente analoga a una variabile puntatore. Infatti in C i puntatori possono essere trattati esattamente come array e quindi essere indicizzati. Esempio int a[100]; int *p; a[i] è equivalente a *(a+i) p[i] è equivalente a *(p+i) Relazione tra puntatori ed array int a[100]; int *p; Un espressione come a+i assume come valore l’indirizzo di memoria ottenuto spostandosi di i celle dall’indirizzo di basse assegnato al vettore a in fase di esecuzione. Quindi a+i è equivalente a &a[i]. > Ciò vuol dire che usando l’operatore di dereferenziamento * un altro modo di accedere all’elemento a[i] è quello di usare l’espressione *(a+i). > Analogamente possiamo procedere per un puntatore generico. > Espressioni come p+i denotano l’indirizzo ottenuto muovendosi di i posizioni dall’indirizzo p. Il linguaggio C consente di eseguire operazioni di somma e sottrazioni su puntatori per consentire al programmatore maggiore flessibilità nella gestione dell’indirizzamento esplicito e nella gestione della memoria Se p punta a un particolare tipo di dato, l’espressione p+1 fornisce l’indirizzo in memoria per l’accesso o la memorizzazione della prossima variabile di quello stesso tipo di dato nella memoria. Relazione tra puntatori ed array: differenze int a[100]; int *p, *q; a è un puntatore costante, cioè non può assumere altri indirizzi di memoria, a parte quello assegnatogli in esecuzione al momento della sua dichiarazione. Per cui espressioni come a = p; ++a; a+= 2; che avrebbero l’effetto di modificare l’indirizzo di a non sono ammesse e quindi scorrette. Mentre p e q possono assumere qualunque indirizzo, quindi espressioni come: p =q, ++p; p +=2; sono invece corrette ed ammesse e sono parte dell’aritmetica dei puntatori Relazione tra puntatori ed array int a[100]; *p, sum; Se in esecuzione al vettore a è assegnata 300 come indirizzo base ed un intero occupa 4 byte avremo che l’indirizzo del primo elemento di a è 300 (cioè &a[0]=300), l’indirizzo del secondo elemento di a è 304 (cioè &a[1] =304) e cosi via. L’assegnamento p=a; ha come effetto quello di assegnare l’indirizzo 300 al puntatore p. Vale a dire è equivalente a scrivere p = &a[0]. p= a+1; ha come effetto quello di assegnare a p l’indirizzo 304.Vale a dire è equivalente a scrivere p = &a[1] Relazione tra puntatori ed array int a[100]; *p, sum=0,i; Per cui la somma degli elementi di un vettore Versione 1 for(i=0;i<dim,i++) sum += a[i]; Come si può scrivere? Versione 2 for( p=a; p<&a[100]; p++) sum += *p; Versione 3 for( i=0; i<100; i++) sum += *(a+i); Versione 4 p=a; for(i=0; i<100; i++) sum += p[i]; Aritmetica dei puntatori:dimensioni Sizeof è un operatore che si usa per sapere l’occupazione in byte di una variabile o di un tipo. int a[5]; sizeof(a[3]) restituisce il valore 4 sizeof(a) restituisce il valore 20 Se p e q sono due puntatori ad elementi di un array, allora la loro differenza p-q restituisce il valore int che rappresenta il numero di elementi nell’ array tra p e q (se p<q). Comunque siccome un elemento di un array è salvato su più locazioni di memoria (Esempio i double occupano 8 byte), la differenza in termini di locazioni di memoria tra p e q è differente tra la differenza in termini di elementi dell’array double a[2], *p,*q; p =a; q=p+1; printf(“q-p=%d\n”, q-p); /* stampa 1 */ printf(“q-p=%d\n”, (int) q – (int) p); /* stampa 8 */ Metodo: Iterazione su Vettori Ricerca di un elemento in un array Idea algoritmo Cerchiamo l’elemento nell’array a partire dal primo elemento e ci fermiamo quando troviamo l’elemento o quando non abbiamo piu elementi da cercare i l’elemento non è presente Inizializzazione: i=0; trovato= 0 (falso) Proseguimento: se t[i]=el, allora abbiamo terminato. quindi trovato =1; altrimenti proseguiamo con il prossimo elemento da trattare. Quindi i++ Terminazione: trovato =1 oppure i=dim. Verifica Terminazione: incrementiamo ad ogni iterazione, quindi prima o poi raggiungiamo l’ultimo elemento oppure se troviamo l’elemento trovato = 1 Ricerca di un elemento in un array int ricerca(int *t, int dim, int el){ int trovato=0; int i=0; while (! trovato && i< dim){ trovato = ((*(t + i++)==el) ?1 :0); } return trovato; } Conta‐elemen;: pseudo‐codice Scrivere un algoritmo che conti quanti pari e dispari vi sono in un array di interi di dimensione d (cioè le posizioni vanno da 0 a d-1). Iterativo. Ricorriamo tutto il vettore e ad ogni passo incrementiamo il cnt dei pari o dei dispari a seconda che l’elemento del vettore sia pari o dispari i veGore traGato Inizializzazione: i=0; cnt_pari= 0, cnt_dsp=0 Proseguimento: se t[i] mod 2 =0, allora ++cnt_pari altrimento cnt_dsp++, incremento i Terminazione: i=d. Verifica Terminazione: incrementiamo ad ogni iterazione, quindi prima o poi raggiungiamo l’ultimo elemento Programma void ricerca(int *t, int dim, int *cnt_dsp, int *cnt_pari){ int i; *cnt_pari = *cnt_dsp =0; i=0; while (i< dim){ if ((t[i] % 2) == 0) ++(*cnt_pari); else ++(*cnt_dsp); i++; } Chiamata dal main int cnt_dsp, int cnt_pari; int t[dim]; ricerca(t,dim,&cnt_dsp,&cnt_pari); occorrenze elemento in array: iterativo Scrivere un algoritmo che conti il numero di volte che occorre un elemento in un array di interi di dimensione d (cioè le posizioni vanno da 0 a d-1). Iterativo. Ricorriamo iterativo tutto il vettore e ad ogni passo incrementiamo un cnt Se l’eelmento attuale è quello cercato i veGore traGato Inizializzazione: i=0; cnt= 0 Iterazione: se t[i] = el , allora ++cnt, incrementiamo i Terminazione: i=d. Verifica Terminazione: incrementiamo ad ogni iterazione, quindi prima o poi raggiungiamo l’ultimo elemento occorrenze elemento in array: iterativo int occ_el(int *t, int dim, int el){ int i,cnt; cnt =0; i=0; while (i< dim){ if ((t[i++] == el) ++cnt; } occorrenze elemento in array: ricorsivo Scriviamo la funzione occ_el(t,i,el)= # occorrenze di el in t Caso Base: i=0; occ_el(t,0,el) = 1 se t[0]= el, ed è = 0 altrimenti 0 Caso Ricorsivo: i>0; occ_el(t,i,el) = occ_el(t,i-1,el)+1 se t[i] == el, = occ_el(t,i-1,el)+0 se t[i] != el occ_el(t,i-1, el)= # occorr. di el in t[0,...,i-1] i Terminazione: il parametro della ricorsione si decrementa ad ogni chiamata. Inizianndo da i=d-1 prima o poi raggiungeremo il caso base i=0 occorrenze elemento in array: ricorsivo Algoritmo int occ_el(int *t, int i, int el){ int ris; if (i==0) ris = ((t[0]==el)? 1:0); else ris = occ_el(t,i-1,el)+(t[i]==el) ?1:0); } return ris; } Chiamata dal main int occ, el, t[dim]; occ=occ_el(t,dim-1,el); Esercizi su Array 1. 2. 3. 4. 5. 6. 7. Scrivere un algoritmo per determinare se in un array di interi vi sono solo elementi dispari. Scrivere un algoritmo per determinare se una tavola è ordinata crescentemente Scrivere un algoritmo iterativo per calcolare la media e la varianza di un array di reali. Scrivere un algoritmo iterativo per calcolare la media e la varianza di un array di reali in modo che ogni elemento dell’array venga ispezionato solo una volta. Scrivere una algoritmo per decidere se in un array con un numero di elementi pari t[0,...,2N-1] gli elementi di indice pari sono ordinati in ordine crescente e quelli di indice dispari in ordine decrescente Scrivere una procedure che modifichi un array sommando 1 a tutti i suoi elementi Scrivere un algoritmo per contare il numero di cambi di segno in un array il cui primo elemento è differente da 0. Un cambio di segno si verifica quando nell’array vi sono due interi di segno opposto contigui o separati solo da 0. BubbleSort Supponiamo di avere un array non ordinato di lunghezza d e di volerlo ordinare L’idea del bubble sort e’ quella di ordinare l’array come segue: ad ogni iterazione (i=0,...dim-1) vogliamo inserire il più piccolo elemento della parte da dim ..... fino ad i e per trovare il più piccolo elemento nella parte dim....i confrontiamo tutti gli elementi adiacenti a partire dagli ultimi due scambiandoli di posizione se il più piccolo segue il più grande Ordinata i Muoviamo il più piccolo elemento verso la posizione i BubbleSort: pseudo-codice Ciclo esterno Inizializzazione: i=0; Proseguimento: Sposto usando il ciclo interno il più piccolo elemento di t[i,....,d-1] in posizione t[i]. Terminazione: ho terminato quando ho ordinato le d-1 posizioni del vettore. L’ultimo elemento sarà certamente ordinato. Quindi i = d-1 è la condizione di terminazione Ciclo interno: Iniziando dall’ultimo elemento e scendendo fino alla posizione i scambio nel vettore due valori contigui se il piú grande precede il piú piccolo Inizializzazione: j=d-1; Iterazione: confronto t[j] con t[j-1] e se t[j-1] è più grande li scambio nel vettore. Decremento j di 1. Quindi j--. Terminazione: ho terminato quando j == i BubbleSort: programma void bubble_sort(int *t, int dim, int el){ int i,j; for(i=0;i<dim; i++) for(j=dim-1; j>i; j--) if (t[j-1]>t[j]) swap(&t[j-1],&t[j]); } void swap(int *p, int *q){ int aux; aux = *p; *p = *q; *q = aux; Metodo: Iterazione su Vettori Importanza delle Ipotesi sui dati Elementi con somma > 0 Scrivere una algoritmo che dati due vettori t e s conti il numero di coppie (i,j) tali che t[i]+s[j]>0. Scrivere una algoritmo più efficiente nel caso in cui i due vettori siano ordinati. I Algoritmo. Per ogni elemento del primo array ricorriamo tutto il secondo array e contiamo il numero di coppie (i,j) tali che t[i]+s[j] >0. I ciclo Inizializzazione: i=0 Iterazione: Dato t[i] Ricorriamo tutto per contare quante coppi (i,j) ci sono tali che t[i]+s[j] Terminazione: i=d1 Verifica Terminazione: Ad ogni iterazione incrementiamo i. II Ciclo Inizializzazione: j=0; Iterazione: se t[i]+s[j] >0 incrementa il cnt. Terminazione: j=d2 Elementi con somma > 0 int somma_mag(int *t1, int d1, int *t2, int d2){ /* Pre: d1, d2 >=1 */ /* Post: somma_mag(t1,d1,t2,d2)= # coppie con somma >0 in t ed s */ int i,j,cnt; i=cnt=0; while((i<d1){ j=0; while (j<d2) if (t[i]+s[j]>0){ ++cnt; ++j; } ++i; } return cnt; } Intersezione vettori ordinati Scrivere una algoritmo che dati due vettori di interi ordinati (senza ripetizioni) restituisca un terzo vettore formato dall’intersezione dei due vettori. Idea. Ricorriamo i due vettori in parallelo con due indici i e j. Ad ogni passo dell’iterazione se troviamo due elementi uguali lo aggiungiamo nel nuovo vettore, altrimenti incrementiamo l’indice del vettore con l’ elemento più piccolo i 20 40 veGore traGato j 23 veGore traGato 25 Intersezione vettori ordinati:pseudo-codice Supponiamo i due vettori siano, int t[dim1] e int s[dim2] , int r[dim] Inizializzazione: i=0, j=0, k=0 Iterazione: se t[i]=s[j] allora scriviamo l’elemento in r , incrementiamo k la sua dimensione dim3 e incrementiamo sia i che j. se t[i]<s[j] allora incrementiamo i. Se t[i]>s[j], allora incrementiamo j Terminazione: i=dim-1 oppure j = dim2-1 Verifica Terminazione: Ad ogni iterazione, incrementiamo i oppure j. Quindi uno dei due indici sicuramente raggiungerà l’ultima posizione. Intersezione vettori ordinati: programma int intersezione(int *t1, int d1, int *t2, int d2, int *t3){ /* Pre: t,s ordinati e senza ripetizioni. d1, d2 >1 */ /* Post: t3 = t∩s e intersezione(t1,d1,t2,d2,t3)= |t∩s| */ int i,j,cnt; i=j=cnt=0; /* inzializzo i contatori */ while((i<d1) && (j < d2)){ if (t1[i]==t2[j]){ *(t3+cnt)=t1[i]; ++cnt; ++i; ++j; } else if (t1[i]<t2[j]) i++; else j++; } return cnt; } Fusione ordinata di array ordinati Input: Due array ordinati Output: un array ordinato che contiene l’unione degli elementi dei due array traGato i 25 t j s 20 traGato Idea. ricorriamo i due vettori contemporaneamente con due indice i e j e copiamo nel terzo vettore l’elemento minore dei due vettori e avanziamo solo in questo vettore. Quando raggiungiamo la fine di uno dei due vettori, copiamo il rimanente dell’altro nel terzo vettore Fusione ordinata di array ordinati: Pseudo-codice Ciclo principale Inizializzazione: i=0, j=0, k=0 Iterazione: se t[i]<s[j], allora u[k]=t[i] e i++e k++, altrimenti u[k]=s[j] e j++ e k++. Terminazione: Terminiamo non appena abbiamo terminato di consultare uno dei due vettori. Cioè i=dim1, oppure j= dim2 Verifica Terminazione: Ad ogni iterazione incrementiamo i o j, quindi prima o poi raggiungiamo uno dei casi della terminazione. Algoritmo Fusione ordinata di array ordinati: programma void merge(int *t, int dim1, int *s, int i,j,k; i=0,j=0,k=0; while (i<dim1 && j<dim2) if (t[i]<s[j]) u[k++]=t[i++]; else u[k++]=s[j++]; while(i<dim1)u[k++]=t[i++]; while(j<dim2)u[k++]=s[j++]; } int dim2, int *u){ merge sort ordina con merge sort ordina con merge sort ordinato ordinato fusione ordinata ordinato merge sort Algoritmo void merge_sort(int *t,int inf, int sup){ int i,j,k; k=(inf+sup)/2; mergesort(t,inf,k); mergesort(t,k+1,sup); merge(t,inf,k,t,k+1,sup,) }