Problemi computazionali e algoritmi
In questa pagina 6
Problema computazionale
Un problema computazionaleSpecifica di cosa si vuole ottenere, indipendente dal modo di ottenerlo. è un insieme di coppie con (istanza, dominio delle istanze) e (soluzione, dominio delle soluzioni). Si richiede che ogni istanza abbia almeno una 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 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.
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 .
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
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 →).
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 →.
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: nel caso peggiore manca e si fanno confronti, .
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 -1- Invariante: se è in , allora la sua posizione sta in . Vale all'inizio ( è tutto l'array) e si conserva: se , per l'ordine può stare solo a destra di .
- Complessità: l'intervallo dimezza a ogni iterazione, quindi ci sono al più iterazioni di costo costante: .
Traccia su , : () ; , , restituisce .
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); ogni istanza ha almeno una soluzione, e l'algoritmo ne calcola una.
- Algoritmo risolve se per ogni produce con ; passi elementari del modello RAMRandom Access Machine: dati e programma in memoria, ogni passo elementare costa Θ(1).: assegnamento, operazioni aritmetico-logiche, indicizzazione, return.
- Pseudocodice: input e output sempre dichiarati. Taglia: per array e alberi, ed per i grafi (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 = cosa fanno le operazioni (interfaccia); struttura dati concreta = come (implementazione e costi). 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 →.
- Ricerca in array ordinato: lineare ; binaria , con invariante "se c'è, sta in "; , al più iterazioni.
- 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. Traccia su , : ⇒ indice . - Errori: confondere problema e algoritmo; non dichiarare input e output; indici non interi.