Valutare la complessità in tempo Complessità in tempo: cosa serve? ✦ Per stimare il tempo impiegato da un programma ✦ Informatica II Per stimare il più grande input gestibile in ‘tempi ragionevoli’ ✦ Per confrontare l'efficienza di algoritmi diversi ✦ Capitolo 2 Analisi di algoritmi Per ottimizzare le parti più importanti ✦ “Complessità”: “Dimensione” → “Tempo” ✦ Dobbiamo definire “dimensione” e “tempo”! ✦ Adattamento delle slide originali di A.Montresor. Disponibili secondo Creative Commons Attribution-NonCommercial-ShareAlike License. Dimensione dell'input Criterio di costo logaritmico: ✦ Definizione di tempo Prima idea: Tempo = “Wall-clock” time ✦ La taglia dell'input è il numero di bit necessari per rappresentarlo Il tempo effettivamente impiegato per eseguire un algoritmo ✦ Esempio: moltiplicazione di numeri binari lunghi n bit ✦ ✦ Dipende da troppi parametri: ✦ bravura del programmatore Criterio di costo uniforme: ✦ ✦ La taglia dell'input è il numero di elementi che lo costituiscono ✦ linguaggio di programmazione utilizzato Esempio: ricerca minimo in un array di n elementi ✦ ✦ codice generato dal compilatore ✦ velocità di processore e memoria (cache, primaria, secondaria) ✦ In molti casi: ✦ Spesso possiamo assumere che gli “elementi” siano rappresentati da un numero costante di bit (a volte questa assunzione non è legittima). In tal caso le due misure coincidono a meno di una costante moltiplicativa sistema operativo, processi attualmente in esecuzione ✦ ✦ Dobbiamo considerare un modello astratto ✦ Definizione di tempo Idea migliore: Tempo = “# operazioni elementari” ✦ Modello RAM Random Access Machine (RAM) ✦ Quali operazioni possono essere considerate elementari? Memoria: ✦ ✦ Quantità infinita di celle di dimensione finita ✦ Esempio: ripensate a min(A, n) ✦ Accesso in tempo costante (indipendente dalla posizione) ✦ ✦ In generale serve un modello di calcolo: cioè una rappresentazione astratta di un calcolatore ✦ Processore (singolo) Set di istruzioni elementari simile a quelli reali: ✦ Astrazione: deve semplificare dettagli, altrimenti è inutile (non troppo complesso) ✦ somme, addizioni, moltiplicazioni, operazioni logiche, etc. ✦ istruzioni di controllo (salti, salti condizionati) ✦ Realismo: deve riflettere la situazione reale (non troppo semplice) ✦ Costo delle istruzioni elementari: ✦ Uniforme, ininfluente ai fini della valutazione (alla fine dei conti potremo non distinguere tra i diversi tipi di istruzioni) ✦ “Potenza espressiva” : deve essere abbastanza espressivo da permettere di trarre conclusioni “formali” sul costo (usando ragionamenti formali, dimostrazioni,…) ✦ Tempo di calcolo di min() Ogni istruzione richiede un tempo costante per essere eseguita ✦ Le costanti possono essere diverse da istruzione a istruzione ✦ Ogni istruzione viene eseguita un certo # di volte, dipendente da n ✦ Tempo di calcolo di binarySearch() Il vettore viene suddiviso in due parti ✦ Tempo di calcolo di binarySearch() Assunzioni ✦ Per semplicità, n =2k è una potenza di 2 ✦ Tempo di calcolo di binarySearch() Soluzione della equazione di ricorrenza per sostituzione ✦ Ricordate che abbiamo assunto che n = 2k ⇒ k = log n ✦ L’elemento cercato non è presente (caso pessimo) ✦ Ad ogni suddivisione, scegliamo sempre la parte destra, di dimensione n/2 (di nuovo: il caso pessimo) ✦ Due casi ✦ Equazione di ricorrenza ✦ Analisi di algoritmi Ci sono varie analisi possibili: Analisi del caso pessimo ✦La più importante ✦Il tempo di esecuzione nel caso peggiore è un limite superiore al tempo di esecuzione per qualsiasi input ✦Per alcuni algoritmi, il caso peggiore si verifica molto spesso ✦Es.: ricerca di un dato non presenti in un vettore ✦Analisi del caso medio ✦Difficile in alcuni casi: cosa si intende per “medio”? ✦Distribuzione uniforme ✦Il caso medio è spesso tanto difficile quanto quello peggiore (ad esempio per l’algoritmo insertionSort(), che vedremo) ✦Analisi del caso ottimo ✦Può avere senso se i possibili input presentano una distribuzione particolare ✦ Limiti asintotici superiori e inferiori Limitazioni inferiori e algoritmi ottimi Proprietà degli ordini di grandezza Per ogni f(n), g(n), h(n), q(n) e ogni c, valgono le seguenti proprietà Dato un problema ✦ Se trovate un algoritmo A con complessità O(g(n)), avete stabilito un limite superiore alla complessità del problema - g(n) ✦ Se dimostrate che qualunque algoritmo per il problema deve avere complessità Ω(f(n)), avete stabilito un limite inferiore alla complessità del problema - f(n) ✦ Se f(n)=g(n), allora A è un algoritmo ottimo Riflessività: ✦ c*f(n) è O(f(n)) , Ω(f(n)) e Θ(f(n)) Transitività: se g(n) è O(f(n)) e f(n) è O(h(n)) allora g(n) è O(h(n)) (idem per Ω e Θ) ✦ Simmetria: g(n) è Θ(f(n)) se e solo se f(n) è Θ(g(n)) ✦ ✦ Simmetria trasposta: g(n) è O(f(n)) se e solo se f(n) è Ω(g(n)) ✦ Somma: f(n)+g(n) è O(max{f(n),g(n)}) ✦ (idem per Ω e Θ) Prodotto: se g(n) è O(f(n)) e h(n) è O(q(n)) allora g(n)*h(n) è O(f(n)*q(n)) (idem per Ω e Θ) ✦ Proprietà degli ordini di grandezza Quindi Θ descrive una relazione di equivalenza. Percui ogni insieme Θ(f(n)) è una classe di equivalenza detta classe di complessità ✦ Θ(1) è ordine di grandezza costante ✦ Θ(log n) è ordine logaritmico ✦ Θ(n) è ordine lineare mentre ✦ Θ(n2) è quadratico, Θ(n3) è cubico, in generale Θ(nk) è ordine polinomiale ✦ Θ(kn) è ordine esponenziale Θ(n log n) è ordine pseudo-lineare Un algoritmo è reputato efficiente se ha complessità polinomiale; è invece considerato intrattabile se ha complessità super-polinomiale ✦ Un problema computazionale è considerato difficile se non esiste alcun algoritmo polinomiale che lo risolva ✦ Confronto fra ordini di grandezza Un caso classico: l’ordinamento di una sequenza di numeri Selection sort Problema dell'ordinamento (sorting) ✦ Input: una sequenza A di n numeri <a1, a2, ..., an> ✦ Output: una permutazione B=<b1, b2, ..., bn> di A tale per cui b1 ≤ b2 ≤ ... ≤ bn ✦ Algoritmo “naif” ✦ Generare tutte le permutazioni (n!) della sequenza e per ognuna di esse verificare in tempo O(n) se sia ordinata ✦ Costo totale: O(n n!) ✦ Complessità (caso medio, pessimo, ottimo) ✦ Vediamo degli algoritmi migliori…. Insertion Sort Algoritmo efficiente per ordinare piccoli insieme di elementi ✦ Insertion Sort - Analisi Per questo algoritmo: ✦ il costo di esecuzione non dipende solo dalla dimensione... ✦ Come ordinare una sequenza di carte da gioco “a mano” ✦ ma anche dalla distribuzione dei dati in ingresso ✦ Domande ✦ Qual è il costo nel caso l'array sia già ordinato? ✦ Qual è il costo nel caso l'array sia ordinato in ordine inverso? ✦ Cosa succede “in media”? ✦ Merge Sort Insertion Sort ✦ Merge Sort Il nucleo di Merge Sort è nel passo combina (merge), definiamola così: ✦ E' basato su un approccio incrementale (A[1...j-1] ordinato, aggiungi A[j]) ✦ merge(A, primo, ultimo, mezzo) Merge Sort ✦ E' basato sulla tecnica divide-et-impera dove A è un array di lunghezza n, mentre ✦ primo, ultimo, mezzo sono tre indici tali che 1 ≤ primo ≤ mezzo < ultimo ≤ n ✦ Divide: dividi l'array di n elementi in due sottovettori di n/2 elementi La procedura merge() assume che i sottovettori A[primo...mezzo] e A[mezzo+1...ultimo] siano già ordinati ✦ ✦ Impera: chiama MergeSort ricorsivamente su i due sottovettori ✦ I due vettori vengono fusi in un unico sottovettore ordinato A[primo...ultimo] ✦ Combina: fondi le due sequenze ordinate (merge) ✦ Domanda: ✦ Qual è l'idea? ✦ Fondere i due sottovettori “sfruttando” il fatto che sono ordinati. ✦ Seguendo queste indicazioni, sapreste scrivere un algoritmo ricorsivo che esegua il merge sort? Merge Sort Merge Sort: il passo di merge Come funziona merge(): A B ✦ 1 5 7 + 2 4 6 5 7 + 2 4 6 1 5 7 + 4 6 1 2 5 7 + 6 1 2 4 7 + 6 1 2 4 5 7 + + 1 2 4 5 6 7 1 2 4 5 6 1 2 4 + 5 6 7 Domanda ✦ Quale è il costo computazionale di merge() ? ✦ Merge Sort: l’algoritmo completo Merge Sort: un esempio Il programma completo: ✦ Chiama ricorsivamente se stesso e usa merge() per unire i risultati ✦ Caso base: sequenze di lunghezza ≤ 1 sono già ordinate ✦ Analisi di Merge-Sort Una assunzione semplificativa ✦ Alcune tecniche per stabilire limitazioni inferiori ✦ n=2k, ovvero l'altezza dell'albero di sottodivisioni è esattamente k ✦ ✦ tutti i sottovettori hanno dimensioni che sono potenze esatte di 2 ✦ Costi di Merge Sort Dimensione dei dati ✦Se un problema ha in ingresso n dati e richiede di esaminarli tutti, allora una limitazione inferiore della complessità è Ω(n) ✦ ✦ Eventi contabili ✦Se un problema richiede che un certo evento sia ripetuto almeno n volte, allora una limitazione inferiore della complessità è Ω(n) ✦ Risoluzione della ricorrenza ✦ Esercizio ✦ Ricavare questo risultato svolgendo l’equazione di ricorrenza ✦ Esempio: sommare n numeri Esempio: ricerca del minimo richiede almeno n-1 confronti Oracolo ✦Se un oracolo, utilizzando una certa regola ignota all’algoritmo (anche se valida solo per certi input), “divina” ad ogni opportunità la situazione più sfavorevole all’algoritmo, allora combattendo contro di esso si può individuare una limitazione inferiore Limitazioni inferiori ATTENZIONE! ✦ Limitazioni inferiori ATTENZIONE! ✦ Le tecniche illustrate sono semplici, ma: Le tecniche illustrate sono semplici, ma: Si deve fare attenzioni alle assunzioni di base. Per esempio, si ha che: Si deve fare attenzioni alle assunzioni di base. Per esempio, si ha che: ✦ ✦ Ricerca in vettore ordinato: è O(log n), non O(n) ✦ Ricerca in vettore ordinato: è O(log n), non O(n) Ricerca del minimo in vettore ordinato: è O(1), non O(n) ✦ ✦ Ricerca del minimo in vettore ordinato: è O(1), non O(n) ✦ Ecco un esempio più complesso: l’ordinamento di una sequenza ✦ Ecco un esempio più complesso: l’ordinamento di una sequenza ✦ Limitazione inferiore: è Ω(n) - perché? ✦ Limitazione inferiore: è Ω(n) - perché? Limitazione superiore: è O(n log n) ✦ Possiamo “restringere” questo scarto? ✦ Si dimostra che... ✦ ✦ Limitazione superiore: è O(n log n) ✦ Possiamo “restringere” questo scarto? ✦ Si dimostra che... ✦ … Merge Sort è ottimo, in quanto è possibile dimostrare che Ω(n log n) è un limite inferiore all’ordinamento per gli algoritmi di ordinamento basati su confronti. Un approccio diverso: Counting Sort Come funziona: ✦ Counting Sort Complessità ✦ I numeri (interi) da ordinare sono compresi in un intervallo [1..k] O(n+k) ✦ ✦ Costruire un array B[1..k] che conta il numero di volte che compare un valore in [1..k] ✦ Se k è O(n), allora la complessità è O(n) ✦ Ricollocare i valori così ottenuti in A ✦ Discussione su limite inferiore ✦ Counting Sort non è basato su confronti ✦ Abbiamo cambiato le condizioni di base ✦ Se k è O(n3), questo algoritmo è peggiore di tutti quelli visti finora ✦ Esercizi Esercizio 1 ✦ Dato un array A[1..n] di interi e un intero v, descrivere un algoritmo che determini se esistono due elementi in A la cui somma sia esattamente v Esercizio 2 ✦ Dato un array A[1..n] di interi positivi, descrivere un algoritmo O(n) che determini se esistono due elementi in A la cui somma sia esattamente 17 Esercizio 3 ✦ Siano date n monete d'oro, tutte dello stesso peso tranne una contraffatta perché pesa meno, ed una bilancia con due piatti. Descrivere un algoritmo per individuare la moneta contraffatta in al più log n operazioni di pesatura.