Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -193,16 +193,17 @@ usando la stima $\log_2(n!) = \Omega(n \log n)$ già dimostrata sopra.

## 8. Il significato del risultato

> [!quote]
> "Il fatto che voi non riusciate a trovare un algoritmo migliore non significa che non esista. O c'è un'argomentazione sufficientemente astratta che vi permette di quantificare su tutti i possibili modi di risolvere, oppure non avete modo di rispondere."
> [!tip] Parole del Professore
> > [!quote]
> > "Il fatto che voi non riusciate a trovare un algoritmo migliore non significa che non esista. O c'è un'argomentazione sufficientemente astratta che vi permette di quantificare su tutti i possibili modi di risolvere, oppure non avete modo di rispondere."

La tecnica usata — astrarsi dalla struttura sintattica degli algoritmi e ragionare sulle classi di equivalenza definite dall'albero di decisione — è un esempio di **approccio information-theoretic**: si ragiona sul numero di output distinti che l'algoritmo deve essere in grado di produrre e si deduce il numero minimo di confronti necessari per discriminarli tutti.

Questo è diverso dall'analisi sintattica dei singoli algoritmi. Due algoritmi con alberi di decisione identici sono equivalenti dal punto di vista della complessità, anche se scritti in modo diverso. La tecnica permette di affermare che **nessun** algoritmo comparison-based può fare asintoticamente meglio di $n \log n$, anche uno ancora non scoperto.

---

> [!summary] Punti chiave della lezione
> [!abstract] Punti chiave della lezione
> - Ogni algoritmo comparison-based per ordinamento ha un unico albero di decisione di ordine $n$: i percorsi nell'albero corrispondono alle esecuzioni.
> - L'altezza dell'albero = complessità caso peggiore; la lunghezza media dei percorsi = complessità caso medio.
> - Poiché ci sono $n!$ permutazioni distinte, l'albero ha almeno $n!$ foglie, e questo implica altezza $\geq \log_2(n!) = \Omega(n \log n)$.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ Il corso si divide in due macro-aree:
### 2. Progettazione degli algoritmi
Dato un problema, come si costruisce una soluzione algoritmica? Si seguono due principi fondamentali:

> [!definition] Decomposizione
> [!info] Decomposizione
> Scomporre il problema in **sottoproblemi più semplici**, risolverli separatamente e comporre le soluzioni.
> *Esempio già visto:* problema delle coppie → generazione + filtraggio/conteggio.

> [!definition] Riduzione
> [!info] Riduzione
> Ridurre un problema a un **problema già noto** e risolverlo sfruttando quella soluzione.
> *Esempio già visto:* ordinamento topologico → ridotto alla visita DFS su grafi.

Expand All @@ -58,7 +58,7 @@ Dato un problema, come si costruisce una soluzione algoritmica? Si seguono due p

Per analizzare matematicamente un algoritmo serve un **ponte** tra il mondo degli algoritmi e il mondo della matematica. Questo ponte è il modello RAM (*Random Access Machine*).

> [!definition] Modello RAM
> [!info] Modello RAM
> Astrazione di un calcolatore con:
> - **Memoria illimitata** di celle accessibili a tempo costante (accesso diretto)
> - Un insieme di **istruzioni elementari:** operazioni aritmetiche, lettura/scrittura in memoria
Expand All @@ -68,7 +68,7 @@ Per analizzare matematicamente un algoritmo serve un **ponte** tra il mondo degl
$$T_A : \mathbb{N} \rightarrow \mathbb{R}^+$$
Dove $n \in \mathbb{N}$ è la **dimensione dell'input** (es. numero di elementi di un array) e $T_A(n)$ è il numero di operazioni elementari necessarie.

> [!note] Perché $\mathbb{R}^+$ e non $\mathbb{N}$?
> [!info] Perché $\mathbb{R}^+$ e non $\mathbb{N}$?
> Il numero di operazioni è un naturale, ma le espressioni che useremo (es. con logaritmi) assumono valori reali. Lavorare in $\mathbb{R}$ ci dà accesso a strumenti più potenti: derivate, integrali, limiti — tutti strumenti di $\mathbb{R}$ non disponibili (o difficili) nel discreto.

