Salta al contenuto
Note per Studenti Problemi computazionali e algoritmi

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. Π\Pi è un insieme di coppie (i,s)(i, s) con i∈Ii \in I (istanza, dominio delle istanze) e s∈Ss \in S (soluzione, dominio delle soluzioni). Si richiede che ogni istanza abbia almeno una soluzione, cioè che per ogni i∈Ii \in I esista almeno un s∈Ss \in S con (i,s)∈Π(i, s) \in \Pi. Le coppie stanno in I×SI \times S, quindi Π⊆I×S\Pi \subseteq I \times S. Il simbolo I×SI \times S è 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 →): Π\Pi ne è un sottoinsieme, cioè una relazione tra istanze e soluzioni. Se ogni istanza avesse una sola soluzione, Π\Pi 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 II a SS; 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 (3,4)(3,4) ha come soluzione 77, e (1,9)(1,9) e (4,6)(4,6) hanno la stessa soluzione 1010.

Problema II SS Π\Pi
somma di interi coppie (x,y)(x, y) Z\mathbb{Z} ((x,y),s)((x,y), s) con s=x+ys = x + y
ordinamento (v. 1) array di interi AA array ordinati BB (A,B)(A, B) con BB permutazione ordinata di AA
ordinamento (v. 2) array di interi AA permutazioni PP (A,P)(A, P) con PP che ordina AA

Due osservazioni: una soluzione può servire a più istanze (la somma 1010 vale per (1,9)(1,9) e per (4,6)(4,6)) 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 AA (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 Π\Pi se, ricevuta in input un'istanza i∈Ii \in I, produce in output una soluzione ss con (i,s)∈Π(i, s) \in \Pi: 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 Θ(1)\Theta(1) (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 Θ(1)\Theta(1).

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 (nn ed mm). 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 nn interi è nn; quella di un grafo è la coppia (n,m)(n, m) 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:

  1. 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.
  2. Struttura dati concreta: l'implementazione dell'ADT (una classe in Java, un insieme di struct e 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. II: coppie (AA array crescente di nn interi distinti, xx intero). SS: un indice ii con A[i]=xA[i] = x, oppure −1-1 se xx 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 xx manca e si fanno nn confronti, uno per elemento, quindi Θ(n)\Theta(n).

Ricerca binaria. Si sfrutta l'ordine: si confronta xx 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

Proprietà (ricerca binaria: invariante e complessità)

Perché proprio ⌊log⁡2n⌋+1\lfloor \log_2 n \rfloor + 1. Sia m=hi−lo+1m = hi-lo+1 il numero di posizioni ancora candidate. Se xx non è in midmid, l'elemento centrale è scartato e si tiene una delle due metà restanti, al più la più grande, che ha ⌊m/2⌋\lfloor m/2 \rfloor elementi. Dopo kk iterazioni restano quindi al più ⌊n/2k⌋\lfloor n/2^k \rfloor candidati, e il ciclo termina quando ne restano 00. Vale ⌊n/2k⌋=0  ⟺  n/2k<1  ⟺  2k>n  ⟺  k>log⁡2n\lfloor n/2^k \rfloor = 0 \iff n/2^k < 1 \iff 2^k > n \iff k > \log_2 n (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 kk con k>log⁡2nk > \log_2 n è ⌊log⁡2n⌋+1\lfloor \log_2 n \rfloor + 1. Controllo: per n=7n=7 è ⌊2,81⌋+1=3\lfloor 2{,}81 \rfloor + 1 = 3, per n=8n=8 è 3+1=43+1=4, per n=100n=100 è 6+1=76+1=7 (confermato simulando la ricerca su ogni possibile xx).

Esempio. Su A=[2,5,8,12,16,23,38]A=[2,5,8,12,16,23,38] con x=23x=23 la ricerca binaria restituisce l'indice 55 dopo 22 iterazioni; con n=7n=7 ne servono al più ⌊log⁡27⌋+1=3\lfloor\log_27\rfloor+1=3.

Traccia su A=[2,5,8,12,16,23,38]A = [2, 5, 8, 12, 16, 23, 38], x=23x = 23:

iterazione lolo hihi mid=⌊(lo+hi)/2⌋mid=\lfloor (lo+hi)/2 \rfloor A[mid]A[mid] confronto con x=23x=23 effetto
1 0 6 ⌊6/2⌋=3\lfloor 6/2 \rfloor = 3 12 12<2312 < 23 lo←4lo \leftarrow 4
2 4 6 ⌊10/2⌋=5\lfloor 10/2 \rfloor = 5 23 uguale restituisce 55

Il grafico confronta il numero massimo di passi della ricerca lineare, nn, con il massimo ⌊log⁡2n⌋+1\lfloor \log_2 n \rfloor + 1 della binaria: già a n=100n=100 sono 100100 contro 77.

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 nn e mm).
  • Scrivere mid=(lo+hi)/2mid = (lo + hi)/2 senza la parte intera: gli indici devono essere interi.

Versione ripasso

Teoria collegata