Algoritmi/Il paradigma "divide et impera"

Indice del libro
  1. divide: un problema di dimensione viene ripartito in a sottoproblemi, ciascuno di dimensione , tra loro indipendenti (↔ ricorsione su ogni sottoproblema);
  2. impera: quando si arriva alla soluzione elementare, avviene la risoluzione diretta (↔ condizione di terminazione);
  3. combina: le soluzioni parziali (elementari) vengono ricombinate per ottenere la soluzione del problema di partenza.

Equazioni alle ricorrenze

modifica

L'equazione alle ricorrenze permette di analizzare la complessità di ogni passo del procedimento divide et impera:

 
  1. divide:   (costo della divisione)
  2. impera:   (costo della soluzione elementare)
  3. 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
Equazione alle ricorrenze
 
Unfolding
  1. sviluppo:  
  2. condizione di terminazione:  [1]
  3. sommatoria:  
  4. complessità asintotica:   → algoritmo logaritmico

Ricerca del massimo di un vettore

modifica
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
 
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
Equazione alle ricorrenze
 
Unfolding
  •  
  • progressione geometrica →  
  • per la proprietà dei logaritmi:  
  •   → più efficiente della moltiplicazione non ottimizzata ( )

Le Torri di Hanoi

modifica

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:

  1. si può spostare un solo piolo per volta;
  2. sopra ogni disco solo dischi più piccoli.

Il problema si suddivide in 3 sottoproblemi:

  1. problema da n − 1: 000 → 011 (2 deposito) → si suddivide a sua volta in 3 sottoproblemi elementari
  2. problema da 1: 011 → 211 → sottoproblema elementare
  3. problema da n − 1: 211 → 222 (0 deposito) → si suddivide a sua volta in 3 sottoproblemi elementari

Equazione alle ricorrenze:  

  1. dividi:   (considero n − 1 dischi)
  2. risolvi:   (ho 2 sottoproblemi ciascuno da n − 1)
  3. impera:   (termino quando sposto un disco solo)
  4. combina:   (nessuna combinazione)

Unfolding:

  •  
  •  
  • progressione geometrica →  
  •   → algoritmo esponenziale (anche se decidibile)

Il righello

modifica

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

Merge sort (o ordinamento per fusione)

modifica
  1. divide: si suddivide il vettore in due sottovettori
  2. 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
  3. 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

Ragionamento intuitivo: ho   livelli di ricorsione e devo fare   unioni →   operazioni totali:

 
Equazione alle ricorrenze
 
  1. dividi:   (calcola la metà di un vettore)
  2. risolvi:   (risolve 2 sottoproblemi di dimensione   ciascuno)
  3. terminazione:   (semplice test)
  4. combina:  
Unfolding
 

  → algoritmo linearitmico (ottimo)

Quicksort

modifica

È 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

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.

  1. Per semplicità si suppone n una potenza esatta di 2.