La presentazione è in caricamento. Aspetta per favore

La presentazione è in caricamento. Aspetta per favore

Divide et Impera Parte 11 - Risoluzione di problemi per divisione in sottoproblemi “bilanciati” Corso A: Prof. Stefano Berardi http://www.di.unito.it/~stefano.

Presentazioni simili


Presentazione sul tema: "Divide et Impera Parte 11 - Risoluzione di problemi per divisione in sottoproblemi “bilanciati” Corso A: Prof. Stefano Berardi http://www.di.unito.it/~stefano."— Transcript della presentazione:

1 Divide et Impera Parte 11 - Risoluzione di problemi per divisione in sottoproblemi “bilanciati” Corso A: Prof. Stefano Berardi Corso B: Prof. Ugo de’ Liguoro

2 Divide et Impera

3 Indice Parte 11 La ricorsione è efficiente? Due algoritmi per il minimo e il massimo. Divide et Impera: l’ordinamento per fusione MergeSort. Un altro esempio di Divide et impera: il Quicksort Divide et Impera

4 1. La ricorsione è efficiente?
Nel caso della ricorsione lineare l’efficienza di una funzione ricorsiva è paragonabile a quella di un’iterazione Negli esempi visti di ricorsione ad albero (fibonacci, coefficiente binomiale) la ricorsione è molto meno efficiente dell’iterazione. Questo è vero in generale? Molto dipende da come progettiamo gli algoritmi ricorsivi. Un metodo di progettazione per ottenere algoritmi ricorsivi ad albero efficienti, come vedremo, è il metodo del “Divide et Impera”. Divide et Im pera

5 Minimo e Massimo iterativo
Come esempio di ricorsione efficiente vediamo una versione iterativa e una ricorsiva dell’algoritmo MinMax, che calcola il minimo ed il massimo tra i valori di un vettore A[]. void MinMax (int A[], int n, int& min, int& max) // pre: n > 0, ossia A[0..n-1] non e' vuoto // post: min e max sono il minimo ed il massimo // in A[0..n-1] rispettivamente { min = max = A[0]; for (int i = 1; i < n; i++) if (A[i] < min) min = A[i]; else if (max < A[i]) max = A[i];} Sia n=lun(A). L’algoritmo iterativo richiede (n-1) confronti per il minimo, (n-1) per il massimo, dunque 2n  2 confronti. Sembra difficile fare di meglio! Divide et Impera

6 Minimo e Massimo ricorsivo
L’algoritmo DI_MinMax calcola il min e max in A[i..j] con un metodo di dimezzamenti analogo alla Ricerca Binaria. Se [i..j] ha al più due punti, calcola il minimo e il massimo direttamente. Altrimenti, divide l’intervallo A[i..j] in due parti il più possibile uguali, A[i..m] e A[m+1..j], dove m è il punto medio di [i..j] Calcola il massimo e il minimo (max1 e min1) di A[i..m], e il massimo e il minimo (max2 e min2) di A[m+1..j]. Il massimo di A[i..j] è il massimo di max1, max2, mentre il minimo di A[i..j] è il minimo di min1, min2. Divide et Impera

7 Minimo e Massimo ricorsivo
void DI_MinMax(int A[], int i, int j, int& min, int& max) // pre: i <= j, ossia A[i..j] non e' vuoto // post: min e max sono il min/max in A[i..j] risp. { if (i == j) min = max = A[i]; else if (i == j-1) if (A[i] < A[j]) { min = A[i]; max = A[j];} else { min = A[j]; max = A[i];} else // i..j ha piu' di 2 elementi { int min1, max1, min2, max2; int m = (i + j)/2; // punto medio in i..j DI_MinMax(A, i, m, min1, max1); DI_MinMax(A, m+1, j, min2, max2); min = Min(min1, min2); max = Max(max1, max2); } Quanti sono i confronti richiesti nel caso ricorsivo? Divide et Impera

8 Minimo e Massimo: la ricorsione è più efficiente
Se n = lun(A[i..j]) = j – i + 1 allora i confronti C (n) richiesti dall’algoritmo ricorsivo sono: se n = 1 C (n) = se n = 2 2C (n/2) se n > 2 Infatti: quando dividiamo il vettore in due vettori di lunghezza n/2, ognuno di essi ci richiede C(n/2) confronti, poi con un altro confronto calcoliamo il massimo tra i due massimi, con un altro ancora il minimo tra i due minimi. A questo punto, ragionando per induzione su n, è possibile dimostrare che i confronti sono C(n) = (3/2)n – 2. L’algoritmo iterativo è più semplice, ma richiede un numero maggiore di confronti: 2n-2. Divide et Impera

