Logo dell'Università di Catania: Siciliae Studium Generale 1434 Logo del Centro IPPARI, Programmazione
matite e gomma
Loghi istituzionali: Siciliae Studium Generale 1434, Centro ricerche IPPARI, Università di Catania, Facoltà di Scienze Matematiche, Fisiche, Naturali, Insegnamento di Programmazione

Algoritmi ricorsivi e programmazione dinamica

Lezione 20 di Programmazione 2

Docente: Giuseppe Scollo

Università di Catania, sede di Comiso (RG)
Facoltà di Scienze Matematiche, Fisiche e Naturali
Corso di Studi in Informatica applicata, AA 2006-7

Logo di Conformità WCAG-1 di Livello Tripla A, W3C-WAI Web Content Accessibility Guidelines 1.0 Validazione XHTML 1.0 Validazione CSS 2

Indice

  1. Algoritmi ricorsivi e programmazione dinamica
  2. problema del righello
  3. torri di Hanoi e righello
  4. programmazione dinamica
  5. programmazione dinamica bottom-up
  6. programmazione dinamica top-down
  7. problema dello zaino
  8. soluzione ricorsiva inefficiente
  9. soluzione con programmazione dinamica
  10. programmazione dinamica e prestazioni
  11. esercizi

problema del righello

problema: dividere un righello in 2n parti di eguale lunghezza, marcandolo con una tacca di altezza n al centro, quindi con una tacca di altezza n-1 al centro di ciascuna delle sue due metà, una tacca di altezza n-2 al centro di ciascuno dei suoi quattro quarti, etc., fino alle tacche di altezza 1 al centro di ciascuna delle sue 2n-1 parti di eguale lunghezza

ecco una soluzione ricorsiva:

è evidente una certa analogia formale di questa soluzione con la soluzione ricorsiva del problema delle torri di Hanoi ...

questo ci aiuterà a trovare un algoritmo non ricorsivo per il problema delle torri di Hanoi!

torri di Hanoi e righello

osserviamo innanzitutto che l'altezza h della m-sima tacca prodotta dall'algoritmo del righello ha lo stesso valore di N (diametro del disco) nella corrispondente mossa prodotta dall'algoritmo ricorsivo delle torri di Hanoi:

