Un algoritmo realizza una relazione funzionale tra i valori di input e quelli di output F = { (s, s ) } per ogni s esiste una e una sola coppia (s, s ). Esempio: un algoritmo che calcola il quadrato di un numero naturale è descritto dal seguente insieme di coppie: {(0, 0), (1, 1), (2, 4), (3, 9), (4, 16)... }.
insieme di coppie infinito predicati della logica per caratterizzare le coppie di input/output {(x, z) / x N & z = x. x } Specifica di un algoritmo: espressione non ambigua dei requisiti funzionali che l algoritmo deve soddisfare.
Algoritmo A variabili di input: x1, x2,..., xn ( x ) variabili di output: z1, z2,...,zk ( z ) Specifica di A è una coppia di predicati: < p-in(x), p-out(x, z)>, dove: p-in(x): predicato di input di A, specifica i vincoli cui devono soddisfare i valori forniti per le variabili di input (il dominio delle variabili); p-out(x, z): predicato di output di A, specifica la relazione di input / output.
quadrato di un numero naturale p-in(x) x 0 p-out(x, z) z = x. x moltiplicazione di x1 per x2 p-in(x1, x2) x1 0 & x2 0 p-out(x1, x2, z) z = x1. x2 quoziente e il resto della divisione di x1 per x2 p-in(x1, x2) x1 0 & x2 > 0 p-out(x1, x2, z1, z2) x1 = z1. x2 + z2 & 0 z2 < x2
Correttezza parziale Dato un algoritmo A e una specifica < p-in(x), p-out(x, z)> A si dice parzialmente corretto rispetto alla specifica se per ogni valore a delle variabili di input x, che soddisfa il predicato di input, se l algoritmo termina, termina con valore b per le variabili di output z, che soddisfa il predicato p-out(a, b).
Terminazione Dato un algoritmo A e un predicato di input p-in(x), si dice che A termina su p-in(x) se per ogni valore a delle variabili di input x, che soddisfa il predicato di input, l algoritmo termina.
Correttezza totale Dato un algoritmo A e una specifica < p-in(x), p-out(x, z)> A si dice totalmente corretto rispetto alla specifica se è parzialmente corretto rispetto alla specifica e termina sul predicato di input.
Verifica della correttezza parziale La correttezza parziale di un algoritmo A rispetto alla specifica <p-in(x), p-out(x, z)> è espressa dalla Formula di correttezza di Hoare: { p-in(x) } A { p-out (x, z) } p-in(x) p-out(x, z) { } A { }
{ p-in(x1, x2) x1 0 & x2 0 } Prodotto { p-out(x1, x2, z) z = x1. x2 }
{ p-in(x) x 0 } Incremento (x) 1 z x 2 z z + 1 3 return z { p-out(x, z) z > 0 }
{ p-in(x) x 0 } Incremento (x) { x 0 } x 0 x +1 > 0 1 z x z +1 > 0 2 z z + 1 { z > 0 } z > 0 3 return z { p-out(x, z) z > 0 } per ogni valore 0 assunto da x alla chiamata del metodo, al rientro il valore di z sarà > 0
z = x z 0 & (z = x z = - x) { } modulo (x) 1 if x 0 then { x 0 } 2 z x { x 0 & z 0 & z = x} else { x < 0 } 3 z - x { x < 0 & z > 0 & z = - x} { z 0 & (z = x z = - x) } 4 return z { p-out(x, z) z = x } p-in(x) true
then { x 0 } x 0 & x 0 & x = x z x { x 0 & z 0 & z = x} else { x < 0 } x < 0 & -x > 0 & -x = - x z - x { x < 0 & z > 0 & z = - x} { z 0 & (z = x z = - x) }
{x1 0 & x2 0 } moltiplicazione (x1, x2) y x1 z 0 while y > 0 do z z + x2 y y - 1 return z {z = x1. x2 }
{x1 0 & x2 0 } moltiplicazione (x1, x2) {x1 0 & x2 0} x1. x2 = x1. x2 + 0 & x1 0 y x1 z 0 {x1. x2 = y. x2 + z & y 0} while y > 0 do z z + x2 y y - 1 return z {z = x1. x2 }
{x1 0 & x2 0 } moltiplicazione (x1, x2) y x1 z 0 {x1. x2 = y. x2 + z & y 0} while y > 0 do {x1. x2 = y. x2 + z & y > 0} z z + x2 y y - 1 {x1. x2 = y. x2 + z & y 0} return z {z = x1. x2}
x1. x2 = (y-1). x2 + x2 + z & y-1 0 {x1. x2 = y. x2 + z & y > 0} z z + x2 y y - 1 {x1. x2 = y. x2 + z & y 0}
E dopo l esecuzione del while, se termina: {x1 0 & x2 0} moltiplicazione (x1, x2) y x1 z 0 { x1. x2 = y. x2 + z & y 0 } while y > 0 do { x1. x2 = y. x2 + z & y > 0} z z + x2 y y - 1 { x1. x2 = y. x2 + z & y 0 } { x1. x2 = y. x2 + z & y = 0 } return z {z = x1. x2}
Difficoltà: esprimere il predicato che si mantiene vero durante l esecuzione del ciclo: Invariante di ciclo y 45 19 44 19 43 z 19.. 3 19 2 19 1 19 y: valori della prima colonna z : somma parziale Ad esempio: 855 = 3 x 19 + 798 x1 x x2 y x x2 z 855
Moltiplicazione alla russa y1 y2 45 19 22 38 19 --- z z: somma parziale y1: valori della prima colonna y2: valori della seconda colonna y1 11 76 5 152 2 304 1 608 76 152 --- 608 Ad esempio: 855 = 5 x 152 + 95 x1 x x2 y1 x y2 z y2 855
{ x1 0 & x2 0 } molt-russa (x1, x2) y1 x1 y2 x2 z 0 { x1. x2 = y1. y2 + z & y1 0 } while y1 > 0 do { x1. x2 = y1. y2 + z & y1 > 0 } if y1 is odd then z z + y2 y1 y1 div 2 y2 y2 + y2 { x1. x2 = y1. y2 + z & y1 0 } { x1. x2 = y1. y2 + z & y1 = 0 } return z { z = x1. x2 }
x1. x2 = x1. x2 + 0 & x1 0 { x1 0 & x2 0 } y1 x1 y2 x2 z 0 { x1. x2 = y1. y2 + z & y1 0 }
x1. x2 = (y1 div 2). (y2 + y2) + z se y1 pari (y1 div 2). (y2 + y2) + (z + y2) se y1 dispari & y1 div 2 0 { x1. x2 = y1. y2 + z & y1 > 0 } if y1 is odd then { x1. x2 = y1. y2 + z & y1 > 0 & y1 dispari} z z + y2 y1 y1 div 2 y2 y2 + y2 { x1. x2 = y1. y2 + z & y1 0 }
2n x m = n x 2m (2n + 1) x m = 2n x m + m = n x 2m + m y1. y2 = (y1 div 2). (y2 + y2) se y1 pari y1. y2 = (y1 div 2). (y2 + y2) + y2 se y1 dispari x1. x2 = y1. y2 + z & y1 > 0 x1. x2 = (y1 div 2). (y2 + y2) + z se y1 pari (y1 div 2). (y2 + y2) + (z + y2) se y1 dispari
{ x1 0 & x2 0 } molt-russa (x1, x2) y1 x1 y2 x2 z 0 { x1. x2 = y1. y2 + z & y1 0 } while y1 > 0 do if y1 is odd then z z + y2 y1 y1 div 2 y2 y2 + y2 return z { z = x1. x2 }
{ x1 0 & x2 > 0 } divisione (x1, x2) q 0 r x1 { x1 = q. x2 + r & 0 r & x2 > 0 } while r x2 do { x1 = q. x2 + r & 0 r & x2 > 0 & r x2 } q q + 1 r r - x2 { x1 = q. x2 + r & 0 r & x2 > 0 } { x1 = q. x2 + r & 0 r & r < x2 } return (q, r) { x1 = q. x2 + r & 0 r < x2 }
x1 0 & x2 > 0 q 0 r x1 x1 = 0. x2 + x1 & 0 x1 & x2 > 0 x1 = q. x2 + r & 0 r & x2 > 0 & r x2 q q + 1 r r - x2 x1 = (q + 1). x2 + (r - x2) & 0 (r - x2) & x2 > 0 }
Il costrutto for: for i a to b do S supponiamo che: - gli estremi a e b siano numeri naturali - a b - passo uno. il costrutto for e` equivalente a: i a while i b do S i i + 1
{ n 1 } n = length[a] Massimo (A, n) current-max A[0] i 1 { current-max A[j], per ogni j, 0 j < i } for i 1 to n-1 do while i < n do if current-max < A[i] then current-max A[i] i i + 1 { current-max A[j], per ogni j, 0 j < n } return current-max { current-max è il massimo tra gli elementi di A }
P(x) = a 0 + a 1 x+ + a n x n n = grado del polinomio { n 1} Poly-eval (A, x, n) y 1 result A[0] i 1 { result = A[0] + A[1] x +... + A[i-1] x i-1 & y = x i-1 } for i 1 to n do y y x result result +A[i] y i i +1 return result { result = A[0] + A[1] x +... + A[n] x n }
P(x) = a 0 + x (a 1 + + x (a n-1 + x a n )) ) { n 1} Horner (A, x, n) result A[n] i n - 1 {result = A[i+1] + A[i+2] x +... + A[n] x n-(i+1) } for i n - 1 downto 0 do result result x + A[i] i i - 1 {result = A[i+1] + A[i+2] x +... + A[n] x n-(i+1) } return result {result = A[0] + A[1] x +... + A[n] x n }
Metodi ricorsivi { n 1} Massimo-ricorsivo (A, n) if n = 1 then current-max A[0] return current-max y Massimo-ricorsivo (A, n-1) current-max max (y, A[n-1]) return current-max n = length[a] { current-max è il massimo tra gli elementi di A} T(n) = h se n = 1 T(n-1) + k altrimenti
{ n = 1} current-max A[0] return current-max {current-max è il massimo tra gli elementi di A} n = 1 A[0] è il massimo tra gli elementi di A
{ n > 1} y Massimo-ricorsivo (A, n-1) current-max max (y, A[n-1]) return current-max { current-max è il massimo tra gli elementi di A} La correttezza viene verificata supponendo che la chiamata interna funzioni bene e dimostrando che le modifiche subite dalle variabili rendono vero il predicato.
{ n > 1} y Massimo-ricorsivo (A, n-1) current-max max (y, A[n-1]) return current-max { current-max è il massimo tra gli elementi di A} Supponiamo che Massimo-ricorsivo su un numero di elementi < n lavori correttamente: { n > 1} y Massimo-ricorsivo (A, n-1) { current-max è il massimo tra gli elementi di A[0..n-2]}
{ n > 1} y Massimo-ricorsivo (A, n-1) { current-max è il massimo tra gli elementi di A[0..n-2] } current-max max (y, A[n-1]) { current-max è il massimo tra gli elementi di A[0..n-1] } return current-max { current-max è il massimo tra gli elementi di A} La dimostrazione di correttezza per i metodi ricorsivi viene fatta per induzione sul numero di chiamate.
{ p 0 & r 0 & p r} DI-Massimo-ricorsivo (A, p, r) if p = r then current-max A[p] return current-max if p = r-1 then if A[p] < A[r] then current-max A[r] return current-max else current-max A[p] return current-max q (p + r)/2 current-max-1 DI-Massimo-ricorsivo (A, p, q) current-max-2 DI-Massimo-ricorsivo (A, q +1, r) current-max max(current-max1, current-max2) return current-max { current-max è il massimo tra gli elementi di A}
La correttezza e` facile da verificare. Piu` difficile valutarne la complessita` esprimiamo la complessita` come relazione di ricorrenza: T(1) = c (p = q) T(2) = d (p = q-1) T(n) = T( n/2 ) + T( n/2 ) + e (p < q-1) Anche per l algoritmo ricorsivo la complessita` e` Θ(n), come per l algoritmo iterativo visto in precedenza.