9 “Per meglio dominare occorre dividere gli avversari”
2. Divide et Impera “Per meglio dominare occorre dividere gli avversari” Ossia: suddividi il problema in sottoproblemi di dimensione circa eguale (“bilanciati”); risolvi i sottoproblemi con la ricorsione, infine combina i risultati MinMax e la Ricerca binaria sono esempi di algoritmi progettati con “Divide et Impera”. Il vantaggio del Divide et Impera è che la dimensione massima del sottoproblema che dobbiamo risolvere decresce esponenzialmente, e quindi scende rapidamente a zero. Per esempio, quando dividiamo in due sottoproblemi di dimensione uguale, la dimensione massima si dimezza ogni volta.

10 “Per meglio dominare occorre dividere gli avversari”
2. Divide et Impera “Per meglio dominare occorre dividere gli avversari” Ossia: suddividi il problema in sottoproblemi di dimensione circa eguale (“bilanciati”); risolvi i sottoproblemi con la ricorsione, infine combina i risultati Ecco come appare un programma “Divide et Impera” DivideEtImpera (P, n) // Pre: n è la dimensione del problema P if n  k then risolvi direttamente P else dividi P nei sottoproblemi P1,…, Ph di dimensioni n1,…, nh all’incirca uguali for i = 1 to h do Risi = DivideEtImpera (Pi, ni) return combinazione di Ris1, …, Rish uguale al risultato

11 “Per meglio dominare occorre dividere gli avversari”
Divide et Impera “Per meglio dominare occorre dividere gli avversari” Ossia: suddividi il problema in sottoproblemi di dimensione circa eguale (“bilanciati”); risolvi i sottoproblemi con la ricorsione, infine combina i risultati In genere, una stima del tempo necessario si calcola cosí: Tempo per dividere Tempo per combinare Somma dei tempi dei sottoproblemi Divide et Impera

12 Esempio di “Divide et impera”: Ordinamento per fusione
88 14 98 25 62 52 79 30 23 31 dividi l’insieme da ordinare il più possibile a metà, in modo qualsiasi 14,23,30,62,79 … e l’altra metà da ordinare ad un altro 25,31,52,88,98 Affida una metà da ordinare ad un amico … Divide et Impera

13 Ordinamento per fusione
25,31,52,88,98 14,23,30,62,79 25,31,52,88,98 14,23,30,62,79 14,23,25,30,31,52,62,79,88,98 “Fondi” tra loro i due vettori ordinati ottenuti Fa solo attenzione che la “fusione” sia ordinata! Divide et Impera

14 Ordinamento per fusione o “MergeSort”
void MergeSort(int A[], int i, int j) { if (i < j) { int m = (i+j)/2; MergeSort(A, i, m); MergeSort(A, m+1, j); /* per completare l’opera, devo solo “fondere” i segmenti ordinati A[i,m] e A[m+1,j] */ Merge(A, i, m, j); }} Divide et Impera

15 Fusione ordinata (merge) di due segmenti ordinati adiacenti
Definiamo un metodo per “fondere” due segmenti ordinati consecutivi A[i..m] e A[m+1..j] in un segmento ordinato A[i...j] che comprende tutti i loro punti. Creiamo un vettore di appoggio B in cui costruire l’unione ordinata di A[i..m] e A[m+1..j]. Prendiamo il primo elemento di A[i..m] e il primo elemento di A[m+1..j] e li confrontiamo. Il più piccolo dei due diventa il minimo di B. Continuiamo così a togliere il più piccolo elemento rimasto da A[i…m] o da A[m+1..j] e a inserirlo in B, finché A[i..m] oppure A[m+1..j] diventano vuoti. A questo punto gli elementi rimasti in A[i..m] oppure A[m+1..j] sono i più grandi, e li ricopiamo in B. Finito di costruire B, lo ricopiamo all’indietro su A. Divide et Impera

16 I primi due passi del Merge
25,31,52,88,98 14,23,30,62,79 14 25,31,52,88,98 23,30,62,79 14 14, 23 Divide et Impera

