Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Che cosa e' un algoritmo e come vogliamo studiare gli algoritmi? Vedi: • D.E. Knuth, The art of computer programming, Vol. 1, Fundamental Algorithms, Addison-Wesley, cap. 1, Basic concepts • E. Horowitz, S. Sahni, Computer algorithms, Computer Science Press, cap. 1, Introduction Copyright © 2006-2009 by Claudio Salati. Lez. 1 1 ALGORITMO (o algorismo) • Il nuovissimo Melzi, 1950 • Aritmetica col sistema arabo (dal matematico arabo Al-Kuarismi, del IX secolo) • Processo del calcolo • Grande Dizionario Enciclopedico UTET, 1954 • Qualunque procedimento di calcolo • Questa strana parola nasce dal nome del matematico arabo Al Khovarizmi … passo' ad indicare le regole di calcolo • Cosi' si dice anche oggi Algoritmo di Euclide per indicare il procedimento di calcolo che serve per la ricerca del massimo comun divisore • The Concise Oxford Dictionary, 1976 • Arabic (decimal) notation of numbers • Process or rules for (esp. machine) calculation 2 ALGORITMO: aritmetica col sistema arabo • Sistema arabo-indiano: basato sulla notazione posizionale e su cifre relative ad una base, e.g. in base 10: nm nm-1 ... n1 n0 = nm*10m+ nm-1*10m-1+ ... +n1*101+ n0*100 • Le operazioni su numeri qualsiasi si basano sulla capacita’ di operare sulle singole cifre • Algoritmo di addizione typedef digit[MAXDIGIT] numero; // numero rappresentato come sequenza di MAXDIGIT digit, // il digit 10k in posizione k nella sequenza void addizione(numero op1, numero op2, numero somma) { unsigned riporto = 0, pot, parziale; for (pot = 0; pot < MAXDIGIT; pot += 1) { parziale = op1[pot] + op2[pot] + riporto; riporto = parziale / 10; parziale = parziale % 10; digit[pot] = parziale; } assert(riporto==0); // altrimenti overflow } 3 Algoritmo Microsoft® Encarta® Enciclopedia. © 1993-2002 Microsoft Corporation. Tutti i diritti riservati. • Termine che formalizza il concetto intuitivo di procedura generale, di metodo sistematico valido per risolvere una certa classe di problemi. • In matematica, è la soluzione di un problema basata sull'uso ripetuto di un semplice metodo computazionale (ne è un esempio comune il procedimento dell'operazione di divisione in aritmetica - in realta' il procedimento di tutte le 4 operazioni nell’aritmetica basata sui numeri arabi, quella in notazione posizionale). • In informatica, indica, in generale, una sequenza di passi successivi, ciascuno dei quali specifica un'azione (o istruzione) eseguibile in modo meccanico. La sequenza completa permette di risolvere un problema, una volta che siano assegnate le condizioni iniziali. • In informatica, la nozione di algoritmo contiene due concetti impliciti, fondamentali ai fini dell'esecuzione dell'algoritmo stesso: • quello di automa, l'esecutore meccanico dell'algoritmo, e • quello di linguaggio in cui l'algoritmo è scritto, che l'automa 4 riconosce. Algoritmo Microsoft® Encarta® Enciclopedia. © 1993-2002 Microsoft Corporation. Tutti i diritti riservati. • L'algoritmo deve rispettare alcune proprietà fondamentali: • effettività, vale a dire deve essere effettivamente eseguibile da un automa (o, in linguaggio dei computer, deve essere programmabile); • finitezza di espressione (statica), ovvero il numero di istruzioni da eseguire deve essere finito; • finitezza della procedura (dinamica), ovvero deve essere possibile concludere il calcolo, per qualsiasi situazione dei dati iniziali; • determinismo, ovvero a ogni passo della procedura deve essere definita una e una sola operazione da eseguire. • L'algoritmo può essere scritto con espressioni logiche o termini matematici, o in forma lessicale. 5 ALGORITMO (in informatica!) • Definizione 1 UN METODO PRECISO UTILIZZABILE DA UN CALCOLATORE PER LA SOLUZIONE DI UN PROBLEMA • Definizione 2 DESCRIZIONE DI UNA SEQUENZA ORDINATA E FINITA (staticamente e dinamicamente) DI AZIONI BEN DEFINITE ED EFFICACI CHE A PARTIRE DA UN INSIEME I DI DATI DI INGRESSO CHE SODDISFANO ALL'INSIEME DI CONDIZIONI P PRODUCE UN INSIEME O DI VALORI DI USCITA CHE GODONO DELL'INSIEME DI PROPRIETA' C 6 ALGORITMO: PAROLE CHIAVE 1 • TEMPO FINITO: IL VINCOLO REALE E' MOLTO PIU' STRINGENTE: ABBASTANZA PICCOLO! EFFICIENZA DEGLI ALGORITMI • AZIONI BEN DEFINITE: e.g.: COSA SIGNIFICA -8 DIV -5? CI SONO ALMENO 2 DEFINIZIONI DIVERSE: -8 : -5 = +2 +2 oppure -8 : -5 = +1 -3 • AZIONI EFFICACI: consideriamo una azione efficace quando e' eseguibile in tempo finito da un calcolatore. quando ci riferiamo ad un calcolatore ci riferiamo a qualcosa di simile a quello che teniamo sulla scrivania e usiamo solitamente NON SI PUO' MOLTIPLICARE O DIVIDERE O ... PER , ma solo per una sua approssimazione finita 7 ALGORITMO: PAROLE CHIAVE 2 • INPUT: I, P(I) • OUTPUT: O, C(O) } problema ben definito • SEQUENZA ORDINATA: STIAMO ASSUMENDO UN PARTICOLARE MODELLO COMPUTAZIONALE, IL MODELLO DI VON NEUMANN • DESCRIZIONE: e' un testo che descrive staticamente (tramite istruzioni) una successione di azioni da eseguire dinamicamente • La distinzione statico vs. dinamico e' fondamentale. Noi ragioneremo sul testo statico e non sul/i processo/i dinamico/i che lo interpreta/no. • Un processo dinamico e' condizionato dagli specifici dati in ingresso, il testo statico no (se non per le proprieta' generali P(I)). 8 PROBLEMA Template: TROVARE UN ALGORITMO CHE A PARTIRE DA UN INSIEME I DI DATI DI INGRESSO CHE SODDISFANO ALL'INSIEME DI CONDIZIONI P PRODUCA UN INSIEME O DI RISULTATI DI USCITA CHE GODONO DELL'INSIEME DI PROPRIETA' C Ogni problema che affrontiamo deve, in linea di principio, poter essere espresso secondo la formula indicata dal template MINIMA VULGARIA: SE NON C'E' UN PROBLEMA (FORMULABILE CHIARAMENTE) NON PUO' ESISTERE L'ALGORITMO CHE LO RISOLVE. 9 Citazioni Ludwig Wittgenstein, Tractatus logico-philosophicus, Einaudi 6.2.4 Il metodo della matematica per pervenire alle sue equazioni e’ il metodo di sostituzione. Infatti le equazioni esprimono la sostituibilita’ di due espressioni, e noi procediamo da un certo numero di equazioni a nuove equazioni sostituendo espressioni con altre, corrispondentemente alle equazioni. 6.5 Di una risposta che non si puo’ formulare non puo’ formularsi neppure la domanda. L’enigma non v’e’. Se una domanda puo’ porsi, puo’ pure aver risposta. (warning: Goedel: "purtroppo Wittgenstein si e' sbagliato") ? Cio’ che si puo’ dire, lo si puo’ dire chiaramente (citazione approssimata). 7 Su cio’ di cui non si puo’ parlare, si deve tacere. 10 Modello computazionale di Von Neumann - 1 E’ il modello computazionale dei processori hardware tradizionali: stato del programma o dati vs. azioni o istruzioni, ciascuna delle quali istruzioni modifica lo stato del programma e/o determina quale e' la prossima istruzione del programma da eseguire lo stato del programma e' rappresentato dal contenuto delle celle di memoria (memoria centrale del calcolatore = array di celle ad accesso diretto o random) e dei registri del processore dall'indirizzo della prossima istruzione da eseguire di norma la prossima istruzione da eseguire e' quella che segue quella correntemente in esecuzione nel testo del programma (sequenza dinamica = sequenza statica), salvo che l'istruzione in esecuzione non trasferisca esplicitamente il controllo ad un'istruzione diversa 11 Modello di Von Neumann o imperativo - 2 anche il programma e' memorizzato nella memoria del calcolatore (ma di norma non e' modificabile durante l'esecuzione) nei linguaggi di programmazione imperativi si adotta sostanzialmente lo stesso modello: stato del programma variabili istruzione fondamentale assegnamento istruzioni di controllo di flusso 12 ALGORITMO DI EUCLIDE // // // // // // 1a PER IL CALCOLO DEL MCD DI DUE NUMERI NATURALI > 0 I = {i1, i2} P = {ikN, ik>0 : k{1, 2}} O = {gcd}, C = {gcdN, gcd=MCD(i1, i2)} unsigned euclide_1 (unsigned i1, unsigned i2) { unsigned r, gcd; unsigned k1 = i1; unsigned k2 = i2; 01 02 03 04 05 13 ALGORITMO DI EUCLIDE 1b assert((i1>0) && (i2>0)); assert((k1>0) && (k2>0)); if (k2>k1) { r = k1; k1 = k2; k2 = r; } assert((k1>=k2) && (k2>0)); r = k1 % k2; while (r!=0) { k1 = k2; k2 = r; r = k1 % k2; } gcd = k2; return(gcd); } 06 07 08 09 10 11 12 13 14 15 14 ALGORITMO DI EUCLIDE Stiamo considerando una successione di triple: k1<1> k2<1> r<1> k1<2> k2<2> r<2> k1<3> k2<3> r<3> . . . k1<m> 1c k2<m> r<m> in cui (definizione ricorsiva): k1<1> = i1, k1<j+1> = k2<j> k2<1> = i2, k2<j+1> = r<j> r<j> = k1<j> % k2<j> • Quando: r<j> = 0 noi affermiamo che: MCD(i1, i2) = k2<j> • N.B.: se nm0 e n%m=0 e' evidente che MCD(n, m) = m • m e' un divisore di n • m e' un divisore, il massimo divisore, di se stesso • Quello che stiamo asserendo e' che, per qualunque valore di j, MCD(i1,i2) = MCD(k1<1>,k2<1>) = MCD(k1<j>,k2<j>) 15 Parentesi N.B.: Durante tutto il corso si usera' l'espressione numero naturale per indicare un numero intero non negativo Abbiamo scritto assert((i1>0) && (i2>0)); anziche' assert(i1>0 && i2>0); Perche'? Le due espressioni hanno lo stesso significato? Quale e' il significato di una istruzione/espressione? Chi ci assicura che euclide_1() fa davvero quello che promette di fare? In particolare: chi ci assicura che il ciclo while termina? 16 ALGORITMO DI EUCLIDE 2a // PER IL CALCOLO DEL MCD DI DUE NUMERI // NATURALI 0 // I = {i1, i2} // P = {ikN, ik0 : k{1, 2}} // O = {return}, // C = {return N, // return = (i1!=0 && i2!=0) ? // // MCD(i1, i2) : MAX(i1, i2)} unsigned euclide_2 (unsigned i1, unsigned i2) { assert((i1>=0) && (i2>=0)); 01 02 17 ALGORITMO DI EUCLIDE 2b unsigned new_i1; if (i2 > i1) { new_i1 = i2; i2 = i1; i1 = new_i1; } assert((i1 >= i2) && (i2 >= 0)); while (i2!=0) { new_i1 = i2; i2 = i1 % i2; i1 = new_i1; } return(i1); } 03 04 05 06 07 08 09 10 11 12 13 18 ALGORITMO DI EUCLIDE 2c • In realta' il primo elemento di ciascuna delle triple considerate nell'algoritmo euclide_1() non viene mai utilizzato (dopo che si e’ effettuato il calcolo del terzo elemento della tripla) • ne' per verificare se l'algoritmo e' terminato (viene utilizzato il terzo elemento) • ne' per determinare gli elementi della prossima tripla (vengono utilizzati il secondo ed il terzo elemento) (il primo elemento di una tripla e’ utilizzato solo per il calcolo del terzo elemento della tripla stessa) • Possiamo quindi "eliminare" il primo elemento di ciascuna tripla, limitandoci a considerare la coppia formata dagli ultimi due elementi. • E' quello che fa l'algoritmo euclide_2() 19 Note e confronti assert() e' importata dallo header file standard assert.h se l'espressione tra parentesi e' vera non succede niente, ma se e' falsa l'esecuzione del programma viene interrotta notare che in euclide_2() l'asserzione assert((i1>=0) && (i2>=0)); e' banalmente vera: i1 e i2 sono di tipo unsigned! L'utilizzo del type-system del linguaggio di programmazione ci consente di esprimere le caratteristiche del problema Le due funzioni euclide_1() ed euclide_2() sono molto diverse tra loro: P_1(I) P_2(I) C_1(O) C_2(O) 20 ALGORITMO DI EUCLIDE // // // // // // 3a PER IL CALCOLO DEL MCD DI DUE NUMERI NATURALI > 0 I = {i1, i2} P = {ikN, ik>0 : k{1, 2}} O = {gcd}, C = {gcdN, gcd=MCD(i1, i2)} unsigned euclide_3 (unsigned i1, unsigned i2) { unsigned newk1; unsigned gcd; unsigned k1 = i1; unsigned k2 = i2; 01 02 03 04 05 06 21 ALGORITMO DI EUCLIDE 3b assert((i1>0) && (i2>0)); assert((k1>0) && (k2>0)); // se k2>k1 la prima iterazione effettua // lo scambio: questa azione e' evidente? while (k2!=0) { newk1 = k2; k2 = k1 % k2; k1 = newk1; } gcd = k1; return(gcd); } 07 08 09 10 11 12 13 14 22 ALGORITMO DI EUCLIDE 3c Infatti se k2 > k1 e': • k1 / k2 = 0 • k1 % k2 = k1 per cui durante la prima iterazione • newk1 assume il valore di k2<0> • k2<1> assume il valore di k1<0> • k1<1> assume il valore di newk1, cioe' di k2<0> 23 ALGORITMO DI EUCLIDE: versione ricorsiva // // // // // // // // PER IL CALCOLO DEL MCD DI DUE NUMERI NATURALI 0 I = {i1, i2} P = {k{1, 2}: ikN, i1i20} O = {gcd}, C = {gcdN, gcd = (i1!=0 && i2!=0) ? MCD(i1, i2) : i1} unsigned mcd (unsigned i1, unsigned i2) { assert((i1>=i2) && (i2>=0)); return (i2==0 ? i1 : mcd(i2, i1 % i2)); } 01 02 03 04 24 PROGRAMMA • E' UN ALGORITMO FORMULATO IN UN PARTICOLARE LINGUAGGIO DI PROGRAMMAZIONE • IL LINGUAGGIO DEVE ESISTERE: • DEVONO ESISTERE INTERPRETI E/O COMPILATORI, • E LA SUA SEMANTICA DEVE ESSERE BEN DEFINITA e.g. differenza (anche per la precedenza degli operatori!) tra • lo statement Pascal IF (N<>0) AND ((M DIV N) > K) THEN … (semantica di valutazione dell'operatore AND per valore e • quindi statement errato) lo statement C if (N!=0 && M/N>K) … ; (semantica di valutazione dell'operatore && per nome, o shortcircuit, e quindi statement corretto) 25 CORRETTEZZA DELL'ALGORITMO DI EUCLIDE • • • • • • Assumiamo (w.l.g.) i1 i2 > 0 allora si puo' scrivere (qN, rN ): i1 = q * i2 + r UN QUALSIASI NUMERO j CHE DIVIDE SIA i1 CHE i2 DEVE DIVIDERE ANCHE r, ED E' VERO ANCHE CHE QUALSIASI DIVISORE COMUNE DI i2 E r DEVE DIVIDERE ANCHE i1 CIO' E' VERO IN PARTICOLARE PER IL MCD DI i1 E i2 PERCIO' (indicando con r il resto della divisione di i1 per i2) SE r=0 ALLORA MCD(i1, i2) = i2 ( UN NUMERO NON PUO' ESSERE DIVISO DA NUMERI PIU' GRANDI DI LUI ) SE r0 ALLORA LA RICERCA DI MCD(i1, i2) E' RICONDOTTA ALLA RICERCA DI MCD(i2, i1 % i2) (ESSENDO MCD(i1, i2) = MCD(i2, i1 % i2)) 26 CORRETTEZZA DELL'ALGORITMO DI EUCLIDE • In realta' quello che abbiamo dimostrato e' la validita' della logica dell'algoritmo Non abbiamo nemmeno fatto riferimento ad una versione precisa dell'algoritmo/programma, tra le 4 che abbiamo considerato! In effetti non ci siamo riferiti a nessuna descrizione precisa dell'algoritmo N.B.: scrivere un programma e' una maniera di descrivere in modo preciso un algoritmo. Ci consente di parlarne in modo preciso (perche’, per definizione, il significato di ogni statement del programma deve essere chiaro e univoco). • Quello che vorremmo e': • potere verificare la validita' logica degli algoritmi che utilizziamo, • potere verificare la correttezza dei programmi che scriviamo. 27 TERMINAZIONE DELL'ALGORITMO DI EUCLIDE • Assumiamo i1 i2 > 0 • allora si puo' scrivere: • dove q = i1 / i2 e i2 > r 0 • PERCIO': • SE r=0 L'ALGORITMO TERMINA IMMEDIATAMENTE i1 = q * i2 + r e questo e' in particolare il caso quando i1=i2 • SE r0 (e quindi i1>i2) ALLORA i1 E i2 DECRESCONO STRETTAMENTE DA UNA ITERAZIONE ALL'ALTRA • ED ESSENDO 1 MCD(i1, i2) • LA PROCEDURA DEVE TERMINARE IN AL MASSIMO i2 ITERAZIONI 28 COMPLESSITA' DELL'ALGORITMO DI EUCLIDE • SI E' DETTO CHE L'ALGORITMO TERMINA AL MASSIMO IN i2 PASSI: SI RIESCE A DARE UN LIMITE PIU' STRINGENTE? • CONSIDERIAMO: i1 > i2 > 0, i1 = q * i2 + r • dove q = i1 / i2 e i2 > r 0 • SE i2 (i1 / 2) ALLORA IN UNA ITERAZIONE i1 SI DIMEZZA (ALMENO) • SE i2 (i1 / 2) ALLORA E' r < (i1 / 2) ED IN AL PIU' 2 ITERAZIONI i1 SI DIMEZZA • PERCIO' L'ALGORITMO E' DI COMPLESSITA' LOGARITMICA: L'ITERAZIONE NON VIENE ESEGUITA PIU' DI 2*log2i1 VOLTE • NON ABBIAMO CERCATO UN RISULTATO ESATTO, MA UN ORDINE DI COMPLESSITA'. • QUESTO DIPENDE DALL'ALGORITMO, • LE COSTANTI MOLTIPLICATIVE, ADDITIVE, ... ANCHE DAL PROGRAMMA 29 CONFRONTO TRA LE DIVERSE VERSIONI DELL'ALGORITMO DI EUCLIDE • DOMINIO DI DEFINIZIONE DELL'ALGORITMO, e.g.: – NUMERI INTERI NON NEGATIVI vs. NUMERI INTERI POSITIVI – VINCOLO (pre-condizione) i1 i2 • EFFICIENZA/TEMPO DI ESECUZIONE Nell'esempio i quattro programmi condividono l'algoritmo, per cui l'efficienza e’ dello stesso ordine. – Pero’, ci sono differenze? – Come si confronta la versione ricorsiva? • LUNGHEZZA DEL TESTO DEL PROGRAMMA • CHIAREZZA – Utilizzo del linguaggio per esprimere caratteristiche del problema – Scambio implicito di i1 e i2 quando i2 > i1 in euclide_3() • ELEGANZA (e.g. trattamento dei casi particolari) • DOCUMENTAZIONE, ORGANIZZAZIONE 30 STUDIO DEGLI ALGORITMI • • • • 1 COME INVENTARE ALGORITMI: STRATEGIE DI PROGETTO – DIVIDE-AND-CONQUER, – GREEDY METHOD, – ALGORITMI PROBABILISTICI, – ... COME DIMOSTRARE CHE NON ESISTONO ALGORITMI EFFICIENTI O CHE UN PROBLEMA E' TROPPO DIFFICILE { CHI LO RISOLVESSE VINCEREBBE IL PREMIO NOBEL PER L'INFORMATICA, ISTITUITO APPOSITAMENTE } E, PIU' IN GENERALE, COME VALUTARE LA COMPLESSITA' DI UN PROBLEMA COME ESPRIMERE ALGORITMI, cioe` COME SCRIVERE PROGRAMMI – PROGRAMMAZIONE STRUTTURATA – PROGETTAZIONE TOP-DOWN – PROGETTAZIONE PER RAFFINAMENTI SUCCESSIVI 31 STUDIO DEGLI ALGORITMI • • • 2 COME VERIFICARE – LA CORRETTEZZA – LA TERMINAZIONE DI UN ALGORITMO O PROGRAMMA COME ANALIZZARE LA EFFICIENZA/COMPLESSITA' – NELLO SPAZIO (e.g. STRUTTURE DATI IN MEMORIA) – NEL TEMPO (DI ESECUZIONE) DI UN PROGRAMMA COME OPERARE – DEBUGGING – TESTING – PROFILING (caratterizzazione) DI UN PROGRAMMA. (N.B. NON SEMPRE L'ALGORITMO TEORICAMENTE OTTIMALE E' IL MIGLIORE DA USARE PRATICAMENTE, e.g. Quicksort, algoritmo di Dantzig per la Programmazione Lineare, sort di piccoli array) 32 PIDGIN C • • • • • NEL CORSO DESCRIVIAMO ALGORITMI (ma per farlo di solito scriveremo dei programmi in C!) L'IMPORTANTE E' CHE LE AZIONI DESCRITTE SIANO – BEN DEFINITE – EFFICACI PER BEN DEFINIRE UNA OPERAZIONE SI POSSONO UTILIZZARE – LINGUAGGI DI PROGRAMMAZIONE, e.g. C – ALTRI LINGUAGGI (, , |, , , #, ...) (l'operatore # applicato a un insieme, lista, … ne ritorna la cardinalita') – AL LIMITE L'ITALIANO PIDGIN C CIO' SI APPLICA ANCHE A STRUTTURE DATI (e.g. INSIEMI) OCCORRE PERO' SEMPRE – VERIFICARE CHE LE AZIONI DESCRITTE SIANO EFFICACI – E INDICARNE L'ORDINE DI COMPLESSITA' 33