Problemi computazionali e algoritmi
In questa pagina 6
Problema computazionale
Definizione (problema computazionale). Un problema computazionaleDice quali risposte sono corrette per ogni possibile input, senza dire come trovarle. Esempio: per la somma, all'input (3, 4) corrisponde la risposta 7. Il modo di calcolare la risposta è l'algoritmo. è un insieme di coppie con (istanza, dominio delle istanze) e (soluzione, dominio delle soluzioni). Si richiede che ogni istanza abbia almeno una soluzione, cioè che per ogni esista almeno un con . Le coppie stanno in , quindi . Il simbolo è il prodotto cartesianoInsieme di tutte le coppie ordinate (i, s) con i in I e s in S. dei due insiemi (vedi Operazioni tra insiemiUnione, intersezione, differenza, complementare e prodotto cartesiano, con le leggi di De Morgan e il legame con la logica.Operazioni tra insiemi →): ne è un sottoinsieme, cioè una relazione tra istanze e soluzioni. Se ogni istanza avesse una sola soluzione, sarebbe il grafico di una funzioneUna funzione associa a ogni elemento del dominio un solo valore: dominio naturale, grafico, immagine, antimmagine e metodo grafico.Funzioni - definizione, dominio e grafico → da a ; qui si ammette il caso più generale, con più soluzioni possibili per la stessa istanza.
Esempio. Per la somma di interi (gli insiemi numerici sono in Insiemi numerici (N, Z, Q, R)Naturali, interi, razionali e reali, perché la radice di 2 obbliga a passare da Q a R, e gli intervalli.Insiemi numerici (N, Z, Q, R) →) l'istanza ha come soluzione , e e hanno la stessa soluzione .
| Problema | |||
|---|---|---|---|
| somma di interi | coppie | con | |
| ordinamento (v. 1) | array di interi | array ordinati | con permutazione ordinata di |
| ordinamento (v. 2) | array di interi | permutazioni | con che ordina |
Due osservazioni: una soluzione può servire a più istanze (la somma vale per e per ) e un'istanza può avere più soluzioni (nella v. 2 con chiavi ripetute, più permutazioni ordinano lo stesso array). In quel caso l'algoritmo ne calcola una.
Algoritmo e modello di calcolo
Un algoritmo (una sequenza finita di passi elementari, come i cicli di calcoloSchemi ricorrenti nei cicli: accumulatore, contatore, massimo e minimo, ricerca con uscita anticipata, verifica universale, filtro e trasformazione, finestra scorrevole, due indici, elaborazione di coppie.Pattern algoritmici iterativi → di Fondamenti di Informatica) risolve se, ricevuta in input un'istanza , produce in output una soluzione con : calcola una funzione che a ogni istanza associa una sua soluzione. I passi sono quelli elementari di un modello di calcolo.
Definizione (macchina RAM). Il modello usato è la macchina RAMRandom Access Machine: input, output, dati intermedi e programma stanno in memoria, e ogni accesso a una cella costa un passo. Non c'entra la memoria RAM del PC.: i passi elementari sono assegnamento, operazioni logiche e aritmetiche, indicizzazione di array, restituzione di un valore da un metodo; ciascuno costa (la notazione è in Notazione asintoticaDefinizioni di O, Omega, Theta e o piccolo con le costanti c ed n0; esempi con costanti esplicite; proprietà (polinomi, esponenziali, logaritmi, somme, implicazioni tra notazioni); sommatorie notevoli; terminologia (logaritmica, lineare, polinomiale, esponenziale).Notazione asintotica →: significa "un numero di operazioni limitato da una costante che non dipende dalla taglia dell'input"). Il modello rispecchia un vero elaboratore, in cui processore e memoriaModello di von Neumann: CPU, memoria centrale, bus e periferiche; ciclo fetch-decode-execute e gerarchia di memoria.Struttura di un elaboratore → leggono o scrivono una cella con una sola istruzione e la memoria è indirizzabile per posizione; i casi in cui questa semplificazione non regge (accessi lenti fuori dalla cache) sono in Gerarchia di memoria e principio di localitàCaratteristiche delle memorie (posizione, capacità, unità di trasferimento, metodo di accesso, prestazioni, volatilità); compromesso costo-capacità-velocità e gerarchia a livelli; località temporale e spaziale con esempi; hit, miss e tempo medio di accesso con calcolo svolto.Gerarchia di memoria e principio di località →.
Esempio. A[i] <- A[i] + 1 è una indicizzazione, una addizione e un assegnamento: tre passi elementari, in tutto .
Gli algoritmi si scrivono in pseudocodice: costrutti di un linguaggio ad alto livello (for, while, if, ricorsione) mescolati a linguaggio naturale, senza dettagli di tipi o sintassi. Negli esami si richiede sempre di specificare input e output dell'algoritmo prima del corpo.
Taglia di un'istanza
Definizione (taglia di un'istanza). La taglia è una funzione che associa a ogni istanza uno o più numeri che ne misurano la grandezza: la lunghezza di un array, il numero di nodi di un albero, il numero di vertici e archi di un grafo ( ed ). La complessità si esprime in funzione della taglia (vedi Complessità in tempo e caso pessimoPerché lo studio sperimentale non basta; complessità al caso pessimo come massimo sul numero di operazioni tra le istanze di una data taglia; stima con limiti superiore e inferiore senza trovare l'istanza peggiore; esempi arrayMax, prefixAverages e InsertionSort; efficienza asintotica e limiti dell'analisi.Complessità in tempo e caso pessimo →).
Esempio. La taglia di un array di interi è ; quella di un grafo è la coppia di vertici e archi.
Strutture dati e ADT
Un algoritmo organizza i dati in una struttura dati: collezione di oggetti con metodi per accedervi e modificarla. Due livelli:
- ADT (tipo di dato astratto): specifica il tipo dei dati, le operazioni ammesse e i loro parametri, cioè cosa fa ogni operazione e non come. Si descrive con un'interfaccia.
- Struttura dati concreta: l'implementazione dell'ADT (una classe in Java, un insieme di
structe funzioni in C), che fissa come agiscono le operazioni e quindi il loro costo. Vedi Liste, pile e codeRipasso degli ADT elementari: lista index-based (array) e position-based (lista doppiamente concatenata con sentinelle), pila LIFO, coda FIFO, deque, iteratori; interfacce, implementazioni e costi; array estendibile e coda circolare.Liste, pile e code →. Il concetto di tipo di dato astratto è introdotto in Tipi di dato astrattiUn ADT è definito dalle operazioni e dal loro comportamento, non dalla rappresentazione; interfaccia e implementazione; realizzazione in Python con le classi e in C con header e tipo opaco; esempio di un ADT Frazione.Tipi di dato astratti →; due implementazioni già viste in Fondamenti di Informatica sono Liste concatenateLista concatenata semplice: nodi allocati dinamicamente collegati da puntatori; inserimento e rimozione in testa, scorrimento, ricerca, inserimento ordinato, deallocazione; confronto dei costi con l'array; versione in Python.Liste concatenate → e Pila e codaPila (LIFO) e coda (FIFO) come ADT: operazioni e costi; realizzazione in Python con list e collections.deque; realizzazione in C con array e indici, coda circolare; applicazioni (parentesi bilanciate, stack delle chiamate, visite).Pila e coda →.
Esempio svolto: cercare in un array ordinato
Problema. : coppie ( array crescente di interi distinti, intero). : un indice con , oppure se non c'è.
Ricerca lineare. Scorre tutto l'array (vedi Ricerca lineare e binariaRicerca lineare su sequenze qualsiasi in O(n); ricerca binaria su sequenze ordinate in O(log n), versione iterativa e ricorsiva, invariante e errori di indice; modulo bisect.Ricerca lineare e binaria →): nel caso peggiore manca e si fanno confronti, uno per elemento, quindi .
Ricerca binaria. Si sfrutta l'ordine: si confronta con l'elemento centrale e si scarta metà dell'intervallo.
Algoritmo ricercaBinaria(A, n, x)
Input: array crescente A[0..n-1] di interi distinti, intero x
Output: indice di x in A, oppure -1
lo <- 0; hi <- n - 1
while lo <= hi do
mid <- floor((lo + hi) / 2)
if A[mid] = x then return mid
if A[mid] < x then lo <- mid + 1
else hi <- mid - 1
return -1Proprietà (ricerca binaria: invariante e complessità)
- Invariante: se è in , allora la sua posizione sta in . Vale all'inizio ( è tutto l'array) e si conserva: se , per l'ordine crescente tutti gli elementi in posizione valgono al più , quindi può stare solo a destra di e si pone ; il caso è simmetrico. Al termine del ciclo () l'intervallo è vuoto, quindi per l'invariante non c'è in e si restituisce . Il metodo dell'invariante è spiegato in Dimostrazioni, induzione e invariantiTecniche di dimostrazione (esempio, controesempio, assurdo), induzione con casi base multipli, invarianti di ciclo (inizializzazione, conservazione, uso alla fine), schema generale per provare la correttezza; esempi svolti su arrayMax, sequenza di bit e numeri di Fibonacci.Dimostrazioni, induzione e invarianti →.
- Complessità: l'intervallo dimezza a ogni iterazione, quindi ci sono al più iterazioni di costo costante: .
Perché proprio . Sia il numero di posizioni ancora candidate. Se non è in , l'elemento centrale è scartato e si tiene una delle due metà restanti, al più la più grande, che ha elementi. Dopo iterazioni restano quindi al più candidati, e il ciclo termina quando ne restano . Vale (il logaritmo è l'inverso dell'esponenziale: Esponenziale e logaritmoLa funzione esponenziale a^x (base positiva diversa da 1) e la sua inversa, il logaritmo in base a, con grafici e proprietà.Esponenziale e logaritmo →). Il più piccolo intero con è . Controllo: per è , per è , per è (confermato simulando la ricerca su ogni possibile ).
Esempio. Su con la ricerca binaria restituisce l'indice dopo iterazioni; con ne servono al più .
Traccia su , :
| iterazione | confronto con | effetto | ||||
|---|---|---|---|---|---|---|
| 1 | 0 | 6 | 12 | |||
| 2 | 4 | 6 | 23 | uguale | restituisce |
Il grafico confronta il numero massimo di passi della ricerca lineare, , con il massimo della binaria: già a sono contro .
Grafico interattivo: Passi al caso pessimo: ricerca lineare, n, contro ricerca binaria, floor(log2 n)+1 iterazioni; a n = 100 sono 100 contro 7
Errori comuni
- Confondere problema e algoritmo: il problema dice cosa, l'algoritmo come; per lo stesso problema esistono più algoritmi.
- Dimenticare di dichiarare input e output nello pseudocodice, o scegliere una taglia ambigua (per un grafo servono e ).
- Scrivere senza la parte intera: gli indici devono essere interi.
Versione ripasso
- Problema : coppie (istanza, soluzione), con almeno una soluzione per ogni istanza (vedi Operazioni tra insiemiUnione, intersezione, differenza, complementare e prodotto cartesiano, con le leggi di De Morgan e il legame con la logica.Operazioni tra insiemi →, Funzioni - definizione, dominio e graficoUna funzione associa a ogni elemento del dominio un solo valore: dominio naturale, grafico, immagine, antimmagine e metodo grafico.Funzioni - definizione, dominio e grafico →). Esempio: nella somma ha soluzione , mentre e hanno soluzione . Se un'istanza ha più soluzioni, l'algoritmo ne calcola una.
- Algoritmo risolve se, per ogni istanza , produce con . Modello di calcolo: la RAMRandom Access Machine: dati e programma in memoria, ogni passo elementare costa Θ(1)., dove assegnamento, operazioni aritmetico-logiche, indicizzazione di array e
returncostano ciascuno (vedi Struttura di un elaboratoreModello di von Neumann: CPU, memoria centrale, bus e periferiche; ciclo fetch-decode-execute e gerarchia di memoria.Struttura di un elaboratore →). - Pseudocodice: costrutti di alto livello (
for,while,if, ricorsione); input e output si dichiarano sempre. - Taglia dell'istanza: lunghezza di un array, numero di nodi di un albero, ed per un grafo. La complessità si esprime in funzione della taglia (vedi Complessità in tempo e caso pessimoPerché lo studio sperimentale non basta; complessità al caso pessimo come massimo sul numero di operazioni tra le istanze di una data taglia; stima con limiti superiore e inferiore senza trovare l'istanza peggiore; esempi arrayMax, prefixAverages e InsertionSort; efficienza asintotica e limiti dell'analisi.Complessità in tempo e caso pessimo →).
- ADT e struttura dati: l'ADT specifica cosa fanno le operazioni; la struttura concreta fissa come e quindi il costo (vedi Liste, pile e codeRipasso degli ADT elementari: lista index-based (array) e position-based (lista doppiamente concatenata con sentinelle), pila LIFO, coda FIFO, deque, iteratori; interfacce, implementazioni e costi; array estendibile e coda circolare.Liste, pile e code →, Tipi di dato astrattiUn ADT è definito dalle operazioni e dal loro comportamento, non dalla rappresentazione; interfaccia e implementazione; realizzazione in Python con le classi e in C con header e tipo opaco; esempio di un ADT Frazione.Tipi di dato astratti →).
- Ricerca lineare in un array crescente: nel caso peggiore confronti, (vedi Ricerca lineare e binariaRicerca lineare su sequenze qualsiasi in O(n); ricerca binaria su sequenze ordinate in O(log n), versione iterativa e ricorsiva, invariante e errori di indice; modulo bisect.Ricerca lineare e binaria →).
- Ricerca binaria:
lo <- 0; hi <- n-1; while lo <= hi: mid <- floor((lo+hi)/2); if A[mid] = x return mid; if A[mid] < x then lo <- mid+1 else hi <- mid-1; return -1. - Invariante: se è in , la sua posizione sta in . Vale all'inizio; si conserva perché l'ordine crescente esclude la metà scartata; a fine ciclo () l'intervallo è vuoto, quindi non c'è (vedi Dimostrazioni, induzione e invariantiTecniche di dimostrazione (esempio, controesempio, assurdo), induzione con casi base multipli, invarianti di ciclo (inizializzazione, conservazione, uso alla fine), schema generale per provare la correttezza; esempi svolti su arrayMax, sequenza di bit e numeri di Fibonacci.Dimostrazioni, induzione e invarianti →).
- Costo: al più iterazioni, perché dopo iterazioni restano al più candidati; quindi . Per sono , per sono .
- Traccia su , : (, ), poi con : restituisce .
- Errori tipici: confondere problema e algoritmo; non dichiarare input e output; senza parte intera.