Algoritmi/Il paradigma "divide et impera"
- divide: un problema di dimensione viene ripartito in a sottoproblemi, ciascuno di dimensione , tra loro indipendenti (↔ ricorsione su ogni sottoproblema);
- impera: quando si arriva alla soluzione elementare, avviene la risoluzione diretta (↔ condizione di terminazione);
- combina: le soluzioni parziali (elementari) vengono ricombinate per ottenere la soluzione del problema di partenza.
Equazioni alle ricorrenze
[modifica | modifica sorgente]L'equazione alle ricorrenze permette di analizzare la complessità di ogni passo del procedimento divide et impera:
- divide: (costo della divisione)
- impera: (costo della soluzione elementare)
- combina: (costo della ricombinazione)
- = numero di sottoproblemi che risulta dalla fase di divide
- = dimensione di ciascun sottoproblema
Eseguire l'unfolding significa sviluppare alcuni passi di un'equazione alle ricorrenze per trovare una forma analitica chiusa della ricorrenza stessa sotto forma di sommatoria. Serve per analizzare la complessità di una funzione ricorsiva.
Ricerca binaria
[modifica | modifica sorgente]- Equazione alle ricorrenze
- Unfolding
- sviluppo:
- condizione di terminazione: [1]
- sommatoria:
- complessità asintotica: → algoritmo logaritmico
Ricerca del massimo di un vettore
[modifica | modifica sorgente]- Equazione alle ricorrenze
- a = 2: il vettore viene suddiviso in due sottovettori
- b = 2: ciascun sottovettore è ampio la metà del vettore di partenza
- Unfolding
- progressione geometrica →
- → algoritmo lineare
Moltiplicazione rapida di 2 interi
[modifica | modifica sorgente]- Equazione alle ricorrenze
- : per eseguire la somma binaria di due numeri di n cifre occorre scandire ciascuna cifra
- a = 4: i problemi ricorsivi sono le 4 operazioni di prodotto (le somme hanno complessità lineare ma non sono ricorsive)
- b = 2: xs, xd, ys e yd sono ampi la metà dei vettori di partenza
- Unfolding
- progressione geometrica →
- → algoritmo quadratico
Moltiplicazione rapida di 2 interi ottimizzata
[modifica | modifica sorgente]- Equazione alle ricorrenze
- Unfolding
- progressione geometrica →
- per la proprietà dei logaritmi:
- → più efficiente della moltiplicazione non ottimizzata ()
Le Torri di Hanoi
[modifica | modifica sorgente]Ho k = 3 pioli verticali, con n = 3 dischi forati in ordine decrescente di diametro sul primo piolo. Si vuole portarli tutti e 3 sul terzo piolo. Condizioni per lo spostamento:
- si può spostare un solo piolo per volta;
- sopra ogni disco solo dischi più piccoli.
Il problema si suddivide in 3 sottoproblemi:
- problema da n − 1: 000 → 011 (2 deposito) → si suddivide a sua volta in 3 sottoproblemi elementari
- problema da 1: 011 → 211 → sottoproblema elementare
- problema da n − 1: 211 → 222 (0 deposito) → si suddivide a sua volta in 3 sottoproblemi elementari
Equazione alle ricorrenze:
- dividi: (considero n − 1 dischi)
- risolvi: (ho 2 sottoproblemi ciascuno da n − 1)
- impera: (termino quando sposto un disco solo)
- combina: (nessuna combinazione)
Unfolding:
- progressione geometrica →
- → algoritmo esponenziale (anche se decidibile)
Il righello
[modifica | modifica sorgente]Disegnare le tacche di un righello in maniera ricorsiva, di differenti altezze, fino a quando si arriva all'altezza 0. Si disegna ricorsivamente a metà del sottointervallo la tacca, quindi si considerano i due sottointervalli sinistro e destro e si disegna la tacca di una unità di altezza inferiore.
Backtrack: Si esplora una scelta per volta, e se la scelta non va bene si ritorna indietro. (vd. filo di Arianna) Si termina quando tutte le scelte sono esaurite. Tramite il backtrack si può esplorare tutto lo spazio delle soluzioni.
Gli algoritmi ricorsivi di ordinamento
[modifica | modifica sorgente]Merge sort (o ordinamento per fusione)
[modifica | modifica sorgente]Passi
[modifica | modifica sorgente]- divide: si suddivide il vettore in due sottovettori
- ricorsione:
- si applica il merge sort sul sottovettore sinistro
- si applica il merge sort sul sottovettore destro
- condizione di terminazione: il sottovettore ha 1 cella
- combina: si uniscono tutti i sottovettori ordinati, scandendo in parallelo i due sottovettori da sinistra verso destra e confrontando di volta in volta i valori scegliendo quello minore:
Analisi di complessità
[modifica | modifica sorgente]Ragionamento intuitivo: ho livelli di ricorsione e devo fare unioni → operazioni totali:
- Equazione alle ricorrenze
- dividi: (calcola la metà di un vettore)
- risolvi: (risolve 2 sottoproblemi di dimensione ciascuno)
- terminazione: (semplice test)
- combina:
- Unfolding
→ algoritmo linearitmico (ottimo)
Quicksort
[modifica | modifica sorgente]Passi
[modifica | modifica sorgente]È quadratico () → secondo la teoria degli algoritmi non dovrebbe essere ottimo, però: sperimentalmente si dimostra che il caso peggiore ricorre molto raramente, a differenza del caso medio.
Partizione: La divisione non avviene a metà, ma secondo una certa proprietà: sceglie arbitrariamente un elemento separatore (= pivot), quindi separa gli altri valori in sottovettore sinistro e destro a seconda se sono minori o maggiori del pivot.
Individua (tramite un ciclo ascendente su i e un ciclo discendente su j) una coppia di elementi di indici i e j che siano entrambi fuori posto (ovvero l'i-esimo è maggiore del pivot, e il j-esimo è minore del pivot), quindi li scambia.
Condizione di terminazione: i == j
(si ferma quando tutte le coppie sono già a posto).
Costo della partizione: (cioè la scansione di tutti gli n elementi).
Al termine della ricorsione è inutile una fase di ricombinazione, perché i dati si trovano già nella posizione corretta.
Analisi di complessità
[modifica | modifica sorgente]Il quicksort non segue la formula generale del divide et impera.
- Caso peggiore
Il vettore si trova in ordine inverso → la partizione genera un sottovettore da n ‒ 1 elementi e l'altro da 1 elemento.
Equazione alle ricorrenze:
- = costo per il sottovettore da 1 elemento
- = costo per il sottovettore da n ‒ 1 elementi
- = costo della partizione
- = costo unitario della risoluzione (riguarda gli elementi al termine della ricorsione)
Unfolding:
- Caso migliore
Ogni sottovettore è esattamente la metà → ricorda il merge sort (in questo caso è linearitmico).
- Caso medio
Partizione fortemente sbilanciata, senza arrivare al caso peggiore → il quicksort è linearitmico.
È meglio cercare un pivot in modo da dividere sempre in maniera conveniente il vettore → la complessità non cambia (neanche prendendo un pivot che separa sempre il vettore a metà), ma si possono ridurre le costanti.
Note
[modifica | modifica sorgente]- ↑ Per semplicità si suppone n una potenza esatta di 2.