un algoritmo iterativo per il problema delle torri di Hanoi, con una pila di n dischi da spostare nel piolo accanto in direzione d, consta nell'iterare la seguente coppia di mosse, fino al conseguimento dell'obiettivo (v. inoltre l'esercizio 1):

  1. sposta il disco più piccolo in direzione d se n è dispari, opposta se n è pari
  2. esegui l'unica mossa consentita che non sposti il disco più piccolo

la correttezza di questo algoritmo si può facilmente verificare dimostrando per induzione su n che, nell'algoritmo ricorsivo delle torri di Hanoi:

programmazione dinamica

negli algoritmi ricorsivi visti finora, l'efficienza dell'approccio divide et impera è stata garantita dal fatto che l'algoritmo partiziona il problema in istanze più piccole e indipendenti dello stesso problema, vale a dire operanti su parti disgiunte dell'input o della struttura dati in gioco

algoritmi ricorsivi che non soddisfano tale condizione di indipendenza possono avere costi computazionali proibitivi

ad esempio, l'algoritmo per il calcolo dei numeri di Fibonacci derivato direttamente dalla loro definizione ricorsiva richiede tempo esponenziale:

l'inefficienza dell'algoritmo è dovuta al ripetersi di invocazioni ricorsive con ugual valore del parametro: il numero totale di invocazioni ricorsive per il calcolo di FN, per N > 1, è maggiore di FN+1 (v. esercizio 2), che è proporzionale a φN (v. lezione 4)

vediamo come ...

programmazione dinamica bottom-up

per valutare in tempo lineare una funzione definita induttivamente sui numeri naturali si può calcolarla per valori crescenti dell'argomento, a partire dal più piccolo e fino a quello desiderato, memorizzandone i valori in un array

ecco ad es. come si realizza il calcolo dei primi N numeri di Fibonacci con questa tecnica:

la programmazione dinamica bottom-up è efficace nel prevenire la reiterazione superflua di calcoli già effettuati, tuttavia con essa:

una diversa tecnica di programmazione dinamica è la seguente

programmazione dinamica top-down

anziché eliminare le invocazioni ricorsive, si trasforma una definizione ricorsiva di funzione così che:

questa tecnica prende il nome di programmazione dinamica top-down: con essa, una definizione ricorsiva di funzione per induzione naturale può venire automaticamente trasformata in una definizione in cui il numero di invocazioni ricorsive è al più pari al valore dell'argomento

ecco ad es. la trasformazione della funzione di calcolo dei numeri di Fibonacci secondo questa tecnica:

alla struttura dell'algoritmo ricorsivo (inefficiente) visto prima, quello trasformato aggiunge:

problema dello zaino

problema di ottimizzazione combinatoria, in diverse varianti: eccone la più semplice

dati:   uno zaino di capacità M

problema:

varianti più complesse:

soluzione ricorsiva inefficiente

si assuma senza perdita di generalità che il volume di ogni oggetto non superi la capacità dello zaino

specifica di soluzione ricorsiva: per ogni tipo i di oggetti, con 0 i < N, siano

allora la soluzione del problema è data da:   z = max { val i + zi | 0 i < N }

la specifica si traduce nel seguente algoritmo ricorsivo in C++, dove M sarà il parametro attuale nella prima invocazione della funzione ricorsiva:

la possibilità di multiple invocazioni ricorsive con lo stesso valore del parametro è fonte di inefficienza

soluzione con programmazione dinamica

la trasformazione della soluzione ricorsiva inefficiente del problema dello zaino in una ricorsiva efficiente è immediata con la tecnica della programmazione dinamica top-down:

diversamente dal problema del calcolo dei numeri di Fibonacci, la soluzione ricorsiva del problema dello zaino per un dato valore del parametro non richiede la soluzione dello stesso problema per tutti i precedenti valori del parametro, ma solo per alcuni di essi

programmazione dinamica e prestazioni

se è costante il costo computazionale dell'esecuzione di una invocazione ricorsiva, detratto quello delle invocazioni ricorsive che essa eventualmente effettua, allora il costo computazionale di una funzione ricorsiva con argomento intero naturale, definita con una tecnica di programmazione dinamica, è lineare rispetto all'argomento

inoltre, come mostrato dalla variante considerata del problema dello zaino, la programmazione dinamica top-down ha un costo computazionale inferiore a quello della programmazione dinamica bottom-up quando la ricorsione non coinvolge tutti i valori dell'argomento inferiori a quello dato

varianti più complesse del problema dello zaino rivelano un altro vantaggio dell'approccio top-down rispetto a quello bottom-up:

esercizi

  1. sviluppare un algoritmo iterativo per il problema delle torri di Hanoi, in cui si rappresenti lo stato del sistema con un array di 3 pile di interi (i dischi, ciascuno rappresentato dal suo diametro), indicizzato dai pioli, e lo spostamento di un disco da un piolo ad un altro venga effettuato mediante operazioni standard dell'ADT pila sulle corrispondenti pile di dischi
  2. il testo (Sedgewick, 2003), a p. 223, sostiene che il numero totale di invocazioni ricorsive per il calcolo dell'N-simo numero di Fibonacci FN è FN+1: mostrare che, invece, tale numero è maggiore di FN+1 per N > 1, e in verità maggiore di FN+2 per N > 3
  3. esercizio 5.37 del testo (Sedgewick, 2003), p. 230: definire una funzione C++ che calcoli FN mod M usando una quantità di spazio costante, dove FN è l'N-simo numero di Fibonacci
  4. specificare una soluzione ricorsiva della variante del problema dello zaino in cui si richiede di determinare il multinsieme di oggetti che massimizza il valore del contenuto dello zaino, e implementarla con la tecnica della programmazione dinamica top-down
    • suggerimento: considerare diverse alternative per la struttura dati che rappresenta il multinsieme contenuto nello zaino, ad es. pila di oggetti, array int[N] di rappresentazione della molteplicità per ciascun tipo di oggetti, e scegliere quella che sembra permettere la più semplice formulazione della specifica della soluzione ricorsiva; sarà anche quella che produce l'implementazione più efficiente con la tecnica della programmazione dinamica top-down?
  5. confrontare l'efficienza della soluzione sviluppata nell'esercizio precedente con quella della soluzione proposta dal testo (Sedgewick, 2003), a p. 229, Programma 5.13, anch'essa basata sulla programmazione dinamica top-down, ma con una diversa rappresentazione del contenuto dello zaino