




















Studia grazie alle numerose risorse presenti su Docsity
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Prepara i tuoi esami
Studia grazie alle numerose risorse presenti su Docsity
Prepara i tuoi esami con i documenti condivisi da studenti come te su Docsity
Trova i documenti specifici per gli esami della tua università
Preparati con lezioni e prove svolte basate sui programmi universitari!
Rispondi a reali domande d’esame e scopri la tua preparazione
Riassumi i tuoi documenti, fagli domande, convertili in quiz e mappe concettuali
Studia con prove svolte, tesine e consigli utili
Togliti ogni dubbio leggendo le risposte alle domande fatte da altri studenti come te
Esplora i documenti più scaricati per gli argomenti di studio più popolari
Ottieni i punti per scaricare
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Algoritmica
Tipologia: Appunti
1 / 28
Questa pagina non è visibile nell’anteprima
Non perderti parti importanti!





















In offerta
Ricerca un elemento k in un insieme di n elementi memorizzati in un‟array A. Si scandisce l‟array confrontandone ogni elemento con quello ricercato. DA UTILIZZARE QUANDO L‟ARRAY NON E‟ ORDINATO.
precondizione: a è un array di n elementi, numeri interi senza segno.
Il programma scandisce tutto l‟array, anche se l‟elemento è stato trovato non termina perché non gli
viene data l‟istruzione. In questo modo, il programma fa comunque n confronti.
Modifichiamo il programma in modo che faccia solo i confronti necessari usiamo un ciclo
while****.
Quanti confronti fa questa procedura al caso peggiore?
La ricerca sequenziale ha una complessità lineare : al caso pessimo il numero di confronti è n.
ESERCIZI – Costruire l‟algoritmo per la ricerca sequenziale di k nell‟ipotesi che ce ne siamo più copie e vogliamo stampare il valore dell‟indice per ognuna delle copie. Se non trova niente, stampare -1.
Qual è il minimo lavoro che posso fare per determinare se k appartiene all’insieme?
(Procediamo come per il problema delle 12 monete).
Quante possibilità ci sono? n + 1 n + la possibilità che k non appartenga all‟insieme.
Assumiamo che l‟array contenga elementi tutti distinti tra loro. Confrontiamo gli elementi a coppie.
Dopo 1 confronto posso discriminare tra 2 soluzioni (=possibilità).
Dopo 2 confronti posso discriminare tra 4 (2^2 ) soluzioni.
Dopo 3 confronti posso discriminare tra 8 (2^3 ) soluzioni.
In questo caso 2i^ ≥ n +1 dopo i confronti, discrimino tra 2i^ situazioni.
𝟐𝒊^ ≥ 𝒏 + 𝟏 → = 𝐥𝐨𝐠𝟐 𝟐𝒊^ ≥ 𝐥𝐨𝐠𝟐(𝒏 + 𝟏) → 𝒊 ≥ 𝐥𝐨𝐠𝟐( 𝒏 + 𝟏)
Il numero minimo di confronti per trovare k in un‟array di n elementi è log 2 (n + 1).
Al caso ottimo O(log 2 n).
O meglio:
i ·1 ( i volte 1)
𝑛,
N.B. Dopo i + 1 volte si ottiene 1 (n = 2i^ n/2i^ = 1).
𝒏 𝟐𝒊^
Nel caso pessimo, i passi da fare sono log 2 n
Per n elementi, i confronti necessari al caso pessimo sono:
n 2 10 100 1000 10000 1000000 log 2 n 1 ~4 ~6 ~9 ~13 ~
Con circa 24 confronti si può cercare un elemento tra 1.000.000 di dati.
Formula con n elementi, servono i confronti confronti = log 2 elementi
Attenzione! Ricerca Binaria si può utilizzare solo se la sequenza di elementi è ordinata. In caso contrario, si usa Ricerca Sequenziale.
ESERCIZI - Modificare l‟algoritmo affinché restituisca l‟indice della posizione dell‟elemento trovato.
Ricerca Binaria(a, k): sinistra = 0; destra = n - 1; while (sinistra <= destra) { centro = (sinistra + destra)/2; if (a[centro] == k) { return centro; } else if (a[centro] > k) { destra = centro - 1; } else { sinistra = centro + 1; } } return -1;
Un algoritmo è ricorsivo quando è definito in termini di se stesso. Perché è utile la ricorsione? o Permette di scrivere programmi compatti. o Può migliorare la velocità di esecuzione.
Esempio il fattoriale si calcola in maniera ricorsiva:
𝑁! = 𝑁 𝑁 − 1 𝑁 − 2 𝑁 − 3 … 1 (*)
Abbiamo scritto un algoritmo iterativo; come visto, il fattoriale si calcola ricorsivamente, quindi scriviamo un algoritmo ricorsivo.
Simulazione :
Qual è l’algoritmo migliore tra Ricerca Binaria e RRICBIN?
Da un lato Ricerca Binaria è migliore perché le operazioni ricorsive hanno un costo maggiore (serve più
memoria, i parametri e le variabili devono essere memorizzati).
Dall‟altro, l’algoritmo ricorsivo in genere è migliore quando la natura stessa del problema è
ricorsiva.
In questo caso specifico, si dimostra che il numero di confronti da effettuare è identico.
Dimostrazione - Complessità di RRICBIN ricavata con le equazioni di ricorrenza
Per il problema della ricerca binaria, il numero di confronti da effettuare rimaneva identico, sia utilizzando la ricorsione, sia no. In altri casi, l’algoritmo ricorsivo è peggiore a causa della complessità. Un esempio è l‟algoritmo che ricava i numeri di Fibonacci.
1. RFIB(n): 2. if (n == 0) { 3. return 0; 4. } 5. if (n == 1) { 6. return 1; 7. } 8. else { 9. return (RFIB(n - 1) + RFIB(n - 2)); 10. }
Questo è un uso disastroso della ricorsività si calcola sempre la stessa cosa!
Il tempo di esecuzione è dato da queste equazioni:
𝑇 0 = 𝑇 1 = 𝑐 (𝑐𝑜𝑠𝑡𝑎𝑛𝑡𝑒) 𝑇 𝑛 = 𝑇 𝑛 − 1 + 𝑇 𝑛 − 2 + 𝑐
Se si risolve la seconda equazione, si ottiene che
𝒏 𝟐 (^) < 𝑇 𝒏 < 𝟐𝒏
Quando la dimensione del problema (n) è esponente del tempo di computazione, la crescita del problema è esponenziale anche se la base è molto piccola!
Supponiamo di dover fare 2n^ operazioni e di svolgere una operazione al secondo. n 5 10 15 20 25 35 40 tempo 32 secondi 17 minuti 9 ore 12 giorni 1 anno 1089 anni 34865 anni
Per migliorare quest‟algoritmo possiamo operare in due modi: memorizzare i numeri di Fibonacci in un‟array; memorizzare come variabili solo gli ultimi due elementi calcolati.
Quante operazioni compie questa procedura? O(n) il ciclo si compie n -1 volte, le operazioni da svolgere sono 3 quindi circa 3n siamo passati da 2 n^ ad un numero proporzionale ad n, l‟algoritmo è molto migliore.
Simulazione con 4 dischi
Come visto, Ricerca Binaria [O(logn)] è molto più efficiente di Ricerca Sequenziale [O(n)]. Se l‟array è ordinato infatti, esso viene suddiviso confrontando k con l‟elemento centrale.
Questa è una tecnica usata molto spesso, chiamata divide et impera :
[Con Ricerca Binaria sono sufficienti i primi due passi]
Problemi correlati: ricerca del massimo in un insieme.
Algoritmo banale
Si scandisce l‟array confrontandone ogni elemento con il primo; se questo risulta maggiore, diventa il massimo. Confronti necessari: n – 1 per dimensione dell‟input: n.
Algoritmo divide et impera
Si divide l‟array in due sottoarray di n/2 elementi ciascuno. Si trova il massimo M1 del primo sottoarray ricorsivamente (o direttamente se n = 1) e si trova il massimo M2 del secondo sottoarray. Si combinano le soluzioni, trovando il massimo tra M1 e M2. Confronti necessari: n – 1.
N.B.: nella prima riga potremmo scrivere RMassimo(a, 0, n -1) ma questa istruzione sarebbe valida solo
all‟inizio; utilizziamo sx e dx perché è necessario che siano variabili.
La procedura continua a dividere l‟array finché arriva ad ottenere sottoinsiemi di singoli elementi.
Se abbiamo l‟array
2 8 3 10 6 5 7 9 0 1 2 3 4 5 6 7
La soluzione di un problema di 8 elementi si rimanda alla soluzione di due sottoproblemi di 4 elementi.
La procedura continua a dividere l‟array finchè non ottiene elementi singoli di cui può restituire il risultato.
Nella fase TOP-DOWN si divide il problema in sottoproblemi più piccoli.
Nella fase BOTTOM-UP si fanno i confronti.
N.B. Avevamo 8 elementi , abbiamo fatto 7 (8 - 1) confronti.
Supponiamo di voler ottenere, senza simulazione, il numero di confronti. Un metodo è risolvere l‟ equazione di ricorrenza (scritta in termini di se stessa).
𝐶 𝑛 = 0 𝑠𝑒 𝑛 = 1 𝐶 𝑛 = 2𝐶
C(n) = numero di confronti fatti da RMassimo su n elementi.
N.B. si aggiunge 1 rappresenta il confronto finale.
Nell‟equazione per n > 1 si ha 2*C(n/2) perché RMassimo è chiamato ricorsivamente su n/2 due volte.
Simulazione per n = 2i
Utilizziamo la seconda equazione:
𝐶 𝑛 = 2𝐶
Possiamo riscrivere C(n/2) come:
𝐶
E quindi
𝐶 𝑛 = 1 + 2 1 + 2𝐶
Ma possiamo scrivere anche C(n/4) come:
𝐶
E ottenere così:
𝐶 𝑛 = 1 + 2 + 4 1 + 2𝐶 𝑛 8
che risolto è: 𝐶 𝑛 = 2^0 + 2^1 + 2^2 + 2^3 𝐶 2 𝑛 3
Continuiamo a dividere per 2, fino ad arrivare ad insiemi di un solo elemento (1 = n/2i, dove n = 2i):
𝐶 𝑛 = 2^0 + 2^1 + 2^2 + 2^3 + … + 2𝑖^ 𝐶 𝑛 2 𝑖
Ma 𝐶 2 𝑛𝑖 = 𝐶(1) perché n = 2i^
Dobbiamo svolgere le operazioni:
C(n) = 2i^ C(1) + 2i^ –^1 + 2i -2^ + … + 2^0.
Poiché per n = 1 C(n) = 0, si ha 2i·0 = 0 e quindi:
C(n) = 2i^ –^1 + 2i -2^ + … + 2^0.
Per una regola matematica 2^0 + 2^1 + 2^2 + … + 2i^ –^1 = 2i^ – 1
Sappiamo che n = 2i Quindi possiamo scrivere semplicemente:
C(n) = n – 1
Questo metodo consente di determinare la complessità di un algoritmo.
CONCLUSIONI - Abbiamo visto che sia Massimo che RMassimo necessitano di n – 1 confronti:
Quanti confronti fa questa procedura? (n -1) + (n - 2) + (n - 3) + …. + 1
E‟ il numero di confronti necessario all‟ordinare l‟array, considerando che l‟array si riduce ogni volta di un elemento. Ma quest‟espressione è uguale anche a:
Al crescere di n, il termine quadratico diventa molto più preponderante diciamo quindi che questa procedura è:
O(n^2 ) = numero di confronti da fare
N.B. questo numero rimane invariato, sia al caso pessimo che al caso ottimo si devono fare O(n^2 ) confronti!
usa il Divide et impera; algoritmo migliore per l‟ordinamento; usa un passo casuale meccanismo si prende l‟ultimo elemento del‟array (posizione fissa ma valore casuale); si confrontano tutti gli elementi dell‟array con questo elemento; quelli minori si metteranno a sinistra, quelli maggiori a destra. Si posiziona poi il perno (l‟ultimo elemento dell‟array) nella giusta posizione. Quindi, si suddivide l‟array in 2 parti: minori del perno e maggiori del perno. N.B. fatto ciò, gli elementi non sono ancora ordinati! Si usa ricorsivamente la stessa procedura:
La suddivisione dell‟array in due parti non è perfetta i sottoarray possono avere dimensioni diverse, e l‟algoritmo funziona meglio tanto più le partizioni sono bilanciate.
precondizione: per semplicità si suppone che l‟array abbia elementi tutti distinti tra loro
Note:
riga 1 all‟inizio l‟algoritmo lavorerà su (a, 0, n - 1);
riga 2 sx == dx condizione di terminazione.
riga 4 sceglie l‟ultimo elemento dell‟array; ci permette di sapere in che posizione è il perno rispetto agli altri elementi, dopo aver spostato i minori del perno a sinistra e i maggiori del perno a destra.
righe 5-6 per effettuare le chiamate ricorsive dobbiamo prima separare gli elementi < a[dx] dagli elementi > a[dx].
SIMULAZIONE
5 30 21 11 9 39 29 14 32 4 35 6 28 24
Si sceglie come perno a[dx] = 24; Si utilizzano due variabili: i e j; Lavoriamo con i : finché gli elementi sono < 24 non vanno spostati, gli altri sì. Iniziamo da i = 0 a[0] = 5 5 < 24? SI‟, non viene spostato. i = 1 a[1] = 30 30 < 24? NO, smetto di lavorare con i e continuo con j. j lavora in modo opposto, sposta gli elementi minori e lascia stare gli elementi maggiori. Iniziamo dall‟ultimo elemento prima del perno: 28 > 24? SI‟ non viene spostato. Continuiamo con 6 > 24? NO scambiamo il 6 con il 30.
5 6 21 11 9 39 29 14 32 4 35 30 28 24
Lavoriamo con i : 21, 11, 9 < 24? SI‟ non vengono spostati
Quanti confronti in totale verranno fatti dall’indice i e dall’indice j?
Il costo di distribuzione è O(n), costo simile al numero di confronti necessari gli indici proseguono senza mai tornare indietro.
Se le partizioni sono bilanciate, QuickSort funziona bene al caso pessimo preso il perno potremmo avere un sottoarray degenere e un sottoarray con tutti gli altri elementi. caso in cui l‟array era già stato ordinato.
𝑻 𝒏 = 𝟎 𝒑𝒆𝒓 𝒏 = 𝟏 𝑻 𝒏 = 𝑶 𝒏 + 𝑻 𝒅 + 𝑻 𝒏 − 𝒅 − 𝟏 𝒑𝒆𝒓 𝒏 > 1
Se n 0 confronti;
Se n > 1 T(n) = O(n) + T(d) + T(n – d -1). [gli ultimi due rappresentano il tempo sull‟insieme di dx e quello sull‟insieme di sx, il primo il costo di distribuzione].
Al caso pessimo T(n) = O(n) + T(n -1) (infatti avremo un insieme vuoto e uno di n – 1 elementi manca d che rappresenta una partizione degli elementi!)
Quanto vale?
T(n) = O(n) + T(n -1)
T(n -1) = (n – 1) + T(n – 2)
T(n) = n + (n – 1) + T(n -2)
T(n -2) = (n – 2) + T(n -3)
T(n) = n + (n – 1) + (n – 2) + T(n – 3)
…potrei continuare fino a trovare T(1).
T(n) = n + (n – 1) + (n – 2) + …..+ T(1).
Per la legge di Gauss:
e allora ha un costo stimabile in O(n^2 ).