La presentazione è in caricamento. Aspetta per favore

La presentazione è in caricamento. Aspetta per favore

Lezione 8 Ricorsione Code. Sommario Paradigma di programmazione Divide et Impera Ricorsione e tempo di calcolo Pile e code un ADT Pila.

Presentazioni simili


Presentazione sul tema: "Lezione 8 Ricorsione Code. Sommario Paradigma di programmazione Divide et Impera Ricorsione e tempo di calcolo Pile e code un ADT Pila."— Transcript della presentazione:

1 Lezione 8 Ricorsione Code

2 Sommario Paradigma di programmazione Divide et Impera Ricorsione e tempo di calcolo Pile e code un ADT Pila

3 Ricorsione Molti algoritmi hanno una struttura ricorsiva Struttura ricorsiva: –l’algoritmo è definito in termini di se stesso –un algoritmo è ricorsivo quando contiene una o più istruzioni di chiamata a se stesso –le chiamate ricorsive non possono succedersi in modo infinito altrimenti l’algoritmo non terminerebbe mai –deve sempre esistere una condizione di terminazione che determina quando l’algoritmo smette di richiamarsi –in questo caso la computazione prosegue eseguendo un insieme di istruzioni dette passo base

4 Divide et Impera Paradigma di programmazione Divide et Impera Si sfrutta la soluzione ricorsiva di un problema Il procedimento è caratterizzato dai seguenti passi: –Divide: suddivisione del problema in sottoprolemi –Impera: soluzione ricorsiva dei sottoproblemi. Problemi piccoli sono risolti direttamente. –Combina: le soluzioni dei sottoproblemi sono ricoombinate per ottenere la soluzione del problema originale

5 Esempio di algoritmo Divide et Impera: merge sort L’algoritmo merge sort ordina un vettore di elementi nel modo seguente: –Divide: ogni sequenza di n elementi da ordinare è suddivisa in 2 sottosequenze di n/2 elementi –Impera: ordina (sort) ricorsivamente le sottosequenze –Combina: fonde (merge) le due sottosequenze per produrre la sequenza ordinata La condizione di terminazione si ha quando si deve ordinare una sequenza di lunghezza 1 (in quanto già ordinata)

6 Spiegazione intuitiva L’operazione chiave è la procedura di fusione L’idea è di fondere fra di loro due sottosequenze già ordinate (in ordine decrescente ad esempio) –Si confrontano in ordine gli elementi delle due sottosequenze –Si trascrive l’elemento più piccolo –Si reitera il procedimento a partire dall’elemento successivo a quello già considerato

7 Visualizzazione del concetto di fusione Sequenze: –A: –B: –R: Step di fusione: 1) –A: –B: _ –R: 1 2) –A: _ –B: _ –R: 1 2 3) –A: _ –B: _ _ 5 7 –R: ) –A: _ _ 6 8 –B: _ _ 5 7 –R: ) –A: _ _ 6 8 –B: _ _ _ 7 –R: ) –A: _ _ _ 8 –B: _ _ _ 7 –R: ) –A: _ _ _ 8 –B: _ _ _ _ –R: ) –A: _ _ _ _ –B: _ _ _ _ –R:

8 Visualizzazione del concetto di fusione ricorsiva Sequenza:

9 Pseudo Codice MERGE-SORT(A,p,r) 1if p

10 Pseudo Codice MERGE(A,p,q,r) 1 aux  A 2 i  p 3 j  q+1 4 for k  p to r 5do if i=q+1 6then A[k]  aux[j++] 7else if j=r+1 8then A[k]  aux[i++] 9else if aux[i]

11 Il merge sort in C++ (l’algoritmo) typedef int Item; // tipo di ogni elemento da ordinare void merge(Item A[], int p, int q, int r) { const int MaxItems=1000; static Item aux[MaxItems]; int i,j; for (i = q+1; i > p; i--) aux[i-1] = A[i-1]; for (j = q; j < r; j++) aux[r+q-j] = A[j+1]; for (int k = p; k <= r; k++) if (aux[j] < aux[i]) A[k] = aux[j--]; else A[k] = aux[i++]; }