17 Fusione ordinata (merge) di due segmenti ordinati adiacenti
void Merge(int A[], int i, int m, int j) // pre: i <= m <= j A[i..m] e A[m+1..j] ordinati // post: A{i..j] e' ordinato {int* B = new int[j-i+1]; int p = i, q = m+1, k = 0; while (p <= m && q <= j) if (A[p]< A[q]) {B[k]=A[p];k++;p++} else {B[k]=A[q];k++;q++} /* se A[i..m] o A[m+1..j] sono vuoti copio tutti gli elementi rimasti in A[i..m] A[m+1..j] su B. */ Copy(A, p, m, B, k); Copy(A, q, j, B, k+(m-p+1)); /* Riporto il risultato da B in A */ Copy(B, 0, k-1, A, i); delete B;} Invariante while: A[p..m], A[q..j] ordinati, B[0..k – 1] ordinato ed i suoi elem. sono quelli di A[i..p – 1] e A[m+1..q – 1] Divide et Impera

18 La funzione di “copia” tra vettori
Nel lucido precedente abbiamo utilizzato una funzione di “copia” definita come segue: void Copy(int A[], int i, int j, int B[], int k) // post: copia A[i..j] su B[] a partire da k { for (int h = i; h <= j; h++, k++) B[k] = A[h]; } Divide et Impera

19 Merge Sort: prima parte esecuzione (divisione)
7 4 1 8 2 3 6 5 7 4 1 8 2 3 6 5 7 4 1 8 2 3 6 5 7 4 1 8 2 3 6 5 Scomponiamo il vettore in segmenti di un punto soltanto (necessariamente ordinati!) Divide et Impera

20 Merge Sort: seconda parte esecuzione (merge)
1 2 3 4 5 6 7 8 1 4 7 8 2 3 5 6 4 7 1 8 2 3 5 6 7 4 1 8 2 3 6 5 Fondiamo tra loro i segmenti ordinati in segmenti più grandi, preservando l’ordine, fino a ricostruire il vettore Divide et Impera

21 Come si stima il tempo di calcolo di una funzione ricorsiva?
Ricordiamo quanto abbiamo visto studiando i tempi di calcolo: a ogni algoritmo associamo un ordine di infinito per il tempo di calcolo nel caso peggiore. Per le funzioni ricorsive il tempo di calcolo è a sua volta definito ricorsivamente Per esempio, questa è la definizione ricorsiva del tempo di calcolo per la funzione ricorsiva “fattoriale” Divide et Impera

22 Il tempo di Merge Sort Questa è la definizione ricorsiva del tempo di calcolo per il MergeSort. Richiede di conoscere il tempo richiesto dal Merge: E’ facile vedere che dunque: Occorrono log2(n) dimezzamenti successivi per trasformare n in 1. Dunque la formula per calcolare T(n) ci chiede di sommare n per log2(n) volte (vedi prossimo lucido) Divide et Impera

23 Il tempo di Merge Sort Divide et Impera

24 Il tempo di Merge Sort Divide et Impera

25 Il tempo di Merge Sort Divide et Impera

26 Il tempo del Merge Sort è O(n log(n))
Albero delle chiamate ricorsive, ciascuna con il suo costo È possibile ordinare in tempo meno di O(n log n) ? Ogni “livello” dell’albero ha costo totale n, e i livelli sono + + + + Dunque la somma di tutti i costi è: Il MergeSort è ulteriormente migliorabile? Bisogna prima stabilire quale è il tempo minimo richiesto dall’ordinamento Divide et Impera

27 Alberi di decisione e limitazioni inferiori al tempo di calcolo
Un albero rappresenta le decisioni prese da un algoritmo quando valgono le condizioni seguenti: i ”nodi interni” dell’albero rappresentano le decisioni prese dall’algoritmo le ”foglie” dell’albero rappresentano i possibili risultati dell’algoritmo i ”rami” dell’albero rappresentano particolari esecuzioni dell’algortimo. La minima lunghezza del ramo più lungo di un albero di decisione di un algoritmo fornisce una limitazione inferiore al numero di decisioni necessarie all’algoritmo nel caso peggiore. Divide et Impera

28 Alberi quasi perfettamente bilanciati
Def. Un albero binario è completo se ha 2k vertici per ogni livello k non vuoto. Un albero binario è quasi perfettamente bilanciato (qpb) se, avendo altezza h, è completo sino al livello h 1 (come nella figura qui sopra). Le foglie di un albero quasi perfettamente bilanciato di altezza h sono in numero di: 2h1  s  2h , ovvero h  log2 (s) Quindi un problema la cui soluzione può rappresentarsi con un albero q.p.b. con s(n) possibili soluzioni richiede tempo almeno O(log2 s(n)) (la lunghezza del suo ramo più lungo).

