-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
400 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# Altri tipi | ||
|
||
Tra gli [alberi binari di ricerca](../04/README.md) ci sono anche altri tipi come: | ||
- **Alberi AVL** | ||
|
||
Sono alberi [**bilanciati**](../README.md#alberi-k-ari) i cui nodi contengono un **fattore di bilanciamento** che è minore o uguale ad $1$ per ogni nodo, e rappresenta la differenza dell'altezza del sottoalbero sinistro con quella del destro. | ||
|
||
L'_inserimento_ e _cancellazione_ sono più complesse dato che bisogna mantenere l'albero bilanciato. | ||
|
||
- **B-Alberi** | ||
|
||
Sono alberi **bilanciati** che hanno **almeno** due figli, dove: | ||
- Tutte le foglie hanno la stessa profondità | ||
- Ogni nodo $v$ (_radice_ esclusa) contiene $\mathrm{grado}(v)-1 \leq K(v) \leq 2\mathrm{grado}(v)-1$ chiavi **ordinate** | ||
- La radice $r$ contiene $1 \leq K(r) \leq 2\mathrm{grado}(r)-1$ chiavi **ordinate** | ||
- Ogni nodo interno $v$ ha $K(v)+1$ figli | ||
- Le chiavi separano gli intervalli delle chiavi nei sottoalberi | ||
|
||
Per esempio, | ||
```dot process | ||
graph { | ||
node [shape=record] | ||
0 [label="46"] | ||
1 [label="27 | 37"] | ||
2 [label="66 | 79"] | ||
3 [label="10 | 15 | 25"] | ||
4 [label="30 | 35"] | ||
5 [label="40 | 45"] | ||
6 [label="50 | 55 | 65"] | ||
7 [label="68 | 74"] | ||
8 [label="80 | 99"] | ||
|
||
0 -- 1, 2 | ||
1 -- 3, 4, 5 | ||
2 -- 6, 7, 8 | ||
} | ||
``` | ||
|
||
- **Alberi rossi e neri** | ||
|
||
Contengono oltre alla chiave il **colore** del nodo, che può essere **rosso** o **nero**. | ||
|
||
L'albero viene vincolato in base al _colore_ in un modo che garantisce che l'albero sia **bilanciato**. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# Insertion sort | ||
|
||
L'**insertion sort** consiste nell'estendere una parte di $k$ elementi ordinati al $(k+1)$-esimo elemento. | ||
|
||
```c | ||
insertion(Array A) | ||
for j = 2 to A.length | ||
key = A[j] | ||
i = j - 1 | ||
while i >= 1 and key < A[i] | ||
A[i + 1] = A[i] | ||
i = i - 1 | ||
A[i + 1] = key | ||
``` | ||
L'algoritmo è **corretto** per la sua [invariante](../../01/02/README.md#analisi-della-correttezza): | ||
> L'array `A[1, ..., j-1]` contiene gli elementi **ordinati** che originariamente erano in `A[1, ..., j-1]` | ||
e quando il `for` termina `j = A.length+1` quindi l'_invariante_ vale per `A[1, ..., n+1-1]`, cioè l'intero array. | ||
Nel caso **migliore** è $\Theta(n)$ e nel **peggiore** $\Theta(n^2)$ perchè il `for` itera $n - 1$ volte ed il numero di confronti è: | ||
$$ | ||
\left(\sum_{j = 2}^n j - 1\right) = \sum_{k = 1}^{n - 1} k = \frac{n(n - 1)}{2} = \Theta(n^2) | ||
$$ | ||
L'algoritmo è **stabile** ed **in loco**. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# Merge sort | ||
|
||
Il **merge sort** divide a metà l'array e riordina ricorsivamente le due parti per poi riunirle. | ||
|
||
```c | ||
mergesort(Array A, int p, int r) | ||
if p < r | ||
med = (p + r)/2 | ||
mergesort(A, p, med) | ||
mergesort(A, med+1, r) | ||
merge(A, p, med, r) | ||
|
||
merge(Array A, int p, int med, int r) | ||
L = [] | ||
left_len = med - p + 1 | ||
for i = 1 to left_len | ||
push(L, A[p + i - 1]) | ||
|
||
R = [] | ||
right_len = r - med | ||
for i = 1 to right_len | ||
push(R, A[q + j]) | ||
|
||
push(L, Infinity) | ||
push(R, Infinity) | ||
|
||
i = 1, j = 1 | ||
for k = p to r | ||
if L[i] <= R[j] | ||
A[k] = L[i] | ||
i++ | ||
else | ||
A[k] = R[j] | ||
j++ | ||
``` | ||
L'algoritmo è **corretto** per l'[invariante](../../01/02/README.md#analisi-della-correttezza) dell'ultimo `for`: | ||
> In `A[p, ..., k-1]` sono **ordinati** elementi che, come `L[i]` e `R[j]`, sono minori del resto di `L` e `R` | ||
e quando il `for` termina `k = r + 1` quindi l'_invariante_ vale per `A[p, ..., r+1-1]` cioè l'intero array. | ||
La complessità di `merge` si può ricavare, sapendo che $n = r - p + 1$, da: | ||
$$ | ||
\begin{split} | ||
T(n) &= \Theta(\texttt{left\_len}) + \Theta(\texttt{right\_len}) + \Theta(r - p + 1) = \\ | ||
&= \Theta(\texttt{left\_len} + \texttt{right\_len}) + \Theta(n) = \\ | ||
&= \Theta(\texttt{med} - p + 1 + r - \texttt{med}) + \Theta(n) = \\ | ||
&= \Theta(r - p + 1) + \Theta(n) = \Theta(n) | ||
\end{split} | ||
$$ | ||
mentre quella di `mergesort` è $T(n) = 2T(\frac{n}{2}) + \Theta(n) = \Theta(n \log n)$ per il [teorema master](../../../ct0371-1/01/03/README.md#teorema-master). | ||
L'algoritmo è **stabile** ma non _in loco_, ed è migliorabile con l'[insertion sort](../01/README.md) su array piccoli. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
# Quick sort | ||
|
||
Il **quick sort** sceglie un elemento **pivot** spostando gli elementi minori a sinistra per **partizionare** l'array in `A[p, ..., q-1]` e `A[q+1, ..., r]` e poi riordinarli ricorsivamente, dove `A[q]` è il valore del _pivot_. | ||
|
||
```c | ||
quicksort(Array A, int p, int r) | ||
if p < r | ||
q = partition(A, p, r) | ||
quicksort(A, p, q-1) | ||
quicksort(A, q+1, r) | ||
|
||
partition(Array A, int p, int r) -> int | ||
x = A[r] // Pivot | ||
i = p - 1 | ||
for j = p to r - 1 | ||
if A[j] <= x | ||
i = i + 1 | ||
swap(A[i], A[j]) | ||
swap(A[i+1], A[r]) | ||
return i+1 | ||
``` | ||
L'algoritmo è **corretto** per l'[invariante](../../01/02/README.md#analisi-della-correttezza) del `for`: | ||
> Ogni elemento tra `p` ed `i` è **minore o uguale** al _pivot_ e tra `i+1` e `j-1` è **maggiore** del _pivot_ | ||
e quando il `for` termina `j = r` quindi l'_invariante_ vale con gli elementi tra `i+1` e `r-1` maggiori del _pivot_. | ||
La complessità di `partition` è $\Theta(n)$ dato che impiega `r - p + 1 = n` iterazioni, mentre di `quicksort`: | ||
$$ | ||
T(n) = \begin{cases} | ||
O(1) & \text{se } n = 1 \\ | ||
T(k) + T(n - k - 1) + \Theta(n) & \text{se } n > 1 | ||
\end{cases} | ||
$$ | ||
che si può risolvere separando nei diversi casi: | ||
- **Peggiore** | ||
In questo caso una _partizione_ è grande $n - 1$ e l'altra è vuota, quindi: | ||
$$ | ||
\begin{split} | ||
T(n) &= T(n - 1) + T(0) + \Theta(n) = T(n - 1) + \Theta(n) = \\ | ||
&= T(n - 1) + cn = \\ | ||
&= T(n - 2) + c(n - 1) + cn = \\ | ||
&= \sum_{i = 1}^n ci = c\frac{n(n + 1)}{2} = \Theta(n^2) | ||
\end{split} | ||
$$ | ||
- **Migliore** | ||
In questo caso le _partizioni_ sono grandi $\lfloor\frac{n}{2}\rfloor$ e $\lfloor\frac{n}{2}\rfloor - 1$, quindi: | ||
$$ | ||
T(n) = 2T\left(\frac{n}{2}\right) + \Theta(n) = \Theta(n \log n) | ||
$$ | ||
per il [teorema master](../../../ct0371-1/01/03/README.md#teorema-master). | ||
- **Medio** | ||
In questo caso una _partizione_ è grande $\frac{9}{10}n$ e l'altra $\frac{1}{10}n$, quindi: | ||
$$ | ||
T(n) = T\left(\frac{9n}{10}\right) + T\left(\frac{n}{10}\right) + cn | ||
$$ | ||
risolvibile con l'_albero delle ricorsioni_: | ||
```dot process | ||
graph { | ||
node [shape=box] | ||
0 [label="cn"] | ||
1 [label="¹⁄₁₀cn"] | ||
2 [label="⁹⁄₁₀cn"] | ||
3 [label="¹⁄₁₀₀cn"] | ||
4 [label="⁹⁄₁₀₀cn"] | ||
5 [label="⁹⁄₁₀₀cn"] | ||
6 [label="⁸¹⁄₁₀₀cn"] | ||
0 -- 1, 2 | ||
1 -- 3, 4 | ||
2 -- 5, 6 | ||
{ | ||
node [shape=point width=0] | ||
7, 8, 9, 10, 11, 12, 13, 14 | ||
} | ||
{ | ||
edge [style=dashed] | ||
3 -- 7, 8 | ||
4 -- 9, 10 | ||
5 -- 11, 12 | ||
6 -- 13, 14 | ||
} | ||
} | ||
``` | ||
in cui ogni livello somma al più $cn$ dato che alcuni cammini **termineranno prima**, fino a: | ||
$$ | ||
\frac{n}{10^i} = 1\ \lor\ \left(\frac{9}{10}\right)^i n = 1 | ||
$$ | ||
da cui si ricava che il cammino **più corto** è alto $\log_{10} n$ e quello **più lungo** $\log_{\frac{10}{9}} n$, quindi: | ||
$$ | ||
T(n) \leq cn \cdot \log_{\frac{10}{9}} n = O(n \log n) | ||
$$ | ||
Questo processo si può **generalizzare** per ogni $0 < \alpha < 1$ e $c > 0$: | ||
$$ | ||
T(n) = T(\alpha n) + T((1 - \alpha)n) + cn = O(n \log n) | ||
$$ | ||
Se però il caso medio provenisse dall'**alternarsi** del caso migliore $L(n)$ e peggiore $U(n)$ definiti come: | ||
$$ | ||
\begin{split} | ||
L(n) &= 2U\left(\frac{n}{2}\right) + \Theta(n) \\ | ||
U(n) &= L(n - 1) + \Theta(n) | ||
\end{split} | ||
$$ | ||
si avrebbe che: | ||
$$ | ||
\begin{split} | ||
L(n) &= 2\left(L\left(\frac{n}{2} - 1\right) + \Theta\left(\frac{n}{2}\right)\right) + \Theta(n) = \\ | ||
&= 2L\left(\frac{n}{2} - 1\right) + 2\Theta\left(\frac{n}{2}\right) + \Theta(n) = \\ | ||
&= 2L\left(\frac{n}{2} - 1\right) + \Theta(n) = \Theta(n \log n) | ||
\end{split} | ||
$$ | ||
che è comunque $O(n \log n)$. | ||
## Pivot casuale | ||
Scegliere un _pivot_ **casuale** al posto di `A[r]` diminuisce le probabilità che capiti il caso peggiore. | ||
```c | ||
random_quicksort(Array A, int p, int r) | ||
if p < r | ||
q = random_partition(A, p, r) | ||
random_quicksort(A, p, q-1) | ||
random_quicksort(A, q+1, r) | ||
random_partition(Array A, int p, int r) -> int | ||
i = random(p, r) | ||
swap(A[i], A[r]) | ||
return partition(A, p, r) | ||
``` | ||
|
||
Come per il [merge sort](../02/README.md), è possibile migliorare le prestazioni sfruttando l'[insertion sort](../01/README.md) su array piccoli, oppure scegliendo il _pivot_ come **mediana** di tre elementi equidistanti. | ||
|
||
## Chiavi duplicate | ||
|
||
Nel caso fossero presenti chiavi **duplicate** si rischierebbe di avere array **sbilanciati** (e.g. nel caso di chiavi tutte uguali), che si possono evitare creando una _partizione_ per gli elementi uguali al _pivot_ `A[r]`: | ||
```c | ||
quicksort(Array A, int p, int r) | ||
if p < r | ||
<q, t> = partition(A, p, r) | ||
quicksort(A, p, q-1) | ||
quicksort(A, t+1, r) | ||
|
||
partition(Array A, int p, int r) -> <int, int> | ||
x = A[r] | ||
min = eq = p | ||
mag = r | ||
while eq < mag | ||
if A[eq] < x | ||
swap(A[eq], A[min]) | ||
min++ | ||
eq++ | ||
else if A[eq] == x | ||
eq++ | ||
else | ||
mag-- | ||
swap(A[eq], A[mag]) | ||
swap(A[r], A[mag]) | ||
return <min, mag> | ||
``` | ||
che è **corretto** perchè il `while` ha _invariante_: | ||
> Tra `p` e `min` sono **minori**, tra `min` ed `eq` sono **uguali** e tra `mag` e `r` sono **maggiori** del _pivot_ | ||
assicurando che quando il `while` termina `eq = mag` e quindi ci sono solo tre _partizioni_. | ||
Nel caso **medio** `quicksort` è $O(n \log n)$ e nel **peggiore** $O(n^2)$ con `partition` da $\Theta(n)$. | ||
L'algoritmo è **in loco**, ma non è _stabile_. |
Oops, something went wrong.