12 Nota: Si è copiata la seconda sottosequenza in ordine decrescente in modo da poter gestire il caso in cui la sequenza originale è formata da un numero dispari di elementi e quindi una sottosequenza è più piccola dell’altra in questo modo non si devono effettuare controlli sugli indici infatti quando l’indice i si riferisce ad un elemento della seconda sottosequenza (B) questa risulterà sicuramente  degli elementi di B ancora da elaborare

13 Il merge sort in C++ void mergesort(Item A[], int p, int r) { if (p

14 Il merge sort in C++ int main() { const int N=100; Item A[N]; for (int i=0; i

15 Il merge sort in C++ (l’algoritmo con template) template void merge(Item A[], int p, int q, int r){ const int MaxItems=1000; static Item aux[MaxItems]; int i,j; for (i = q+1; i > p; i--) aux[i-1] = A[i-1]; for (j = q; j < r; j++) aux[r+q-j] = A[j+1]; for (int k = p; k <= r; k++) if (aux[j] < aux[i]) A[k] = aux[j--]; else A[k] = aux[i++]; }

16 Il merge sort in C++ (l’algoritmo con template) template void mergesort(Item A[], int p, int r){ if (p

17 Il merge sort in C++ (l’algoritmo con template) int main(){ const int N=100; int A[N]; for (int i=0; i

18 Il merge sort in C++ (l’algoritmo con confronto generico) #include #include "Student.h" template void merge(Item A[], int p, int q, int r){ const int MaxItems=1000; static Item aux[MaxItems]; int i,j; Compare key_compare; // oggetto usato per il confronto for (i = q+1; i > p; i--) aux[i-1] = A[i-1]; for (j = q; j < r; j++) aux[r+q-j] = A[j+1]; for (int k = p; k <= r; k++) if (key_compare(aux[j],aux[i])) A[k] = aux[j--]; else A[k] = aux[i++]; }

19 Nota In sono incluse i seguenti oggetti funzioni: template struct less : public binary_function { bool operator()(const _Tp& __x, const _Tp& __y) const { return __x < __y; } }; // oltre a questa esistono anche (con l'ovvio significato) less_equal,greater, greater_equal, equal_to, not_equal_to

20 Il merge sort in C++ (l’algoritmo con confronto generico) template void mergesort(Item A[], int p, int r){ if (p

21 Il merge sort in C++ (l’algoritmo con confronto generico) class BigStudent: public Student{ private: string birthplace; string birthdate; string address1; string address2; string ; //... etc etc... public: BigStudent(int _id=0, string _name=""): Student(_id,_name) {} }; typedef BigStudent* BigStudentPtr; struct bigstudent_less{ bool operator()(const BigStudentPtr & x, const BigStudentPtr & y) const { return x->getid() getid(); } }; 124: // Ok adesso una breve dimostrazione 125: int main() 126: { 127: const int N=5; 128: // Array di 5 studenti 129: Student A[N]; 131: // esempio di valori : A[0] = Student(13, "Paolo"); 133: A[1] = Student(32, "Luca"); 134: A[2] = Student(15, "Giovanni"); 135: A[3] = Student(52, "Mario"); 136: A[4] = Student(34, "Gianni"); 138: // istanziazione del mergesort per Student con ordinamento decrescente 139: mergesort >(A,0,N-1); 140: for (int i=0; i

22 Il merge sort in C++ (l’algoritmo con confronto generico) int main(){ const int N=5; Student A[N]; // Array di 5 studenti // esempio di valori... A[0] = Student(13, "Paolo"); A[1] = Student(32, "Luca"); A[2] = Student(15, "Giovanni"); A[3] = Student(52, "Mario"); A[4] = Student(34, "Gianni"); mergesort >(A,0,N-1); for (int i=0; i

23 Il merge sort in C++ (l’algoritmo con confronto generico) // Oggetti grandi: conviene un array di puntatori!! BigStudentPtr Ap[N]; Ap[0] = new BigStudent(13, "Paolo"); Ap[1] = new BigStudent(32, "Luca"); Ap[2] = new BigStudent(15, "Giovanni"); Ap[3] = new BigStudent(52, "Mario"); Ap[4] = new BigStudent(34, "Gianni"); mergesort (Ap,0,N-1); for (int i=0; i

24 Il tempo di calcolo del merge sort Per semplificare l’analisi: –numero di elementi da ordinare sia una potenza di 2 –così per ogni applicazione del merg sort si lavora con sotto sequenze di lunghezza identica Tempo per elaborare sequenze di 1 elemento  (1) quando si hanno n>1 elementi –Divide: calcolo dell’indice mediano della sequenza  (1) –Impera: si risolvono ricorsivamente due sotto problemi di dimensione n/2, quindi 2T(n/2) –Combina: il tempo di calcolo per la procedura Merge fra due sotto sequenze di n/2 elementi è  (n)

25 Il tempo di calcolo del merge sort Si ha pertanto: T(n)=  (1)+2T(n/2)+  (n) = 2T(n/2)+  (n) O più precisamente: T(n)=  (1) per n=1 T(n)= 2T(n/2)+  (n) Vedremo che T(n)=  (n lg n)

26 Il tempo di calcolo di un algoritmo ricorsivo Per risolvere il calcolo di ricorrenze del tipo –T(n)=aT(n/b)+f(n) si usa il Teorema Principale (non lo dimostreremo) Le ricorrenze del tipo indicato descrivono i tempi di calcolo di algoritmi che –dividono un problema di dimensione n in a sottoproblemi di dimensione n/b –risolvono i sotto problemi in tempo T(n/b) –determinano come dividere e come ricomporre i sottoproblemi in un tempo f(n)

27 Il Teorema principale Sia T(n)=aT(n/b)+f(n) con a,b>1 e f(n) asintoticamente positiva allora T(n) può essere asintoticamente limitato –se f(n)=O(n log b a-e ) per qualche costante e allora T(n)=  (n log b a ) –se f(n)=  (n log b a ) allora T(n)=  (n log b a lg n) –se f(n)=  (n log b a+e ) per qualche costante e allora se af(n/b) n 0 allora T(n)=  (f(n))

28 Significato intuitivo Si confronta f(n) con n log b a. La soluzione è determinata dalla più grande fra le due funzioni se prevale n log b a allora la soluzione è  (n log b a ) –cioè il tempo è dominato dalla soluzione ricorsiva dei sottoproblemi se prevale f(n) allora la soluzione è  (f(n)) –cioè il tempo è dominato dalle procedure di divisione e ricombinazione delle soluzioni parziali se hanno lo stesso ordine di grandezza allora la soluzione è  (f(n)ln n)

29 Nota Cosa significa che una funzione prevale sull’altra? Nel primo caso non solo si deve avere f(n)< n log b a ma deve esserlo in modo polinomiale, cioè deve esserlo per un fattore n e per un qualche e Nel terzo caso f(n) deve essere polinomialmente più grande di n log b a e inoltre devono valere le condizioni di regolarità af(n/b)

30 Applicazione del Teorema Principale T(n)=2T(n/2)+n –f(n)=n, a=2, b=2 –n log b a = n log 2 2 =n –pertanto, dato che f(n)=  (n log b a )=  (n) allora (caso 2) –T(n)=(n lg n) T(n)=9T(n/3)+n –f(n)=n, a=9, b=3 –n log b a = n log 3 9 =n 2 –pertanto, dato che f(n)=O(n log b a-1 )=O(n 1 ) allora (caso 1) –T(n)=  (n 2 )

31 Applicazione del Teorema Principale T(n)=T(2n/3)+1 –f(n)=1, a=1, b=3/2 –n log b a = n log b 1 = n 0 =  (1) –pertanto, dato che f(n)=  (n log b a )=  (1) allora (caso 2) –T(n)=  (lg n) T(n)=3T(n/4)+n lg n –f(n)=n lg n, a=3, b=4 –n log b a = n log 4 3 =n –pertanto, dato che f(n)=(n log b a+e )=  (n 1 ) allora –se f(n) è regolare siamo nel caso 3 –af(n/b)

32 Applicazione del Teorema Principale T(n)=2T(n/2)+n lg n –f(n)=n lg n, a=2, b=2 –n log b a = n log 2 2 =n –pertanto, dato che f(n) è più grande di n ovvero f(n)=(n log b a+e )=  (n 1 ) allora –se f(n) è polinomialmente minore allora potremo essere nel caso 3 –deve essere f(n) / n log b a > n e per qualche e –ma f(n)/ n log b a =(n lg n)/n=lg n –lg n è sempre minore di n e per qualsiasi e –pertanto non possiamo usare il Teorema

33 Pile e code Pile e Code sono insiemi dinamici sono definite le operazioni di inserzione e cancellazione l’operazione di cancellazione elimina un elemento predefinito pile e code si distinguono per le politiche di eliminazione

34 Pila o Stack Il termine Pila (Stack) ed il nome delle operazioni di inserzione (push) e cancellazione (pop) derivano da quelli usati per descrivere le operazioni di caricamento per le pile di piatti a molla. Solo il piatto in cima può essere rimosso. Nuovi piatti vengono posti in cima. La politica di cancellazione è del tipo ultimo arrivato primo servito o LIFO (Last In First Out)

35 Implementazione di una Pila Una Pila si può realizzare con un vettore di n elementi S Un attributo top[S] indica la posizione dell’elemento inserito più di recente che è anche quello da cancellare quando top[S] è 0 si dice che la pila è vuota

36 Pseudo codice per le operazioni su Pila Stack-Empty(S) 1 if top[S]=0 2then return TRUE 3else return FALSE PUSH(S,x) 1 top[S]  top[S]+1 2 top[S]  x POP(S) 1 if not Stack-Empty(S) 2then top[S]  top[S]-1 3return S[top[S]+1]

37 Codice C++ #include using namespace std; template class ADT_Stack{ public: virtual void push(T)=0; virtual T pop()=0; };

38 Codice C++ template class Array_Stack: public ADT_Stack { public: Array_Stack(int usr_size=10):size(usr_size),top(0){data=new T[size];} ~Array_Stack(){delete[] data;} void push(T); T pop(); private: T * data; int size; int top; };

39 Codice C++ template void Array_Stack ::push(T item){ assert(top=0); return data[top]; }

40 Codice C++ int main(){ const int DIM=10; Array_Stack array_stack(DIM); int v[]={1,2,3,4,5,6,7,8,9,10,11}; ADT_Stack *stack=&array_stack; for(int i=0;i

41 Coda La politica di cancellazione per una coda è di tipo FIFO (First In First Out), primo arrivato primo servito, cioè il primo elemento ad essere stato inserito è anche il primo elemento ad essere eliminato La operazione di inserzione viene chiamata Enqueue e l’operazione di eliminazione Dequeue Quando un nuovo elemento viene inserito prende posto in fondo alla coda

42 Implementazione di una coda E’ possibile realizzare una coda con un vettore Q La coda ha attributi head[Q] e tail[Q] che rappresentano gli indici degli elementi in testa e in coda alla struttura L’attributo tail[Q] indica la posizione in cui sarà inserito il prossimo elemento Gli elementi della coda sono alle locazioni head[Q], head[Q]+1,…,tail[Q]-1 La locazione 1 segue la locazione n (ordine circolare) quando head[Q]=tail[Q] la coda è vuota quando head[Q]=tail[Q]+1 la coda è piena

43 Pseudocodice coda ENQUEUE(Q,x) 1 Q[tail[Q]]  x 2 if tail[Q]=length[Q] 3 then tail[Q]  1 4else tail[Q]  tail[Q]+1 DEQUEUE(x) 1 x  Q[head[Q]] 2 if head[Q]=length[Q] 3then head[Q]  1 4else head[Q]  head[Q]+1 5 return x

44 Visualizzazione operazioni su coda head[Q]=6tail[Q]= head[Q]=6 tail[Q]=3Inserzione degli elementi 3,4, head[Q]=8 tail[Q]=3 Cancellazione di 2 elementi


Scaricare ppt "Lezione 8 Ricorsione Code. Sommario Paradigma di programmazione Divide et Impera Ricorsione e tempo di calcolo Pile e code un ADT Pila."

Presentazioni simili


Annunci Google