29 Un esempio di albero q. p. b
Un esempio di albero q.p.b. di decisione per l’ordinamento di 3 elementi a:b a < b b < a b:c b:c b < c c < b b < c c < b a, b, c a:c a:c b, c, a a < c c < a a < c c < a a, c, b c, a, b b, a, c b, c, a Ogni nodo corrisponde al confronto tra due elementi del vettore {a,b,c} da ordinare, ogni foglia è uno dei 3! = 6 possibili ordinamenti finali, la massima lunghezza di un “ramo” è 3, mentre la minima è 2 (dato che l’albero è q.p.b.). Divide et Impera

30 Il tempo di calcolo minimo richiesto dall’ordinamento
Nel caso dell’ordinamento le foglie dell’albero di decisione sono s(n) = n! (il risultato di un algoritmo di ordinamento è una permutazione, dunque i risultati possibili sono n!). I nodi interni rappresentano i confronti tra due elementi. Nel caso dell’ordinamento il numero dei confronti nel caso peggiore deve essere dunque almeno log2(n!). Usando la formula di Stirling per n! concludiamo che: Ossia il tempo per ordinare è almeno O(nlog(n)). Algoritmi O(nlog(n)), come il MergeSort, impiegano dunque tempo minimo (a meno di costanti moltiplicative, che la Teoria del Tempo di Calcolo non considera). Divide et Impera

31 3. Divide et impera: il Quick Sort
Dividi l’insieme in due scegliendo un elemento (detto “perno”) a caso, e disponendo gli elementi ≤ del perno da una parte e quelli ≥ dall’altra 88 14 98 25 62 52 79 30 23 31 14 25 30 23 31 88 98 62 79 ≤ 52 ≤ Divide et Impera

32 Quick Sort Fà ordinare una metà …e la seconda metà ad un amico …
14 25 30 23 31 88 98 62 79 ≤ 52 ≤ 14,23,25,30,31 Fà ordinare una metà ad un amico … 62,79,98,88 …e la seconda metà ad un altro amico Divide et Impera

33 Quick Sort 14,23,25,30,31 52 62,79,98,88 Metti insieme i due risultati con il perno in mezzo: formano un insieme ordinato senza bisogno di fare altro 14,23,25,30,31,52,62,79,88,98 Divide et Impera

34 Quick Sort (supponendo già definita la funzione Perno)
void QuickSort(int A[], int i, int j) //pre: A[] e' un vettore, i <= j < dim. di A[] // post: ordina A in senso non decrescente { if (i < j) { int k = Perno(A, i, j); QuickSort(A, i, k); QuickSort(A, k+1, j); } Divide et Impera

35 Divisione di A[p,u] in due parti con il perno in mezzo
int Perno (int A[], int p, int u) // pre: p <= u < dimensione di A[], x = A[p] “perno” // post: se y in A[p..j-1] allora y <= x, A[j-1] == x // se y in A[j..u] allora y >= x { int x = A[p], i = p+1, j = p+1; while (i <= u) // INVARIANTE. Se y in v[p+1..j-1] allora y < x // se z in v[j..i-1] allora z >= x if (A[i] < x) {swap(A[i], A[j]);i++;j++;} else i++; swap(A[p], A[j-1]); // inserisco il perno in mezzo return j-1;} // restituisco la posizione del perno Divide et Impera

36 Quick Sort: il caso pessimo
14,23,25,30,31,52,62,79,88,98 ≤ 14 ≤ 23,25,30,31,52,62,79,88,98 (vuoto) Se Perno sceglie sempre il primo elemento, allora il caso peggiore si verifica se la lista è già ordinata, perché la dimensione del secondo sottoproblema descresce molto lentamente, di un solo elemento alla volta. Divide et Impera

37 Il tempo peggiore per il Quick Sort è O(n2)
Sia n la lunghezza del vettore da ordinare e p la posizione del primo perno da inserire poniamo Tempo = num. dei confronti Tempo della funzione Perno Caso peggiore: avviene quando p = 1. In tal caso, infatti, abbiamo P(n) = n – 1 e T(1) = 0, quindi deduciamo che: Divide et Impera

38 Il tempo medio per il Quick Sort è O(nlog(n))
Il tempo impiegato nel caso medio è la media dei tempi impiegati nelle n possibili posizioni del perno: p=0,…,n-1. p è la posizione del perno Partendo dalla formula per il tempo nel caso medio si dimostra che: Almeno, il QuickSort è ottimo nel caso medio: richiede tempo medio O(nlog(n)) Divide et Impera

39 Fine del Corso di Informatica 2009/2010
Divide et Impera


Scaricare ppt "Divide et Impera Parte 11 - Risoluzione di problemi per divisione in sottoproblemi “bilanciati” Corso A: Prof. Stefano Berardi http://www.di.unito.it/~stefano."

Presentazioni simili


Annunci Google