> [!tip] Codifica dell'input
Expand Down Expand Up @@ -131,7 +131,7 @@ Far partire `j` da `i` invece che da `1`: si evitano i confronti inutili (tutti
4. return sam
```
$$T_3(n) = 6n + 4 \quad \Rightarrow \quad \Theta(n)$$
> [!success] Miglioramento asintotico
> [!tip] Miglioramento asintotico
> Da $\Theta($n^2$)$ a $\Theta(n)$: un ciclo annidato in meno produce un salto di classe di complessità.

### Algoritmo 4 — Soluzione a tempo costante (geometrica)
Expand All @@ -142,7 +142,7 @@ $$\text{risultato} = \frac{n^2 - n}{2} + n = \frac{n(n+1)}{2}$$
1. return n * (n + 1) / 2
```
$$T_4(n) = O(1)$$
> [!success] Tempo costante
> [!tip] Tempo costante
> Nessun ciclo: la risposta si calcola in un numero fisso di operazioni indipendentemente da $n$.

---
Expand All @@ -158,7 +158,7 @@ $$T_4(n) = O(1)$$

Confrontare due funzioni punto per punto non è sempre possibile (potrebbero incrociarsi). Ci interessa il comportamento **al crescere illimitato di $n$**, cioè il regime asintotico.

> [!important] Intuizione fondamentale
> [!info] Intuizione fondamentale
> Un algoritmo è **intrinsecamente** migliore di un altro se lo è **indipendentemente dall'hardware** su cui gira. Un computer può essere $c$ volte più veloce di un altro, ma questo fattore è **costante** e non dipende da $n$. Due algoritmi sono equivalenti se la differenza tra loro è solo una costante moltiplicativa fissa.

### Le tre notazioni
Expand All @@ -173,7 +173,7 @@ Confrontare due funzioni punto per punto non è sempre possibile (potrebbero inc

### $O$ grande — Limite superiore asintotico

> [!definition] O grande
> [!info] O grande
> $$f(n) = O(g(n)) \iff \exists\, c > 0,\ \exists\, n_0 > 0 : \forall n \geq n_0,\quad f(n) \leq c \cdot g(n)$$
**Interpretazione:** $c$ è il fattore con cui posso *rallentare* l'esecutore di $g$ per renderlo peggio di $f$. Se esiste tale $c$ fissata a priori, allora $f$ non è peggio di $g$.

Expand All @@ -186,15 +186,15 @@ Confrontare due funzioni punto per punto non è sempre possibile (potrebbero inc

### $\Omega$ grande — Limite inferiore asintotico

> [!definition] Omega grande
> [!info] Omega grande
> $$f(n) = \Omega(g(n)) \iff \exists\, c > 0,\ \exists\, n_0 > 0 : \forall n \geq n_0,\quad f(n) \geq c \cdot g(n)$$
Duale di $O$ grande: $c$ è ora il fattore con cui *accelero* l'esecutore di $g$.

---

### $\Theta$ (Theta) — Equivalenza asintotica

> [!definition] Theta
> [!info] Theta
> $$f(n) = \Theta(g(n)) \iff \exists\, c_1, c_2 > 0,\ \exists\, n_0 > 0 : \forall n \geq n_0,\quad c_1 \cdot g(n) \leq f(n) \leq c_2 \cdot g(n)$$
Equivale a: $f = O(g)$ **e** $f = \Omega(g)$.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Da questo punto in poi, sia la condizione $O$ che quella $\Omega$ sono soddisfat
│ solo O vale │ O e Ω valgono entrambi
```

> [!note]
> [!info]
> Se $n_{0,2} > n_{0,1}$, c'è un intervallo $[n_{0,1}, n_{0,2})$ in cui vale solo $O$ ma non $\Omega$. Prendendo il massimo, si elimina questo problema.

---
Expand Down Expand Up @@ -110,7 +110,7 @@ flowchart LR
style Ck fill:#fff3cd
```

> [!note] Perché funziona quando $L = k$ (costante)
> [!info] Perché funziona quando $L = k$ (costante)
> Se $h$ tende a $k$ da sopra o da sotto, le oscillazioni diventano irrilevanti rispetto a costanti opportunamente scelte. Si trovano $c_1 < k < c_2$ tali che, da un certo $n_0$ in poi, $c_1 \leq h(n) \leq c_2$, ovvero $c_1 \cdot g(n) \leq f(n) \leq c_2 \cdot g(n)$ → definizione di $\Theta$.

> [!tip] Quando usare De L'Hôpital
Expand All @@ -126,7 +126,7 @@ Vogliamo confrontare $f(n) = 4n^2 + 10n$ con $g(n) = n^2$.
$$L = \lim_{n \to \infty} \frac{4n^2 + 10n}{n^2} = \lim_{n \to \infty} \left(4 + \frac{10}{n}\right) = 4$$
$L$ è una costante positiva finita → $f(n) = \Theta($n^2$)$.

> [!note] Proprietà generale sui polinomi
> [!info] Proprietà generale sui polinomi
> Per qualsiasi polinomio $f(n)$ di grado $d$ con coefficiente principale positivo:
> $$f(n) = \Theta(n^d)$$
> La dimostrazione è una generalizzazione diretta: tutti i termini di grado inferiore producono termini $\to 0$ nel rapporto con $n^d$.
Expand Down Expand Up @@ -186,7 +186,7 @@ Per $n \geq n_3$:
$$f(n) \leq c_1 \cdot g(n) \leq c_1 \cdot c_2 \cdot h(n)$$
Scelgo $c_3 = c_1 \cdot c_2 > 0$ (prodotto di positivi). Ho dimostrato $f = O(h)$. $\square$

> [!note] Stessa dimostrazione per $\Omega$ e $\Theta$
> [!info] Stessa dimostrazione per $\Omega$ e $\Theta$
> Il ragionamento è identico per $\Omega$. Per $\Theta$, si combina transitività di $O$ e $\Omega$.

---
Expand Down Expand Up @@ -215,10 +215,11 @@ Ora $\log c_1$ e $\log c_2$ sono **costanti** (anche potenzialmente negative, ma
$$\frac{\log g(n) + \log c_1}{\log g(n)} \leq \frac{\log f(n)}{\log g(n)} \leq \frac{\log g(n) + \log c_2}{\log g(n)}$$
I due lati tendono entrambi a 1 (poiché $\log g(n) \to \infty$ e le costanti sommative diventano irrilevanti) → per il metodo dei limiti, il rapporto tende a una costante positiva → $\Theta$.

> [!quote]
> *"Il punto chiave è questo: una costante moltiplicativa, prendendo il logaritmo, diventa una costante additiva, e le costanti additive sono irrilevanti rispetto a funzioni che crescono all'infinito. Con l'esponenziale è il contrario: una costante moltiplicativa nell'esponente diventa un fattore moltiplicativo esponenziale, e quello non è più trascurabile."*
> [!tip] Parole del Professore
> > [!quote]
> > *"Il punto chiave è questo: una costante moltiplicativa, prendendo il logaritmo, diventa una costante additiva, e le costanti additive sono irrilevanti rispetto a funzioni che crescono all'infinito. Con l'esponenziale è il contrario: una costante moltiplicativa nell'esponente diventa un fattore moltiplicativo esponenziale, e quello non è più trascurabile."*

> [!note] Generalizzazione: torri di esponenziali
> [!info] Generalizzazione: torri di esponenziali
> Il logaritmo "taglia" un solo livello di esponenziale. Per confrontare $2^{2^n}$ e $2^{4^n}$ bisognerebbe applicare il logaritmo due volte ($\log \log$). La torre degli esponenziali ha come inversa la torre dei logaritmi.

---
Expand All @@ -245,7 +246,7 @@ I due lati tendono entrambi a 1 (poiché $\log g(n) \to \infty$ e le costanti so
>
> Senza il vincolo di contiguità, basterebbe sommare tutti i valori non negativi. **Il vincolo di contiguità rende il problema non banale.**

> [!note] Gestione del caso "tutti negativi"
> [!info] Gestione del caso "tutti negativi"
> Se tutti i valori sono negativi, la sottosequenza ottimale è quella **vuota**, con somma 0. Per questo l'algoritmo parte con $\text{somma\_massima} = 0$ — se non trova niente di meglio, la risposta sarà 0.

### Quante sono le sottosequenze contigue?
Expand Down Expand Up @@ -291,24 +292,25 @@ $$\boxed{T(n) = \Theta(n^3)}$$

## Anticipazione: verso l'algoritmo quadratico

> [!note] *(Verrà approfondito nella prossima lezione)*
> [!info] *(Verrà approfondito nella prossima lezione)*

Il ciclo interno fa **lavoro inutile**: ogni volta che $j$ avanza di 1, ricalcola da zero l'intera somma $\sum_{k=i}^{j} A[k]$, che era già stata calcolata al passo precedente!

**Osservazione chiave:** se conosco $S([i, j])$, allora:
$$S([i, j+1]) = S([i, j]) + A[j+1]$$
cioè posso estendere la sottosequenza a destra in **tempo costante**, senza ricalcolare tutto. Questo elimina il ciclo `k`, abbassando la complessità da $O($n^3$)$ a $O($n^2$)$.

> [!note] *(Verrà approfondito nelle lezioni successive)*
> [!info] *(Verrà approfondito nelle lezioni successive)*

Abbassare ulteriormente da $O($n^2$)$ a $O(n)$ richiede di **non analizzare tutte le sottosequenze**, scartandone alcune con la garanzia formale che non possono contenere la soluzione ottima.

> [!quote]
> *"Il numero di sottosequenze contigue è quadratico, quindi l'unico modo di abbassare rispetto al quadratico è non analizzare tutte le sottosequenze — devo avere un modo di scartarle con la certezza di non perdere quella buona."*
> [!tip] Parole del Professore
> > [!quote]
> > *"Il numero di sottosequenze contigue è quadratico, quindi l'unico modo di abbassare rispetto al quadratico è non analizzare tutte le sottosequenze — devo avere un modo di scartarle con la certezza di non perdere quella buona."*

---

> [!summary] Punti chiave della lezione
> [!abstract] Punti chiave della lezione
> 1. **$\Theta = O \cap \Omega$**: la dimostrazione del verso non banale richiede di scegliere $n_0 = \max(n_{0,1}, n_{0,2})$ per garantire che entrambe le condizioni valgano contemporaneamente.
> 2. **Transitività**: $O$, $\Omega$ e $\Theta$ sono transitive; la costante si ottiene come prodotto $c_3 = c_1 \cdot c_2$.
> 3. **Metodo dei limiti**: $L=0 \Rightarrow o$; $L=\infty \Rightarrow \omega$; $L=k>0 \Rightarrow \Theta$. Utile De L'Hôpital per forme indeterminate.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ $$T_{\text{worst}}(n) = \Theta(n^2)$$

---

> [!summary] Punti chiave della lezione
> [!abstract] Punti chiave della lezione
> 1. **Da $\Theta(n^3)$ a $\Theta(n^2)$**: eliminando il ciclo interno e mantenendo la somma incrementale tra iterazioni successive di $j$, si riduce il costo di valutazione di ogni sottosequenza da $O(n)$ a $O(1)$.
> 2. **Da $\Theta(n^2)$ a $\Theta(n)$ (Kadane)**: dimostrando che ogni sottosequenza contenente un prefisso di somma negativa e subottimale, si riduce lo spazio di ricerca da $\Theta(n^2)$ a $O(n)$ sottosequenze. L'algoritmo e **ottimo** (limite inferiore $\Omega(n)$).
> 3. **Due strategie di ottimizzazione**: (a) ridurre il costo per soluzione, (b) ridurre il numero di soluzioni esaminate. La seconda e piu potente ma richiede una dimostrazione di correttezza.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ L'approccio usato per Merge Sort si generalizza a qualsiasi algoritmo ricorsivo:

---

> [!summary] Punti chiave della lezione
> [!abstract] Punti chiave della lezione
> 1. **Insertion Sort --- caso migliore**: $t_j = 1$ per ogni $j$ (array ordinato) $\Rightarrow$ $T(n) = \Theta(n)$.
> 2. **Insertion Sort --- caso peggiore**: $t_j = j$ per ogni $j$ (array ordinato al contrario) $\Rightarrow$ $T(n) = \Theta(n^2)$.
> 3. **Insertion Sort --- caso medio**: il valore atteso $E[t_j] = \frac{j+1}{2}$ porta a $T(n) = \Theta(n^2)$. La conferma arriva anche dall'analisi delle inversioni: il numero medio di inversioni e $\frac{n(n-1)}{4} = \Theta(n^2)$.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ $$T(n) = \sum_{i=2}^{n} T_{\text{findmax}}(i) = \sum_{i=2}^{n} \Theta(i) = \Thet

Il problema di Selection Sort è che ogni chiamata a `find_max` parte da zero, dimenticando tutto quello che ha visto nelle chiamate precedenti. Eppure, durante la ricerca del massimo, l'algoritmo fa confronti e acquisisce informazioni sulle relazioni di ordine tra elementi — che vengono poi dimenticate.

> [!quote]
> "Se il nostro algoritmo ricordasse le cose che ha già visto, potrebbe evitare di fare confronti inutili nelle ricerche successive. La prima ricerca del massimo sarà sempre $\Theta(n)$ — non c'è modo di evitarlo su input arbitrario. Ma le ricerche successive potrebbero costare molto meno, se disponessimo dell'informazione accumulata in precedenza."
> [!tip] Parole del Professore
> > [!quote]
> > "Se il nostro algoritmo ricordasse le cose che ha già visto, potrebbe evitare di fare confronti inutili nelle ricerche successive. La prima ricerca del massimo sarà sempre $\Theta(n)$ — non c'è modo di evitarlo su input arbitrario. Ma le ricerche successive potrebbero costare molto meno, se disponessimo dell'informazione accumulata in precedenza."

L'idea è mantenere una struttura dati che rappresenta un **ordinamento parziale** tra gli elementi: non sappiamo l'ordinamento totale, ma conosciamo alcune relazioni tra coppie. Questa struttura ha naturalmente forma di albero.

Expand Down Expand Up @@ -134,7 +135,7 @@ Da questa corrispondenza si ricavano le funzioni di navigazione dell'albero (con

$$\text{FiglioSinistro}(i) = 2i \qquad \text{FiglioDestra}(i) = 2i + 1 \qquad \text{Padre}(i) = \lfloor i/2 \rfloor$$

> [!important] Proprietà strutturali dell'array
> [!info] Proprietà strutturali dell'array
> - I **nodi interni** (quelli con almeno un figlio) occupano le posizioni $1$ a $\lfloor n/2 \rfloor$.
> - Le **foglie** occupano le posizioni $\lfloor n/2 \rfloor + 1$ a $n$.
> - Ogni array di lunghezza $n$ corrisponde implicitamente a un albero binario completo.
Expand Down Expand Up @@ -265,7 +266,7 @@ flowchart LR

Totale: $T(n) = \Theta(n) + O(n \log n) = O(n \log n)$

> [!important] Il costo è esattamente $\Theta(n \log n)$, non solo $O(n \log n)$
> [!info] Il costo è esattamente $\Theta(n \log n)$, non solo $O(n \log n)$
> Il limite inferiore $\Omega(n \log n)$ deriva dal **Teorema degli Alberi di Decisione** (che verrà dimostrato più avanti): nessun algoritmo di ordinamento basato su confronti può fare meglio di $\Omega(n \log n)$. Quindi HeapSort è asintoticamente ottimale.

### Confronto finale
Expand All @@ -280,7 +281,7 @@ HeapSort combina i vantaggi di Merge Sort (complessità garantita $\Theta(n \log

---

> [!summary] Punti chiave della lezione
> [!abstract] Punti chiave della lezione
> - **Selection Sort** è il duale di Insertion Sort: seleziona le posizioni e cerca l'elemento. Ha sempre costo $\Theta(n^2)$ perché `find_max` è ottimale ma non ha memoria.
> - **HeapSort** nasce dall'idea di memorizzare le relazioni d'ordine viste durante la prima ricerca del massimo in una struttura dati (lo heap), evitando lavoro ridondante nelle ricerche successive.
> - Uno **heap** è un albero binario completo con la proprietà che ogni nodo è ≥ dei suoi figli. Il massimo è sempre in radice.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ Le stime superiori che avevamo dato erano: $O(n \log n)$ sia per `BuildHeap` che

`BuildHeap` applica `Heapify` a tutti i nodi interni, ma la **gran parte delle chiamate** avviene su nodi che sono radici di sottoalberi di altezza molto bassa. I nodi profondi (vicini alle foglie) sono molti, ma i loro sottoalberi sono piccoli. I nodi vicini alla radice (con sottoalberi alti) sono pochi.

> [!quote]
> "La stragrande maggioranza delle chiamate Heapify vengono fatte su heap in cui H è molto basso."
> [!tip] Parole del Professore
> > [!quote]
> > "La stragrande maggioranza delle chiamate Heapify vengono fatte su heap in cui H è molto basso."

### La struttura dell'analisi

Expand Down Expand Up @@ -245,12 +246,12 @@ $$T(n) = \begin{cases} \Theta(1) & \text{se } n \leq 1 \\ \Theta(n) + T(k) + T(n

dove $k$ dipende dall'input. L'analisi richiede quindi di distinguere **caso peggiore**, **caso migliore** e **caso medio**, analogamente a quanto fatto per InsertionSort.

> [!important] Implicazione
> [!info] Implicazione
> Il comportamento di QuickSort non dipende solo dalla dimensione dell'input, ma dai suoi **valori**. Se il pivot è sempre il minimo o il massimo, la partizione sarà sempre sbilanciata (1 elemento da un lato, $n-1$ dall'altro), degradando a $\Theta(n^2)$. La prossima lezione tratterà la soluzione di questa equazione di ricorrenza.

---

> [!summary] Punti chiave della lezione
> [!abstract] Punti chiave della lezione
> - `BuildHeap` ha costo $\Theta(n)$, non $\Theta(n \log n)$: la maggior parte delle chiamate `Heapify` avviene su sottoalberi molto bassi.
> - `HeapSort` ha costo $\Theta(n \log n)$: la stima era stretta perché le prime $n/2$ chiamate costano ciascuna $\log n$.
> - QuickSort evita la fusione garantendo che ogni elemento della partizione sinistra sia $\leq$ di ogni elemento della destra.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,13 @@ Per il Teorema Master (caso 2): $T(n) = \Theta(n \log n)$.

**Caso medio**: in media su tutti i possibili input, il tempo atteso è $\Theta(n \log n)$. Questo verrà dimostrato nelle lezioni successive.

> [!quote]
> "Dovremo fare analisi di caso migliore, caso peggiore e eventualmente caso medio, analogamente a quanto fatto con InsertionSort."
> [!tip] Parole del Professore
> > [!quote]
> > "Dovremo fare analisi di caso migliore, caso peggiore e eventualmente caso medio, analogamente a quanto fatto con InsertionSort."

---

> [!summary] Punti chiave della lezione
> [!abstract] Punti chiave della lezione
> - `Partition` termina in $\Theta(n)$: la somma degli incrementi di $i$ e dei decrementi di $j$ è al più $n+1$.
> - Si restituisce $j$ perché si ferma su un elemento $\leq x$ (corretto per la partizione sinistra); $i$ si ferma su un elemento $\geq x$ che potrebbe essere troppo grande.
> - Fermarsi sugli uguali è fondamentale per garantire R2 su array con elementi duplicati.
Expand Down
Loading