Algoritmi
1.1: Algoritmi
- Algoritmo: sequenza di istruzioni elementari che portano alla soluzione di problemi computazionali.
- Problema computazionale: fornire parametri che descrivano input e output.
- Input: un array di interi.
- Output: un numero intero.
- Vincoli: descrivono il legame fra input e output.
- Istanza: si ottiene specificando i valori nell’input -> esempi di input (gli input sono diversi se contengono diversi valori).
- Esercizio: ordinare array.
- Input: array di n interi.
- Output: un array (può essere anche uno diverso) di n interi.
- Vincoli: L’array di output è la permutazione dell’array di input tale che: primo elemento <= secondo elemento <= terzo elemento ecc.
- Istanza: 10 7 9 3 4 74 3.
- T(n): Caso peggiore, ossia l’algoritmo più lento nell’effettuare l’operazione p.
- T(n): Caso migliore, ossia l’algoritmo più veloce nell’effettuare l’operazione m.
- T(n): Caso medio, ossia “tempo dell’esecuzione previsto” (linea sopra la M piccola).
Elementi utilizzati per gli algoritmi
- Pseudocodice simil Java - C.
- If / else / then (poco usato).
- Commenti */ /*.
- Assegnamenti x <- 1 / x = 1 / x:= 1.
- Test x == 1.
- Variabili locali a meno dei vettori.
- Array V[i] 1...n (si parte da 1, non da 0).
- Puntatori.
- Passaggio di parametri (per valore).
- Macchina su cui si eseguono gli algoritmi è la RAM (Random Access Machine).
- Esempio: 6.
- Algoritmo elabora 10 op./sec.
- Dimensioni dell’input: n.
- T(n) ? p.
Tempo alg. n 20 100 10000 1000n 0.02” 0.1” 1” 10n (1/100)*0.02” 100” 1/100” 2 100n 0.04” 10” 2’ n 62 1” 3*10 anni 1000000n 20” 1000”.
Esercizio: Ricerca sequenziale
Ricerca Sequenziale (trovare valore K nel vettore V).
- Input: vettore V, valore K.
- ricercaSequenziale(V,K).
- i = 1.
- while(i < V.length && V[i] == K).
- i++;
- if (i <= length(V).
- return(i).
- else.
- return(-1).
Calcolo tempo dell’esecuzione
| Istruzione | Tempo | Descrizione |
|---|---|---|
| i = 1 | C1 | |
| while | C2 twi | -> quante volte viene eseguito while |
| i++ | C3 twi -1 | -> eseguito una volta in meno del while |
| if | C4 | |
| return | C5 tif | -> eseguito ogni volta che if = true |
| else | Non conta mai nel tempo di esecuzione | |
| return | C6 tif | -> eseguito ogni volta che if = false |
- Caso migliore: K è in V[1] (array vanno da 1 a n). twi = 1.
- Caso peggiore: K non c’è in V.
- Caso medio: K si trova in V[n/2].
- Tempo: C1 + C2 * twi + C3 * twi -1 + C4 + C5 * tif + C6 * tif.
- Tmig(n): C1 + C2 * 1 + C3 * 0 + C4 + C5 * 1 + C6 * 0.
- Tp(n): C1 + C2 * (n + 1) + C3 * n + C4 + C5 * 0 + C6 * 1.
- TM(n): C1 + C2 * ((n + 1)/2) + C3 * (n/2) + C4 + C5 * 1 + C6 * 0.
Esercizio: Ricerca dicotomica
Ricerca Dicotomica (per trovare valore K nel vettore V provo a vedere se si trova a metà. Il programma mi dirà in quale metà si trova e si opererà su essa e così via).
- Input: vettore V, valore K.
- ricercaDicotomica(V[],K).
- sx = 1.
- dx = length(V);
- m = (sx + dx)/2;
- while (V[m]!=k && dx >= sx) {
- if V[m] > k {
- dx = m - 1.
- else sx = m + 1 }
- m = (sx + dx)/2 }
- if sx <= dx.
- return(m).
- else return(-1).
Calcolo tempo dell’esecuzione
| Istruzione | Tempo | Valore |
|---|---|---|
| sx = 1 | C1 | |
| dx = length(V) | C2 | |
| m = (sx + dx)/2 | C3 | |
| while | C4 twi | -> quante volte viene eseguito while |
| if V(m) | C5 twi -1 | -> eseguito una volta in meno del while |
| dx = m -1 | C6 tif | -> eseguito ogni volta che if = true |
| sx = m + 1 | C7 fif | -> eseguito ogni volta che if = true |
| m = (sx + dx)/2 | C8 twi -1 | -> eseguito una volta in meno del while |
| if sx <= dx | C9 | |
| return | C10 tif2 | -> eseguito ogni volta che if2 = true |
| return | C11 fif2 | -> eseguito ogni volta che if2 = true |
- Caso migliore: K è in posizione m. twi = 1 V[m] = k.
- Caso peggiore: K non c’è in V. twi = max. tif + fif = twi -1.
- Caso medio: ciclo while 1 passo n, 2 passi n/2, 3 passi n/3, i - passi n/2i, n/2i = 1, 2i = n, i = log2 n -> max = log2 n.
- Tempo: C1 + C2 + C3 + C4 twi + C5(twi -1) + C6 tif + C7 fif + C8(twi -1) + C9 + C10 tif2 + C11 fif2.
- Tmig(n): C1 + C2 + C3 + C4 + C9 + C10. 6 istruzioni.
- Tp(n): C1 + C2 + C3 + C4 * max + C5 * max + C6 (max - 1) + C8 (max - 1) + C9 + C11.
- TM(n): (log2 n)/2.
Algoritmi di ordinamento
Selection sort
- Cerca minimo e lo mette in 1.
- Riparte da 2 -> cerca minimo -> mette in 2.
- Ecc.
- sel_sort(V[]).
- for i = 1 to length V[] - 1.
- pmin = 1.
- for j = i + 1 to n.
- if V[j] < V[pmin].
- pmin = j.
- Scambia (V, i, pmin) 3 istruzioni.
| Istruzione | Tempo | Valore |
|---|---|---|
| for i | C1 | n |
| pmin = 1 | C2 | n - 1 |
| for j | C3 | ∑i=1n-2 i |
| if | C4 | ∑i=1n-2 i |
| pmin = j | C5 | tif |
| scambia | C6 | 3n |
- Caso migliore: Array già ordinato. tif = 0.
- Caso peggiore: Array ordinato al contrario. tif = max.
- Tempo: (C1 + C2 + 3C6)n + (C3 + C4)(∑ i) + C5 tif, i=1, n-2.
- Tmig(n): (C1 + C2 + 3C6)n + (C3 + C4)(∑ i), i=1, n-2.
- Tp(n): (C1 + C2 + 3C6)n + (C3 + C4 + C5)(∑ i), i=1.
Insertion sort
- Array diviso in due: ordinati e non ordinati.
- Si prendono gli elementi non ordinati e si inseriscono nella apposita posizione.
- void ins_sort(V[]).
- for i = 2 to n.
- k = V[i]; j = i -1;
- while k < V[j] and j > 0.
- V[j + 1] = V[j].
- j--;
- V[j+1] = k.
| Istruzione | Tempo | Valore |
|---|---|---|
| for i | C1 | n |
| k = V[i]; j = i -1; | C21 + C22 | n |
| while | C3 | ∑i=2n twi (tempo in cui il while è vero all’esecuzione i-esima) |
| V[j + 1] = V[j] | C4 | ∑i=2n twi -1 |
| j--; | C5 | ∑i=2n twi -1 |
| V[j+1] = k | C6 | n - 1 |
- Caso migliore: Array già ordinato. twi = 1 + 1 + 1.. = n -1.
- Caso peggiore: Array ordinato in modo decrescente. twi = i.
- Tempo: C1n + (C21 + C22 + C6)(n - 1) + C3(∑ twi) + (C4 + C5)(∑ twi -1), i=2, n.
- Tmig(n): C1n + (C21 + C22 + C6)(n - 1) + C3(n-1) -> asintotico a n.
- Tp(n): C1n + (C21 + C22 + C6)(n - 1) + C3(∑ i) + (C4 + C5)(∑ i - 1), i=2, n.
- ∑ i = n(n +1)/2 -1, i=2, n.
- ∑ i - 1 = 2 + 3 + 4 +...(n-1) = ∑ i -2, i=2.
- Il tempo peggiore è asintotico a n2.
- Tempo medio = n2/2.
Limiti asintotici
Si eliminano le costanti e i termini di ordine inferiore.
- O(f(n)) = limite asintotico superiore (O grande).
- Ω(f(n)) = limite asintotico inferiore (Omega grande).
- Θ(f(n)) = limite asintoticamente stretto (Theta).
O grande - limite asintoticamente superiore (caso peggiore)
- t(n) = O(f(n)) se esiste n0, c | ∀n > n0, t(n) < c*f(n).
- Esempio: 3n2 = O(2n2).
- c = 2, n0 = 10.
- 3n2 <= (2n)2 -> 3x2 1 <= 2x2 2 1 -> 3 <= 4.
Omega grande - limite asintoticamente inferiore (caso migliore)
- t(n) = Ω(f(n)) se esiste n0, c > 0 | ∀n > n0, t(n) > c*f(n).
- Esempio: 3n2 = Ω(n2).
- c = 2, n0 = 10.
- 3x2 1 >= 2x2 1 -> 3 >= 2.
Theta - limite asintoticamente stretto
- t(n) = Θ(g(n)) se e solo se t(n) = O(f(n)) e t(n) = Ω(g(n)).
- Θ(g(n)) = {f(n) : esistono costanti positive c1, c2 e n0 tali che 0 < c1g(n) < c2g(n) per ogni n > n0}.
- Una funziona f(n) appartiene all’insieme Θ(g(n)) se esistono costanti positive c1, c2 tali che f(n) possa essere compresa tra le due costanti, per un valore sufficientemente grande di n.
- In sostanza theta esiste se e solo se O grande e Omega grande sono uguali.
- Nel grafico, se dopo n0 f(n) coincide e sta sotto a c1g(n) e coincide o sta sopra a c2g(n), allora si dice che g(n) è un limite asintoticamente stretto per f(n).
- f(n) deve essere non negativa quando n è sufficientemente grande (ovvero nessun membro di f(n) appartenente a Θ(g(n)) deve essere asintoticamente non negativo).
Algoritmo
V1 e V2 sono due array contenenti n valori. Contare quanti elementi di V2 stanno in V1.
- c = 0.
- for i = 1 to V2 length.
- j = 1.
- while(V2[i] != V1[j]) and (j < V2 length).
- j++.
- if j < V1 length.
- c++.
- return c.
| Istruzione | Valore |
|---|---|
| c = 0 | c |
| for i = 1 to V2 length | c * n |
| j = 1 | c * n |
| while(V2[i] != V1[j]) and (j < V2 length) | ∑i=1n twi |
| j++ | ∑i=1n twi |
| if j < V1 length | c * n |
| c++ | c* tif |
| return c | c |
- Tempo → C2 + C3 · n + C2 · ∑ twi + C · tif, i=1, n.
- Caso peggiore: non ci sono elementi di V2 in V1.
- twi = n; tif = 0.
- tp(n) = C2 + C3 · n + C2 · ∑ n ~ C2 + C3 · n + C2 · n2 ~ O(n2).
- Caso migliore: V2 ha un solo numero ripetuto n volte e quel numero è al primo posto di V1.
- twi = 0; tif = n.
- tm(n) = C2 + C3 · n + C · n ~ Ω(n).
- N.B: O e Ω sono diversi, quindi non esiste.
Limiti asintotici
- 3n2 -> O(n2) -> o(n2).
- 3n2 -> Ω(n2) -> w(n2).
- 4n2 − 2n -> O(n2) -> o(n2).
- 4n2 − 2n -> Ω(n2) -> w(n2).
- Esiste una costante che, dopo un certo n0, moltiplicata per n2 sia più piccola di 4n2 − 2n.
- n = 10, c = 1, 4(102) − 2(10) = 380, Ω(n2) = 1/100.
- log n -> O(n) sarebbe meglio nlogn o n -> O((log n)) -> O(n).
- log n -> Ω(logn) -> w(1).
- k log n -> O(nlogn), log n, k log n -> k è costante quindi va via.
- max{f(n); g(n)}.
- f(n) = 2n.
- g(n) = 3n.
- Considero la più grande quindi f(n).
- f(n) = 2n2 + 3 -> O(n2).
- f(n) = 2n2 + 3 -> Ω(n2).
- N.B: b(n + a)b, a,b sono costanti! O(nb).
- Esempio: (n + 10)7 O(n7). NON risolvere tanto tutte le n con esponente minore di 7 vanno via! (n + 10)7.
Algoritmo
2 array contenenti n bit da sommare in un terzo array di dimensioni n + 1.
Esempio: A 10110 + B 10100 = C 101010.
- void Somma (A[], B[]).
- resto = 0.
- for i = n down to 1.
- C[i+1] = A[i] + B[i] + resto.
- if C[i+1] < 1.
- resto = 0.
- else.
- C[i+1] = C[i+1] - 2.
- resto = 1.
- C[i] = resto.
| Istruzione | Valore |
|---|---|
| resto = 0 | c |
| for i = n down to 1 | c * n |
| C[i+1] = A[i] + B[i] + resto | c * n |
| if C[i+1] < 1 | c * n |
| resto = 0 | c* tif |
| C[i+1] = C[i+1] - 2 | c* fif |
| resto = 1 | c* fif |
| return | c |
- Tempo C2 + C3 · n + C tif + C fif.
- Caso peggiore: 0101 ultimo bit di A e B = 1 e poi A[i] o B[i] = 1 1011, c’è sempre riporto.
- tif = 0; fif = n.
- tp(n) = C2 + C3n + C2n = C2 + C5n = O(n).
- Caso migliore: non si ha mai riporto.
- A[i] and B[i] = 0.
- tif = n; fif = 0.
- tm(n) = C2 + C3n + Cn + 0 = C2 + C4n = Ω(n).
- Siccome nel caso migliore è Ω(n) e nel peggiore l’algoritmo è O(n), l’algoritmo è Θ(n).
- Tempo medio: C2 + C4n + C2.
Algoritmo
Trasformare numero da decimale a binario.
- z = n.
- t = 0.
- while z > 0.
- x = z mod 2.
- z = z div 2.
- if x == 0 then.
- for i = 1 to n.
- t = t + 1.
- return t.
| Istruzione | Valore |
|---|---|
| z = n | c |
| t = 0 | c |
| while z > 0 | c* twi |
| x = z mod 2 | c* twi |
| z = z div 2 | c* twi |
| if x == 0 then | c* twi |
| for i = 1 to n | c* n * tif |
| t = t + 1 | c* n * tif |
| return t | c |
- Tempo C3 + C4 · twi + Cn · tif.
- Caso peggiore: n = potenza di 2 = 2h -> h = log2(n).
- twi = h; tif = h.
- tp(n) = C2 + Cn log2(n).
- = O(n log n).
- Caso migliore: n - 1 = 2h.
- twi = h; tif = 0.
- tm(n) = C3 + C4 · logn.
- Ω = O(logn).
Algoritmo
- for i = 1 to n.
- b[i] = 0.
- j = n.
- while j > i.
- b[i] = b[i] + a[j].
- j--.
| Istruzione | Valore |
|---|---|
| for i = 1 to n | c * n |
| b[i] = 0 | c * n (sarebbe n-1 ma fa niente) |
| j = n | c * n |
| while j > i | c* ∑i=1n n − i (è un for) |
| b[i] = b[i] + a[j] | c* ∑i=1n n − i |
| j-- | c* ∑i=1n n − i |
- Non c’è caso migliore o peggiore perché sono due for e devono essere eseguiti per forza.
- t(n) = 3Cn + 3 · C ∑ n − i = 3Cn + 3 · C ∑ i = 3Cn + 3C n(n−1)/2 = Θ(n2).
Algoritmo con cicli For innestati
- for i = 1 to n.
- for j = 1 to n.
- for k = 1 to j - 1.
- A[k] = B[j].
| Istruzione | Valore |
|---|---|
| for i = 1 to n | c * n |
| for j = 1 to n | c * n * n = c * n2 |
| for k = 1 to j - 1 | c * n * ∑j=1n j − 1 |
| A[k] = B[j] | c * n * ∑j=1n j − 1 |
- 3 for innestati = n3.
- Non è mai maggiore di n3.
- Può essere minore di se il for più interno viene eseguito poche volte.
- Non c’è caso migliore o peggiore perché devono essere eseguiti tutti almeno una volta.
- Tempo n n (n)(n−1)n.
- t(n) = C n2 + C n2 + 2 · Cn ∑ j − 1 = C n2 + C n2 + 2 · Cn ∑ j = C n2 + C n2 + 2 · Cn n(n−1)/2 = Θ(n3).
Sommatorie cicli innestati
∑i=1n−1 i(i+1) − n(n+1)/2 = 2/2.
∑i=1n−1 (n−1) · i(i+1) − n(n+1)/2 = 2/2.
∑i=1n−1 (n−1) · i(i + 1) − n(n+1)/2 = 1/2.
∑i=1n−1 (n−1) · 2i + i − n(n+1)/2 = 1/2.
∑i=1n−1 (n−1) · 2i + i − n(n+1)/2 = 1/2.
∑i=1n−1 (n−1) · 2i + ∑i=1n−1 i − n(n+1)/2 = 1/2.
∑i=1n−1 (n−1) · 2i + ∑ i − n(n+1)/2 = n(n+1)(2n+1)/6.
n(n+1)/2 − n(n+1)(2n+1)/6 + n(n−1)(n−1)/2.
Ordine massimo quindi n3, in questo caso non c’è caso migliore o un caso peggiore Θ(n3) perché in ogni caso i 3 for vengono eseguiti.
Principio di induzione & Ricorsione
- La ricorsione è basata sul principio di induzione matematica.
- Proprietà p(n).
- 1. p(n) vera ∀n ≥ n0.
- a. p(n0) vera.
- b. p(n) implica p(n+1) vera.
- 2. n0 < m < n-1 p(m) implica p(n).
- L’induzione si basa sull’
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
-
Algoritmi e strutture dati
-
Algoritmi e strutture dati
-
Algoritmi e Strutture Dati
-
Algoritmi e strutture dati