Lez. 31 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Programmazione strutturata Metodologia top - down elemento minimo di un vettore sort per straight selection Copyright © by Claudio Salati.
2 PROGRAMMAZIONE STRUTTURA Opera top-down (e eventualmente per raffinamenti successivi (stepwise refinement)) DI UN PROBLEMA SONO NOTI PREMESSA E CONSEGUENZA GLI STATEMENT UTILIZZABILI SONO DATI e' data la loro semantica sono date le loro regole di s/composizione SE IL PROBLEMA NON E' RISOLUBILE CON UNA SINGOLA ISTRUZIONE, L'UNICA COSA POSSIBILE E' SCOMPORLO IN PROBLEMI PIU' SEMPLICI secondo le regole della sintassi / semantica degli statement utilizzabili determinando ad ogni stato di scomposizione premessa e conseguenza di ogni sotto-problema PREMESSE E CONSEGUENZE DEVONO ESSERE COERENTI FRA LORO, E CON LA SINTASSI/SEMANTICA DEGLI STATEMENT UTILIZZATI PER SCOMPORRE IL PROBLEMA
3 PROGRAMMAZIONE STRUTTURA AD ESEMPIO, SE SI USA LA REGOLA DI SCOMPOSIZIONE "SEQUENZA" DEL BLOCCO (linguaggio C): // {P} { // il blocco e' l'espansione di S // {P1}, e dovra' essere {P} {P1} S1; // {C1} {P2}, in effetti {C1} {P2} S2; // {C2}, e dovra' essere {C2} {C} } // {C} // {P} S; // {C} diventa
4 PROGRAMMAZIONE STRUTTURA UTILIZZANDO RICORSIVAMENTE LA STRATEGIA SI RICONDUCE IL PROBLEMA ORIGINALE A PROBLEMI ELEMENTARI RISOLUBILI DIRETTAMENTE Cioe risolubili utilizzando una singola istruzione semplice del linguaggio POICHE' IL PROBLEMA E' STATO RISOLTO APPLICANDO NELLA PROGETTAZIONE LE STESSE REGOLE CHE SI UTILIZZEREBBERO PER LA VERIFICA A POSTERIORI DEL PROGRAMMA, QUESTA NON E' PIU' NECESSARIA
5 PROGRAMMAZIONE STRUTTURA N. Wirth: Le tecniche di costruzione di un programma si basano su un unico principio: scomporre l'azione necessaria per risolvere un problema in azioni piu' semplici, e suddividere di conseguenza il problema in sottoproblemi N.B.: in realta' cio' non e' piu' sempre strettamente vero, ma e' vero per il tipo di problemi che affrontiamo in questo corso
6 PROGRAMMAZIONE STRUTTURA ogni passo di suddivisione deve verificare le seguenti condizioni: la scomposizione avviene secondo le regole sintattiche e semantiche delle istruzioni del linguaggio di programmazione utilizzato; la suddivisione scelta da' luogo a sottoproblemi piu' vicini agli strumenti disponibili, cioe' a sottoproblemi risolubili piu' direttamente nel linguaggio di programmazione; la soluzione dei sottoproblemi conduce alla soluzione generale. e' un procedimento che puo' richiedere backtracking se si finisce in un sottoproblema non risolubile o troppo complesso e' un procedimento dall'alto verso il basso (top-down)
7 ALTRE METODOLOGIE In pratica si usa e si usera' sempre di piu' il procedimento inverso: costruire programmi assemblando sottoprogrammi gia' a disposizione (o generati da toolkit di programmazione) anche perche' nella maggior parte dei casi la situazione non e' quella di dovere costruire piccoli programmi per risolvere problemi difficili, ma piuttosto quella di dovere costruire grandi programmi per problemi "facili" ma voluminosi i problemi difficili si trovano risolti in letteratura e nelle librerie si utilizza quindi un procedimento dal basso verso l'alto (bottom-up)
8 Es. 1: Selezione dell'elemento minimo di un vettore Dato un vettore v di N elementi interi, N 1, determinare l'indice dell'elemento (di un elemento) minimo. Premessa: P = { N 1, int v[N] } Conseguenza: C = { N 1, int v[N], 0 iMin<N, j=0..N-1 : v[j] v[iMin] } La premessa dichiara l'esistenza di un vettore v formato da N elementi interi, con N 1. La conseguenza dichiara che l'elemento di indice iMin del vettore e' tale per cui tutti gli elementi del vettore hanno valore maggiore o uguale ad esso. Notare che non tutte le proprieta' sono considerate nel progetto, e.g.: non viene dimostrato (ma si assume come scontato) che il vettore in uscita e' identico a quello in ingresso.
9 Es. 1: Selezione dell'elemento minimo di un vettore Strategia: si devono (e' necessario) esaminare tutti gli elementi del vettore; si scandisce ordinatamente il vettore, esaminandone un elemento alla volta, a partire dal suo primo elemento; si tiene memorizzato l'indice dell'elemento minimo tra quelli esaminati fino a quel momento; si confronta l'elemento sotto esame con quello minimo trovato fino a quel momento (se l'elemento sotto esame e' minore di questo e' anche minore di tutti gli altri gia' esaminati). Progettazione: poiche' esaminiamo in sequenza gli elementi del vettore definiremo una iterazione sugli elementi del vettore stesso; il nucleo della funzione sara' quindi costituito da una istruzione while.
10 Situazione all'(i+1)-esima iterazione del ciclo: i+1indice dell'elemento che si andra' ad esaminare v[0]..v[i]tratto del vettore gia' esaminato iindice dell'ultimo elemento che abbiamo esaminato iMinindice dell'elemento minimo del tratto di vettore gia' esaminato Es. 1: Selezione dell'elemento minimo di un vettore v[0]...v[iMin]v[i]v[i+1]v[N-1]... vettore v iMini
11 Es. 1: Selezione dell'elemento minimo di un vettore Complessita' del problema: per potere stabilire quale e' l'elemento minimo e' necessario che ogni elemento sia esaminato almeno una volta. il problema richiede almeno tanti passi quanti sono gli elementi del vettore: se n e' la dimensione del vettore la complessita' del problema e' di ordine n (si scrive (n): e' un lower-bound). Complessita' dell'algoritmo proposto: ogni elemento viene esaminato (confrontato) una e una sola volta. l'algoritmo prevede tanti passi quanti sono gli elementi del vettore: se n e' la dimensione del vettore la complessita' dell'algoritmo e' di ordine n (si scrive O(n) : e' un upper-bound). pertanto l'algoritmo proposto e' ottimo, non ci possono essere algoritmi migliori (al massimo ci potranno essere codifiche piu' o meno attente e algoritmi altrettanto efficienti).
12 Selezione dell'elemento minimo di un vettore: il torneo Si potrebbe fare diversamente? Si', si potrebbe organizzare un torneo ad eliminazione! Possiamo suddividere il vettore in coppie di elementi e confrontare tra loro gli elementi di ciascuna coppia; I vincitori del primo round possono essere di nuovo abbinati in tante coppie, e gli elementi di ogni coppia confrontati tra loro; Si puo' procedere cosi' fino ad individuare il vincitore assoluto. Complessita' dell'algoritmo Il primo round comporta n/2 confronti, il secondo round n/4 confronti, … In generale, il k-esimo round comporta n/2 k confronti. Quanti round sono necessari? log 2 n Complessita' complessiva:
13 Parentesi: sommatoria della progressione geometrica
14 Complessita dellalgoritmo del torneo a = n m = log 2 n x = ½ La sommatoria inizia da 1 e non da 0
15 Selezione dell'elemento minimo di un vettore: il torneo Torneo ad eliminazione: esercizio Scrivere in C una funzione che implementi l'algoritmo del torneo ad eliminazione per trovare l'indice dell'elemento minimo di un vettore. E' piu' facile/naturale una implementazione ricorsiva o una iterativa? Come si puo' dimostrare la correttezza di un programma ricorsivo? Come potreste indicare la complessita' del programma che avete scritto in funzione del numero di elementi del vettore? N.B.: non pensate all'algoritmo ma alla funzione C che avete scritto
16 Selezione dell'elemento minimo di un vettore: il torneo Torneo ad eliminazione: soluzione unsigned min (int v[], unsigned from, unsigned to) { if (from == to) return from; unsigned mid = (from+to)/2; unsigned min1 = min(v, from, mid); unsigned min2 = min(v, mid+1, to); if (v[min1]<=v[min2]) return min1; else return min2; } Complessita' T(n) = cost + 2 * T(n/2) se T>1 T(1) = cost
17 Elemento minimo di un vettore: per tentativi.1 Si potrebbe fare altrimenti (peggio)? Si', procedendo per tentativi. Confronto in sequenza ciascun elemento del vettore con tutti gli altri (il primo elemento con tutti gli altri, poi il secondo elemento con tutti gli altri, …) fino a che non ne trovo uno che e' minore o uguale a tutti, cioe' che e' un elemento minimo. Se n e' la dimensione del vettore, ogni elemento che viene esaminato e' sottoposto a n-1 confronti, e se l'elemento minimo e' l'ultimo posso dovere esaminare tutti gli n elementi del vettore. In totale posso dovere fare n*(n-1) confronti. Se n e' la dimensione del vettore la complessita' di questo algoritmo e' di ordine n 2 (si scrive O(n 2 )).
18 Elemento minimo di un vettore: per tentativi.2 Esercizio Scrivere in C una funzione che implementi questo algoritmo. Con quale conformazione di dati in ingresso il programma che avete scritto manifesta il suo comportamento peggiore? Se siete fortunati quanti confronti sono necessari? E in media? Come puo essere migliorato questo algoritmo pur senza cambiare la strategia? Devo veramente confrontare un elemento con gli elementi che lo precedono? Devo veramente confrontare un elemento con tutti gli elementi che lo seguono?
19 int findMinEl(int N, int v[]) { 0.1 // P = { N 1, int v[N] } // C = { N 1, int v[N], 0 iMin N-1, // j=0..N-1: v[j] v[iMin] } return iMin; 0.2 } 0.3 Es. 1: Selezione dell'elemento minimo di un vettore Ciclo while la premessa del ciclo (linvariante del ciclo) deve descrivere la situazione indicata nella strategia all'inizio si suppone di avere gia' esaminato il vettore nel tratto v[0]..v[0], e di avere quindi iMin==0: e' ovvio che v[0] e' l'elemento minimo del sotto-vettore v[0]..v[0] si inizia quindi l'analisi dal secondo elemento, se c'e (N.B.: lesistenza del primo elemento [di almeno un elemento] e garantita dalle precondizioni P
20 Notare che fino dall'inizio, e ad ogni iterazione, abbiamo gia' in mano l'elemento minimo. Solo che all'inizio, e fino a che non ho raggiunto la fine del ciclo (cioe' fino a che non ho finito di scandire l'intero vettore), l'elemento selezionato non e' il minimo dell'intero vettore ma solo di una sua parte, il prologo gia' esaminato. Notare anche che lelemento minimo iniziale (quello costruito dallinizializzazione del ciclo) e banale: lelemento minimo di un (sotto)vettore di lunghezza 1 e lunico elemento presente nel (sotto)vettore! Quello che deve fare il ciclo e': mantenere valido l'invariante (possiedo l'elemento minimo) arrivando a rendere falsa la condizione di controllo del ciclo (devo ancora esaminare degli elementi del vettore). Es. 1: Selezione dell'elemento minimo di un vettore
21 int findMinEl(int N, int v[]) { 2.1 // P = { N 1, int v[N] } int i = 0; 2.2 int iMin = 0; 2.3 // P1 = { N 1, int v[N], 0 iMin i, // 0 i N-1, j=0..i: v[j] v[iMin] } while ( B ) { 2.4 // P2 = P1 B S; 2.5 // P3 : deve essere uguale o implicare P1 } 2.6 // P1 B // C = { N 1, int v[N], 0 iMin N-1, // j=0..N-1: v[j] v[iMin] } return iMin; 2.7 } 2.8 Es. 1: Selezione dell'elemento minimo di un vettore
22 Progetto del test B: si vuole che: { P1 B } C si vuole che il test risulti FALSO quando ho gia' scandito l'intero vettore, VERO altrimenti quando e' che ho scandito l'intero vettore? Quando i=N-1 quando e' che mi restano ancora elementi da scandire? Quando i<N-1 poniamo: B ::= (i < N-1) { P1 B } = { N 1, int v[N], 0 iMin i, 0 i N-1, j=0..i: v[j] v[iMin], i N-1 } = { N 1, int v[N], 0 iMin i, j=0..i: v[j] v[iMin], i =N-1 } = { N 1, int v[N], 0 iMin N-1, j=0..N-1: v[j] v[iMin], i =N-1 } = C Es. 1: Selezione dell'elemento minimo di un vettore
23 Progetto dell'istruzione S: a questo punto e' nota anche P2: P2 = { P1 B } = { N 1, int v[N], 0 iMin i, 0 i N-1, j=0..i: v[j] v[iMin], i < N-1 } ad ogni iterazione e' necessario: 1.estendere il sottovettore prefisso gia' esaminato; 2.mantenere l'invariante del ciclo per il sottovettore prefisso esteso. estendere il sottovettore prefisso gia' esaminato equivale a incrementare i (e sicuramente possibile perche i<N-1). cio' corrisponde al fatto che durante l'iterazione si va ad esaminare un nuovo elemento. N.B.: Prima si progetta B poi si puo' progettare S. Infatti, e' solo quando si conosce B che si puo' indicare la precondizione di S. Es. 1: Selezione dell'elemento minimo di un vettore
24 Progetto dell'istruzione S (regola di s/composizione: blocco): // P2 = { P1 B } // = { N 1, int v[N], 0 iMin i, // 0 i<N-1, j=0..i: v[j] v[iMin]} i = i+1; // P4 = { N 1, int v[N], 0 iMin<i, // 0<i N-1, j=0..i-1: v[j] v[iMin]} S2; // P3 : deve essere uguale o implicare P1 infatti, sostituendo (i+1) a i in P4 si ottiene P2. N.B.: prima invento P4 in base a quello che voglio fare poi, in base a P2 e P4, invento i=i+1; ( in base alla semantica intuitiva dell'assegnamento) quindi la verifico utilizzando la sua semantica formale. Es. 1: Selezione dell'elemento minimo di un vettore
25 Progetto dell'istruzione S: sostituisco (i+1) a i in P4: { N 1, int v[N], 0 iMin<(i+1), 0<(i+1) N-1, j=0..(i+1)-1: v[j] v[iMin] } = { N 1, int v[N], 0 iMin i, 0 i<N-1, j=0..i: v[j] v[iMin] } = P2 Es. 1: Selezione dell'elemento minimo di un vettore
26 int findMinEl(int N, int v[]) { 3.1 // P = {... } int i = 0; int iMin = 0; 3.2 // P1 = { N 1, int v[N], 0 iMin i, // 0 i N-1, j=0..i: v[j] v[iMin] } while ( i < N-1 ) { 3.3 // P2={N 1, int v[N], 0 iMin i, // 0 i<N-1, j=0..i: v[j] v[iMin]} i += 1; 3.4 // P4={N 1, int v[N], 0 iMin<i, // 0<i N-1, j=0..i-1: v[j] v[iMin]} S2; // P3 : deve essere uguale o implicare P1 3.5 } 3.6 // C = {... } return iMin; 3.7 } 3.8 Es. 1: Selezione dell'elemento minimo di un vettore
27 Progetto dell'istruzione S2: dobbiamo avere un valore di iMin valido anche considerando il sottovettore v[0]..v[i] (cioe' anche l'elemento v[i]) si hanno due casi (quindi, regola di s/composizione: if): 1. v[i] < v[iMin] 2. v[i] v[iMin] // P4 = { N 1, int v[N], 0 iMin<i, // 0<i N-1, j=0..i-1: v[j] v[iMin]} if (v[i]<v[iMin]) S2-1; else S2-2; // P3 : deve essere uguale o implicare P1, // sia nel caso then che nel caso else Es. 1: Selezione dell'elemento minimo di un vettore
28 Progetto dell'istruzione S2: considerando la regola per le ramificazioni // P4 = { N 1, int v[N], 0 iMin<i, // 0<i N-1, j=0..i-1: v[j] v[iMin]} if (v[i]<v[iMin]) // P5={ N 1, int v[N], 0 iMin<i, // 0<i N-1, j=0..i-1: v[j] v[iMin], // v[i]<v[iMin]} S2-1; else // (v[i]>=v[iMin]) // P6={ N 1, int v[N], 0 iMin<i, // 0<i N-1, j=0..i-1: v[j] v[iMin], // v[i] v[iMin]} S2-2; // P3 Es. 1: Selezione dell'elemento minimo di un vettore
29 Progetto dell'istruzione S2: P6 = { N 1, int v[N], 0 iMin<i, 0<i N-1, j=0..i-1: v[j] v[iMin], v[i] v[iMin] } = { N 1, int v[N], 0 iMin<i, 0<i N-1, j=0..i: v[j] v[iMin] } quindi: P6 P1 quindi S2-2 non deve fare niente, puo' essere uno statement vuoto, e l'istruzione if-then-else puo' collassare in una istruzione if-then Es. 1: Selezione dell'elemento minimo di un vettore
30 Progetto dell'istruzione S2-1: assumiamo P3 = P1 // P4 = { N 1, int v[N], 0 iMin<i, // 0<i N-1, j=0..i-1: v[j] v[iMin]} if (v[i]<v[iMin]) // P5={ N 1, int v[N], 0 iMin<i, // 0<i N-1, j=0..i-1: v[j] v[iMin], // v[i]<v[iMin]} S2-1; // P3 = { N 1, int v[N], 0 iMin i, // 0 i N-1, j=0..i: v[j] v[iMin] } definiamo quindi S2-1 come iMin = i; e verifichiamo Es. 1: Selezione dell'elemento minimo di un vettore
31 Verifica dell'istruzione S2-1: sostituendo i a iMin in P3 si ottiene { N 1, int v[N], 0 i i, 0 i N-1, j=0..i: v[j] v[i] } che e' implicata da P5 = { N 1, int v[N], 0 iMin<i, 0<i N-1, j=0..i-1: v[j] v[iMin], v[i]<v[iMin]} essendo ovviamente v[i] v[i] e, per transitivita', j=0..i-1: v[j]>v[i] notare che con cio' abbiamo implicitamente completato la verifica della regola dell'if-then Es. 1: Selezione dell'elemento minimo di un vettore
32 int findMinEl(int N, int v[]) { 1 // P = { N 1, int v[N] } int i = 0; int iMin = 0; 2 // P1 = { N 1, int v[N], 0 iMin i, // 0 i N-1, j=0..i: v[j] v[iMin] } while ( i < N-1 ) { 3 i += 1; 4 // P4={N 1, int v[N], 0 iMin<i, // 0<i N-1, j=0..i-1: v[j] v[iMin]} if ( v[i] < v[iMin] ) 5 iMin = i; 6 } 7 // C = { N 1, int v[N], 0 iMin N-1, // j=0..N-1: v[j] v[iMin] } return iMin; 8 } 9 Es. 1: Selezione dell'elemento minimo di un vettore
33 int findMinEl(int N, int v[]) { 1 // P = { N 1, int v[N] } int i = 0; int iMin = 0; 2 // P1 = { N 1, int v[N], 0 iMin i, // 0 i N-1, j=0..i: v[j] v[iMin] } while ( i < N-1 ) { 3 // P2 ={ N 1, int v[N], 0 iMin i, // 0 i<N-1, j=0..i: v[j] v[iMin] } i += 1; 4 // P4 = {N 1, int v[N], 0 iMin<i, // 0<i N-1, j=0..i-1: v[j] v[iMin] } if ( v[i] < v[iMin] ) 5 // P5 = { N 1, int v[N], 0 iMin<i, 0<i N-1, // j=0..i-1: v[j] v[iMin], v[i]<v[iMin]} iMin = i; 6 // end if // P3 = { N 1, int v[N], 0 iMin i, // 0 i N-1, j=0..i: v[j] v[iMin] } } 7 // C = { N 1, int v[N], 0 iMin N-1, j=0..N-1: v[j] v[iMin]} return iMin; 8 } 9 Es. 1: Selezione dell'elemento minimo di un vettore
34 Es. 1: Selezione dell'elemento minimo di un vettore Terminazione dell'algoritmo QUALE GRANDEZZA N POSSIAMO DEFINIRE CHE sia MONOTONA DECRESCENTE all'esecuzione del ciclo, e sia tale che se la condizione risulta vera allora e' N 0? SIA N = (N-1-i)-1 LO STATEMENT 4, i = i + 1; GARANTISCE N MONOTONA DECRESCENTE i<N-1 ( N-1-i)-1 0, cioe` N 0 MENO FORMALMENTE BASTA NOTARE CHE i E' MONOTONA CRESCENTE ED N E' COSTANTE, E CHE IL CICLO TERMINA QUANDO i N-1
35 Es. 2: Ordinamento di un vettore Dato un vettore di N elementi interi, N 1, ordinarlo in modo non decrescente Premessa: P = { N 1, int v[N] } Conseguenza: C = { N 1, int v[N], j=0..N-2 : v[j] v[j+1] } La premessa dichiara l'esistenza di un vettore v formato da N elementi interi, con N 1. La conseguenza dichiara che ogni elemento di v (che abbia un elemento successivo) e' minore o uguale dell'elemento successivo. Notare che non tutte le proprieta' sono considerate nel progetto, e.g.: non viene dimostrato che il vettore in uscita contiene gli stessi elementi di quello in ingresso.
36 Es. 2: Sort di un vettore, strategia straight-selection Strategia: Si seleziona l'elemento minimo tra gli N elementi di v e lo si mette al primo posto (scambiandolo di posto con l'elemento v[0]); poi si seleziona l'elemento minimo tra gli ultimi N-1 elementi di v e lo si mette al secondo posto (scambiandolo di posto con l'elemento v[1]); poi si seleziona l'elemento minimo tra gli ultimi N-2 elementi di v e lo si mette al terzo posto (scambiandolo di posto con l'elemento v[2]); e cosi' via, fino a che si sono posizionati in modo ordinato tutti gli elementi di v. Ovviamente, tutto cio' lo faccio all'interno di un ciclo che scandisce N-1 volte il vettore v. Esercizio: perche' un ciclo di N-1 (e non di N) iterazioni?
37 Situazione all'(i+1)-esima iterazione del ciclo: v[0]..v[i-1]Tratto del vettore gia' ordinato in modo non decrescente. Non solo gli elementi del sottovettore v[0]..v[i-1] sono ordinati tra loro: ciascuno di essi e' di tutti gli elementi del sottovettore v[i]..v[N-1] iIndice della posizione nel vettore per la quale voglio selezionare l'elemento minimo all'interno del sottovettore non-ordinato v[i]..v[N-1] Es. 2: Sort di un vettore, strategia straight-selection v[0].. v[i-1] ordinatov[i]v[N-1]... vettore v i
38 Es. 2: Sort di un vettore, strategia straight-selection Progettazione: Gli elementi del vettore ordinato risultato sono determinati in sequenza (a partire dagli elementi del vettore in ingresso), da quello di indice 0 a quello di indice N-1. Per cui definiremo una iterazione sulle posizioni del vettore risultante (regola di s/composizione: while). Il nucleo del programma sara' quindi costituito da una istruzione while. La premessa dell'istruzione while (cosi' come il suo invariante) deve descrivere la situazione esistente all'inizio di ciascuna iterazione secondo la strategia che stiamo seguendo; All'inizio della prima iterazione supporremo di avere un sottovettore gia' ordinato di lunghezza 0 (il sottovettore vuoto v[0]..v[-1]).
39 Es. 2: Sort di un vettore, strategia straight-selection void straightSelection(int N, int v[]) { 1.1 // P= { N 1, int v[N] } int i = 0; 1.2 // P1={N 1, int v[N], 0 i N-1, // j=0..(i-1)-1: v[j] v[j+1], // ( m=0..i-1, k=i..N-1: v[m] v[k])} while ( B ) { 1.3 // P2 = P1 B S; 1.4 // P3 : deve essere uguale a P1 } 1.5 // P1 B // C= { N 1, int v[N], // j=0..(N-1)-1: v[j] v[j+1] } return; 1.6 } 1.7
40 Progetto del test B: si vuole che: { P1 B } C poniamo:B ::= (i < N-1) { P1 B } = { N 1, int v[N], 0 i N-1, j=0..(i-1)-1: v[j] v[j+1], ( m=0..i-1, k=i..N-1: v[m] v[k]), i N-1 } = { N 1, int v[N], j=0..(i-1)-1: v[j] v[j+1], ( m=0..i-1, k=i..N-1: v[m] v[k]), i =N-1 } = { N 1, int v[N], j=0..((N-1)-1)-1: v[j] v[j+1], ( m=0..(N-1)-1, k=(N-1)..N-1: v[m] v[k]), i =N-1 } = { N 1, int v[N], j=0..N-3: v[j] v[j+1], m=0..N-2: v[m] v[N-1], i =N-1 } C N.B.: abbiamo applicato la 1.a regola di implicazione Es. 2: Sort di un vettore, strategia straight-selection
41 Es. 2: Sort di un vettore, strategia straight-selection void straightSelection(int N, int v[]) { 2.1 // P = { N 1, int v[N] } int i = 0; 2.2 // P1={N 1, int v[N], 0 i N-1, // j=0..(i-1)-1: v[j] v[j+1], // ( m=0..i-1, k=i..N-1: v[m] v[k])} while (i < N-1) { 2.3 // P2={N 1, int v[N], 0 i<N-1, // j=0..(i-1)-1: v[j] v[j+1], // ( m=0..i-1, k=i..N-1: v[m] v[k])} S; // P3, che deve implicare P1 2.4 } 2.5 // C = { N 1, int v[N], // j=0..(N-1)-1: v[j] v[j+1] } return; 2.6 } 2.7
42 Progetto dell'istruzione S: ad ogni iterazione e' necessario incrementare i, ma prima di fare cio' bisogna: selezionare l'elemento minimo del sottovettore non ordinato v[i]..v[N-1] e metterlo all'i-esimo posto di v, scambiandolo con l'elemento v[i] notare che se (N==1) l'istruzione S; del ciclo while non viene eseguita nemmeno una volta: in questo caso il vettore v ha un unico elemento, v[0], ed e' quindi intrinsecamente ordinato! abbiamo quindi deciso di scomporre S; in tre sotto-istruzioni tramite la regola di s/composizione blocco: S; { S1; S2; S3; } con S3 ::= i = i+1; Es. 2: Sort di un vettore, strategia straight-selection
43 Progetto dell'istruzione S: void straightSelection(int N, int v[]) { 3.1 int i = 0; 3.2 // P1={N 1, int v[N], 0 i N-1, // j=0..(i-1)-1: v[j] v[j+1], // ( m=0..i-1, k=i..N-1: v[m] v[k])} while (i < N-1) { // P2= { P1 i<N-1 } 3.3 S1; 3.4 // P4 S2; 3.5 // P5 i = i + 1; 3.6 //P3 } 3.7 return; 3.8 } 3.9 Es. 2: Sort di un vettore, strategia straight-selection
44 Progetto dell'istruzione S: P5 si ricava da P3 (cioe' da P1) e dall'istruzione i=i+1; Assumiamo P3 = P1 (i>0); sostituendo i+1 a i in P3 si ottiene: P5 = { N 1, int v[N], 0<(i+1) N-1, j=0..((i+1)-1)-1: v[j] v[j+1], ( m=0..(i+1)-1, k=(i+1)..N-1: v[m] v[k] ) }= { N 1, int v[N], 0 i<N-1, j=0..i-1: v[j] v[j+1], ( m=0..i, k=(i+1)..N-1: v[m] v[k] ) } Es. 2: Sort di un vettore, strategia straight-selection
45 Progetto dell'istruzione S: di S1; si e' detto che deve identificare l'elemento minimo del sottovettore v[i]..v[N-1]: noi abbiamo gia' una funzione che fa questo lavoro! IN GENERALE UN PROGRAMMA E' COMPOSTO DI SOTTOPROGRAMMI UNA VOLTA CHE UN SOTTOPROGRAMMA E' VERIFICATO, SE NE SODDISFANO LE PRECONDIZIONI, E LA SUA CONSEGUENZA CI VA BENE, NON E' NECESSARIO RIVERIFICARLO TUTTE LE VOLTE CHE LO SI USA Es. 2: Sort di un vettore, strategia straight-selection
46 Progetto dell'istruzione S1: definiamo S1 come: int kMin = i + findMinEl(N-i, &v[i]); poiche' P2 garantisce i<N-1, le precondizioni di findMinEl() sono soddisfatte (v[i]..v[N-1] e' un vettore con almeno 2 elementi); pertanto la conseguenza di findMinEl() garantisce che v[kMin] sia l'elemento minimo del vettore di N-i elementi v[i]..v[N-1] possiamo quindi assumere che sia P4 = P2 ( k=i..N-1: v[kMin] v[k]) Es. 2: Sort di un vettore, strategia straight-selection
47 In realta' findMinEl() e' utilizzabile nel contesto di straightSelection() solo grazie alla debolezza della nozione di array in C; infatti : cio' che le passiamo in ingresso non e' un vettore, ma l'indirizzo di un elemento di un vettore v[], che noi consideriamo essere il primo elemento del (sotto)vettore di lunghezza N-i di cui vogliamo localizzare l'elemento minimo; e cio' che ritorna findMinEl() e' l'indice dell'elemento minimo relativamente all'inizio del (sotto)vettore (cioe' all'elemento di indice i del vettore), e se vogliamo l'indice dell'elemento minimo del sottovettore v[i]..v[N-1] all'interno di v[] dobbiamo sommare i a quanto tornato da findMinEl() ; Ridefinizione di findMinEl()
48 meglio sarebbe ridefinire findMinEl() in modo da liberarsi dalla dipendenza linguistica dal C, e da farci ritornare direttamente l'indice dell'elemento minimo relativo all'inizio del vettore v[]. int findMinEl(int from, int to, int v[]); // P= { 0 from to, int v[size] : to size-1 } // C= { 0 from to, int v[size] : to size-1, // k=from..to: v[return] v[k] } Esercizio: riscrivere findMinEl() esplicitando premessa, conseguenza e invariante del ciclo che realizza la funzione Ridefinizione di findMinEl()
49 Progetto dell'istruzione S2: // P2={N 1, int v[N], 0 i N-1, // j=0..(i-1)-1: v[j] v[j+1], // ( m=0..i-1, k=i..N-1: v[m] v[k])} int kMin = i + findMinEl(N-i, &v[i]); // P4={N 1, int v[N], 0 i<N-1, // j=0..(i-1)-1: v[j] v[j+1], // ( m=0..i-1, k=i..N-1: v[m] v[k]), // k=i..N-1: v[kMin] v[k]} S2; // P5={N 1, int v[N], 0 i<N-1, // j=0..i-1: v[j] v[j+1], // ( m=0..i, k=(i+1)..N-1: v[m] v[k])} i = i + 1; // P3={N 1, int v[N], 0<i N-1, // j=0..(i-1)-1: v[j] v[j+1], // ( m=0..i-1, k=i..N-1: v[m] v[k])} Es. 2: Sort di un vettore, strategia straight-selection
50 Progetto dell'istruzione S2: definiamo S2 come: { int temp = v[kMin]; v[kMin] = v[i]; v[i] = temp; } e verifichiamo che P4 derivi correttamente da P5. notiamo come in P5 si possa riscrivere { ( m=0..i, k=(i+1)..N-1: v[m] v[k]) } 1 come (ovviamente e': v[i] v[i]) { ( m=0..i-1, k=(i+1)..N-1: v[m] v[k]), 1.1 k=i..N-1: v[i] v[k] } 1.2 e { j=0..i-1: v[j] v[j+1] } 2 come { j=0..i-2: v[j] v[j+1], 2.1 m=0..i-1: v[m] v[i] } 2.2 Es. 2: Sort di un vettore, strategia straight-selection
51 Verifica dell'istruzione S2: riscrivendo P5 (fondendo 1.1 e 2.2): { N 1, int v[N], 0 i<N-1, j=0..i-2: v[j] v[j+1], ( m=0..i-1, k=i..N-1: v[m] v[k]), k=i..N-1: v[i] v[k] } e scambiando tra loro v[i] e v[kMin]: { N 1, int v[N], 0 i<N-1, j=0..i-2: v[j] v[j+1], ( m=0..i-1, k=i..N-1: v[m] v[k] ), k=i..N-1: v[kMin] v[k] } = P4 Es. 2: Sort di un vettore, strategia straight-selection
52 Progetto dell'istruzione S2 - alternativa: avremmo anche potuto definire S2 come: if (kMin!=i) { int temp = v[kMin]; v[kMin] = v[i]; v[i] = temp; } infatti, se kMin==i P4 P5 Es. 2: Sort di un vettore, strategia straight-selection
53 void straightSelection(int N, int v[]) { 1 // P= { N 1, int v[N] } int i = 0; 2 // P1={N 1, int v[N], 0 i N-1, // j=0..(i-1)-1: v[j] v[j+1], // ( m=0..i-1, k=i..N-1: v[m] v[k])} while (i < N-1) { 3 int kMin = i + findMinEl(N-i, &v[i]); 4 int temp = v[kMin]; 5 v[kMin] = v[i]; 6 v[i] = temp; 7 i = i + 1; 8 } 9 // C= { N 1, int v[N], // j=0..(N-1)-1: v[j] v[j+1] } return; 10 } 11 Es. 2: Sort di un vettore, strategia straight-selection
54 Terminazione dell'algoritmo La terminazione dell'algoritmo e' garantita perche': E' garantita la terminazione del ciclo di selezione dell'elemento minimo (gia' dimostrato) E' garantita la terminazione del ciclo esterno poiche' N e' costante, i e' monotona crescete ad ogni iterazione, e quindi la condizione di controllo del while sara' necessariamente falsificata (in particolare, dopo N-1 iterazioni) Formalmente, la funzione monotona decrescente di terminazione e': N = N-2-i Es. 2: Sort di un vettore, strategia straight-selection
55 Complessita' dell'algoritmo Quanti passi (confronti) richiede l'algoritmo? –N-1 confronti (all'interno di findMinEl() ) durante la prima iterazione del ciclo esterno; –N-2 confronti durante la seconda iterazione del ciclo esterno; –e cosi' via … cioe' un numero di confronti pari a quindi l'algoritmo e' di complessita' O(n 2 ) si puo' fare di meglio? Si'! Es. 2: Sort di un vettore, strategia straight-selection