






























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
asd merelli 2026 non completo, attenzione
Tipologia: Appunti
1 / 38
Questa pagina non è visibile nell’anteprima
Non perderti parti importanti!































Legenda: Rosso: Argomento principale Viola: Sottotema di primo livello Blu: Sottotema di secondo livello Bianco: Sotto tema di terzo livello Concetto di algoritmo Definizione: Un algoritmo è una procedura computazionale ben definita, questa viene eseguita per risolvere un dato, detto problema computazionale. Questo è composto anche da delle sequenze di passi computazionali, che prende dei valori come input e produce altri valori come output. In una descrizione formale si può dire che: L’algoritmo è un procedimento di calcolo esplicito con un numero di regole e passi finiti. Valutazione di un Algoritmo: nel mondo degli algoritmi e strutture dati, è essenziale valutare un algoritmo che possa essere in grado di risolvere correttamente ed efficacemente un problema: ALGORITMO CORRETTO: ovvero che per ogni istanza (input), deve saper risolvere questo problema con un output corretto. (concetto di decidibilità fondamenti) o Caso Base: verifica per input minimi o Passo induttivo: se funziona per input piccoli, funziona per input più grandi DIMOSTRAZIONI MATEMATICHE: parte dell’algoritmo che spiega come quest’ultimo stia seguendo il concetto di correttezza; Infatti, si affida a queste e le descrizioni informali. ALGORITMO EFFICIENETE: con efficienza si intende che si risolva un dato problema in un tempo finito e senza troppi sprechi di risorse Un esempio di algoritmo corretto, possono essere gli algoritmi di ordinamento, ma questi differiscono per efficienza. Nota: l’efficienza è strettamente collegata al problema dell’arresto, questo perché alcuni problemi non sono risolvibili. Classi di complessità: esistono dei casi, in cui l’algoritmo non può essere efficiente, questo perché ci possono essere due casi:
1. Cosa fa questo algoritmo: L’algoritmo ordina un array di elementi in ordine crescente. Prende una lista di numeri o valori confrontabili e li riordina dal più piccolo al più grande. 2. Problema che risolve: Risolve il problema dell’ordinamento di una sequenza di elementi. Trasforma un insieme di dati disordinato in una sequenza ordinata secondo un criterio di confronto (ad esempio dal minore al maggiore). 3. Procedimento di calcolo: Analizza l’array elemento per elemento, a partire dal secondo. Per ogni elemento A[j], lo considera come una chiave (key). Confronta key con gli elementi precedenti dell’array. Sposta in avanti tutti gli elementi più grandi della chiave. Inserisce la chiave nella posizione corretta, in modo che la parte sinistra dell’array risulti ordinata. In questo modo, costruisce progressivamente una parte ordinata dell’array. È il classico algoritmo chiamato Insertion Sort (ordinamento per inserimento).
passi. Nel peggior caso (array completamente inverso), ogni elemento deve essere confrontato con tutti i precedenti. Il numero totale di passi è quindi:
Quindi, per un array di 7 elementi, l’algoritmo compie 21 passi nel caso peggiore.
Analisi di Algoritmi Analizzare un algoritmo, sta a significare che si determinano quali sono le risorse necessarie in termini di:
processo
Questa analisi ci consente di ottenere varie informazioni tra cui: Stima del tempo impiegato Stima il tempo di esecuzione di input molto grandi in tempi ragionevoli Confrontare l’efficienza di due algoritmi Ottimizzare le parti critiche Da qui possiamo definire una funzione “T” che ci servirà per la analisi dei vari algoritmi, in termini specifici, questa mette in relazione la dimensione dell’istanza con il tempo, ovvero: T(n) = tempo impiegato dall’algoritmo quando l’istanza ha dimensione n
Per indicare il tempo che ci impiega un algoritmo, abbiamo la seguente funzione, dove: Con ni, intendiamo il numero di volte che il passo i-esimo viene eseguito Con ci, sarebbe il costo computazionale del passo i-esimo Con k, si intende il numero totale di passi dell’algoritmo T ( n )= (^) ∑ i = 1 ¿ ¿ k n (^) i c (^) i Esempio pratico del modello: Se un algoritmo ha 3 istruzioni con costi c₁, c₂, c₃ che vengono eseguite n, n² e 1 volte rispettivamente: T(n) = n·c₁ + n²·c₂ + 1·c₃ Operazione 1 (costo c₁ = 2 unità): Inizializzazione - eseguita n volte Operazione 2 (costo c₂ = 3 unità): Confronto tra elementi - eseguita n² volte
Problema dell’ordinamento Il problema dell’ordinamento consiste nell'organizzare gli elementi di un insieme secondo una determinata relazione d'ordine (totale), utilizzando algoritmi specifici come Bubble Sort, Selection Sort o Quick Sort. La complessità di questo problema varia da O(n^2 ) a O(n*log 2 n)
Dati n elementi sui quali si possa considerare un relazione d’ordine totale (ogni coppia di elementi è confrontabile) costruire una permutazione (a’1, a’2, a’3, ..., a’n), vale a dire trovare una disposizione degli elementi in modo tale che si abbia: a’ 1 <= a’ 2 <= a’n.
abbiamo delle istanze (31, 41, 59, 26, 41, 58), e dobbiamo ottenere un output ordinato in maniera crescente, quindi l’output deve essere (26, 31, 41, 41, 58, 59). Per risolvere questo problema, dobbiamo scegliere un algoritmo, la scelta dipende da tre fattori:
istanza iniziale: 31, 41, 59, 26, 41, 58 Step 1: Ordinati: [31] Da inserire: 41 Non ordinati: [59, 26, 41, 58] Risultato : [31, 41] Step 2: Ordinati: [31, 41] Da inserire: 59 Non ordinati: [26, 41, 58] Risultato : [31, 41, 59] Step 3: Ordinati: [31, 41, 59] Da inserire: 26 ← qui deve trovare la posizione corretta Non ordinati: [41, 58] Risultato : [26, 31, 41, 59]
o Il numero di confronti è pari a i − 1
per l’elemento i-esimo o L’elemento trova la sua posizione circa a metà della sequenza ordinata
Per un array di dimensione n, il numero totale di confronti è:
≈ n 2
≈ n 2
Notazioni Asintotiche
Se f(n) = O(g(n)) allora g(n) è un limite asintotico superiore per f(n), intendiamo che la funzione f(n) cresce come o meno di g(n) a meno di un fattore costante per valori di n sufficientemente grandi. Per questo, la notazione O(n) viene usata nell’analisi del costo computazionale nel caso pessimo. Graficamente:
Esempio pratico: l’algoritmo di ricerca in un array non ordinato nel caso peggiore, deve esaminare tutti gli n elementi, ovvero il suo limite asintotico superiore.
A livello formale, O(g(n)) non indica una sola funziona, ma rappresenta un insieme di funzioni. Questa è l’insieme di tutte le funzioni f(n) che soddisfano la seguente condizione: Esistono due costanti positive c e n 0 , tali che 0 <= f(n) <= c * g(n) per ogni n >= n 0. Di conseguenza la scrittura f(n) = O(g(n)) è tecnicamente un abuso di notazione, la forma matematica più corretta sarebbe f(n) ∈ O(g(n)), ma per convezione si utilizza l’uguale. Esempio matematico: Provare che f(n) = 3n^2 + 10n = O(n^2 ) Dobbiamo dimostrare l’esistenza di costanti positive c e n 0 tali che 0 <= f(n) < cn^2 per ogni n >= n 0 Per dimostrare questo possiamo scegliere c = 4 e n 0 = 1w0; infatti: 3n^2 + 10n <= cn^2 cn^2 – 3n^2 – 10n >= 0 Raccogli n^2 (c – 3)n^2 – 10n >= 0 e scegliamo c = 4 n^2 – 10n >= 0
La notazione Ω-grande definisce un limite inferiore asintotico , una sorta di "pavimento" per la crescita della funzione. L'algoritmo non potrà mai essere più veloce di così, per input sufficientemente grandi, questa viene usata quando dobbiamo descrivere la complessità di un algoritmo nel caso migliore (ottimo). Questo indica che vengono usate le risorse minime necessarie per risolvere il problema. Esempio pratico: Qualsiasi algoritmo che deve ordinare un array deve, come minimo, leggere tutti gli n elementi. Pertanto, la sua complessità sarà sempre Ω(n). Ω(g(n)), rappresenta l’insieme di funzioni definito come (g (n)) = {f (n) | esistono delle costanti positive c e n0 tali che 0 ≤ cg (n) ≤ f (n) Esempio matematico: Dimostrare che 3n² + 10n = Ω (n²). Per fare questo dobbiamo dimostrare l’esistenza di costanti positive c e n 0 tali che 0 <= cn² <= f(n) per ogni n >= n 0 Per dimostrare tale esistenza, possiamo prendere come valore di c il coefficiente del termine dominante di f(n), ovvero c = 3, mentre n 0 lo possiamo prendere come 0. Se facciamo la diseguaglianza troviamo che: 3n^2 <= 3n^2 + 10n 0 <= (3n^2 - 3n^2 ) + 10n Quest'ultima affermazione è vera per tutti gli n ≥ 0. Pertanto, possiamo scegliere n₀ = 0. Poiché abbiamo trovato una coppia di costanti valide (c = 3, n₀ = 0), la dimostrazione è completa.
NB: ricorda che f (n) = Θ(g (n)) è un “abuso” di notazione
Se f(n) = Θ(g(n)) allora g(n) è un limite asintotico stretto, per f(n) : f(n), a meno di un fattore costate, questa cresce esattamente come g(n), infatti Θ-grande va a descrivere la crescita esatta della funzione. Θ(g(n)) = { f(n) | esistono delle costanti positive c 1 , c 2 e n 0 tali che 0 ≤ c 1 g(n ≤ f(n) ≤ c 2 g(n) } Graficamente:
Per ogni coppia di funzioni f(n) e g(n) si ha che: f(n) =Θ(g(n) ), se e soltanto se f(n) = O( g(n) ) e f(n) = Ω( g(n) ) Esempio matematico: f(n) = 3n² + 10n = Θ(n²) Abbiamo dimostrato che f (n) = O(n^2 ) e f (n) = Ω(n^2 ), questo basta per concludere che f (n) = Θ(n^2 ).
Siano f(n) e g(n) due funzioni non negative; f(n) = ω(g(n)) se per ogni costante positiva c esiste una costante n0 > 0 tale che 0 ≤ cg(n) < f(n) per ogni n ≥ n 0 Intuitivamente, nella notazione ω, la funzione f(n) diventa arbitrariamente grande rispetto a g(n) quando n tende all’infinito Diciamo che f(n) domina g(n):
n → ∞ f ( n ) g ( n ) = ∞
Per la situazione Fibonacci, invece la ricorrenza del tempo di esecuzione è T(n) = T(n – 1) + T(n – 2), specchio della sua definizione matematica, questo porta a una complessità esponenziale O(2n), rendendolo molto inefficiente perché ricalcola più volte gli stessi valori. Metodi per Risolvere le ricorrenze: per risolvere una ricorrenza significa trovare una formula chiusa (non ricorsiva) come O(n log n), per descrivere il comportamento dell’algoritmo. Per risolvere una cosa del genere, possiamo utilizzare 3 metodi principali, ossia il metodo iterativo, il metodo della sostituzione e master theorem. Metodo Iterativo: questo, consiste nello srotolare, la ricorrenza più volte fino a trasformarla in una sommatoria, che può poi essere calcolata; Il metodo iterativo è una tecnica anche visuale per organizzare questa somma. Metodo degli Alberi di Ricorsione: Questo metodo è una tecnica visuale, spesso usata per supportare il metodo iterativo, che aiuta a comprendere il costo di un algoritmo ricorsivo. Consiste nel disegnare un albero dove ogni nodo rappresenta il costo di un singolo sottoproblema all'interno delle chiamate ricorsive. L'obiettivo è calcolare il costo totale dell'algoritmo sommando i costi di tutti i nodi, solitamente aggregandoli per livello. Esempio: T(n) = 2T(n/2) + n Analizziamo una ricorrenza comune, T(n) = 2T(n/2) + n (simile a quella del Merge Sort), assumendo T(1) = 1.
Costo Livello i: 2^i nodi × n/2^i = n Notiamo che il costo di ogni livello è costante e pari a n.
Dove delta è una costante positiva, che misura di quanto f(n) è più lenta o più veloce rispetto al temine n(logb a) Strutture dati Le strutture dati, le definiamo con due concetti molto importati:
definiamo una struttura dati astratta come l’organizzazione di un insieme di oggetti tale da poter essere rappresentata con strutture dati a sostegno delle operazioni che permetteranno la gestione degli oggetti in modo efficiente; queste vanno a specificare esattamente quello che voglio e non viene fatta l’effettiva implementazione. Esempio: La List ADT (Abstract Data Type) è una collezione sequenziale si elementi che supportano dei set di operazioni senza specificare le implementazioni interne. Per restituire una via allo stoccaggio ordinato, lo stoccaggio, l’accesso a questo e la modifica dei dati. Operazioni: L'ADT List deve memorizzare i dati richiesti nella sequenza e deve avere le seguenti operazioni: get(): restituisce un elemento dalla lista in una posizione qualsiasi. insert(): inserisce un elemento in una posizione qualsiasi della lista. remove(): rimuove la prima occorrenza di un elemento da una lista non vuota. removeAt(): rimuove l'elemento in una posizione specificata da una lista non vuota.
replace(): sostituisce un elemento in una posizione qualsiasi con un altro elemento. 4size(): restituisce il numero di elementi nella lista. isEmpty(): restituisce true se la lista è vuota; altrimenti, restituisce false. isFull(): restituisce true se la lista è piena, altrimenti, restituisce false. Applicabile solo in implementazioni a dimensione fissa (ad esempio, liste basate su array). Altri esempi La Pila: Insieme di elementi gestito secondo una politica LIF