Lez. 41 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Programmazione strutturata sort per straight exchange o bubblesort Copyright © by Claudio Salati.
2 Che fare? Procedere in modo formale come si e' fatto nelle lezioni 2 e 3 e' troppo pesante Noi continueremo a dimostrare la correttezza e la terminazione degli algoritmi che andremo ad esaminare, ma senza utilizzare il formalismo della semantica assiomatica Il quadro delle Premesse-Conseguenze restera' lo stesso, ma il passaggio dalle une alle altre non sara' per operazioni formali sul loro testo ma solo per ragionamento informale sulle (sulla semantica intuitiva delle) istruzioni del nostro programma. I nostri ragionamenti saranno basati su due metodi fondamentali: dimostrazione per assurdo dimostrazione per induzione matematica
3 Il principio di induzione matematica Consideriamo una asserzione P sull'intero n. Ad esempio: P (n) = "La somma dei primi n numeri dispari e' uguale a n 2." Si puo' dimostrare che P (n) e' vera per tutti gli interi positivi n nel modo seguente: si prova che P (1) e' vera (base dell'induzione) si prova che, se sono vere P (1), P (2), …, P (n) per un qualunque valore n, allora e' vera anche P (n+1) (passo induttivo) La stessa cosa si puo' fare ovviamente (in modo analogo) per dimostrare P (n) per tutti gli interi non-negativi: basta considerare come base P (0) anziche' P (1) Per dare una dimostrazione per induzione matematica il primo passo e' individuare su che cosa (su quale quantita') effettuare l'induzione: e.g. il numero degli elementi di un vettore.
4 Completiamo l'esempio. Tesi: per ogni intero n 1 vale P(n), dove: P(n) = "La somma dei primi n numeri dispari e' uguale a n 2." Dimostrazione: –possiamo scrivere P(n) come: –Base dell'induzione: P(1) e' vera, infatti 1 = 1 2. –Passo induttivo: Se P(k) e' vera per tutti i k da 1 a n, allora e' vera in particolare per k=n, quindi possiamo assumere (ipotesi induttiva) che P(n) sia vera. Dimostriamo che di conseguenza P e' vera per n+1. Sommiamo (2 * (n+1) - 1), cioe' (2 * n + 1) ai due lati di P(n): il lato sinistro diventa ovviamente per il lato destro: n * n + 1 = (n + 1) 2 QED Il principio di induzione matematica
5 Per effettuare una dimostrazione per induzione matematica occorre: stabilire su quale quantita' si applica l'induzione: la lunghezza della sequenza dei primi numeri dispari esprimere il teorema nei termini della quantita' su cui si applica l'induzione P(n) = "La somma dei primi n numeri dispari e' uguale a n 2." stabilire per quale valore della quantita' su cui si applica l'induzione si ha la condizione base dell'induzione sequenza dei primi numeri dispari di lunghezza 1 dimostrare direttamente il teorema nella condizione base dell'induzione esplicitare l'ipotesi induttiva e il passo induttivo Se P(k) e' vera per tutti i k da 1 a n, allora e' vera in particolare per k=n, quindi possiamo assumere (ipotesi induttiva) che P(n) sia vera. Bisogna di conseguenza dimostrare la verita di P(n+1). dimostrare il passo induttivo N.B.: quando si invoca l'ipotesi induttiva bisogna fare vedere che essa e' applicabile. Il principio di induzione matematica
6 Consideriamo la seguente funzione: unsigned fattoriale(unsigned int n) { // NB: unsigned int n 0 return (n==0 ? 1 : (n * fattoriale(n-1)); } Tesi: essa calcola correttamente la funzione n!. (nell'ipotesi di ignorare i vincoli di architettura (sizeof(unsigned))) Dimostrazione (per induzione matematica): Base dell'induzione: se n=0 la funzione ritorna 1, cioe' 0!. Passo induttivo: consideriamo un qualunque valore n>0. Poiche' n 0 la funzione ritorna n*fattoriale(n-1) Per ipotesi induttiva fattoriale(n-1) = (n-1)! Quindi la funzione ritorna n * (n-1)! = n! QED Principio di induzione matematica: esempio 2
7 su quale quantita' si applica l'induzione? Il valore dell'intero passato come parametro di ingresso alla funzione il teorema nei termini della quantita' su cui si applica l'induzione: fattoriale(unsigned n) calcola correttamente n! per qualunque valore k 0 in ingresso per quale valore della quantita' su cui si applica l'induzione si ha la condizione base dell'induzione? n=0 dimostrare direttamente il teorema nella condizione base dell'induzione N.B.: La dimostrazione deve fare riferimento al testo della funzione. Se n=0 la condizione di controllo dell'operatore triadico ?: e' vera, per cui il risultato dell'espressione e' il valore del secondo operando dell'operatore. Continua alla pagina seguente Principio di induzione matematica: esempio 2
8 ipotesi induttiva e passo induttivo fattoriale(unsigned n) calcola correttamente n! quando il parametro di ingresso ha valore <k. Il passo induttivo dimostra che fattoriale(unsigned n) calcola correttamente n! quando il parametro di ingresso ha valore =k, con k>0 (il caso base e' gia' dimostrato). dimostrare il passo induttivo N.B.: la dimostrazione deve fare riferimento al testo della funzione. Quando si invoca l'ipotesi induttiva (chiamata ricorsiva) occorre fare vedere che essa e' applicabile (nel nostro caso, che il parametro di ingresso passatole rientra tra i valori coperti dall'ipotesi induttiva, cioe' e' <k) che la chiamata ricorsiva soddisfa le precondizioni imposte dalla funzione (in particolare, che il parametro di ingresso e' 0, il che e' garantito essendo =k-1 con k>0 ). Principio di induzione matematica: esempio 2
9 Dimostrare tramite l'applicazione del principio di induzione matematica la validita' delle seguenti formule: Dimostrare tramite l'applicazione del principio di induzione matematica la correttezza dellaalgoritmo del torneo per lidentificazione dellelemento minimo di un vettore. Principio di induzione matematica: esercizi
10 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. N.B.: si e' omesso di specificare che il vettore di uscita deve essere una permutazione di quello di ingresso
11 Sort di un vettore, strategia straight-exchange Strategia: ALLi-ESIMA ITERAZIONE IL SOTTOVETTORE v[0]..v[i-2] E' GIA' ORDINATO (globalmente, rispetto allintero v) SI ANALIZZANO GLI ELEMENTI DEL SOTTOVETTORE v[i-1]..v[N-1] PARTENDO DAL FONDO, SI SCANDISCE UNO PER UNO CIASCUN ELEMENTO DEL SOTTOVETTORE E LO SI CONFRONTA CON QUELLO PRECEDENTE SI SCAMBIANO TRA LORO GLI ELEMENTI ADIACENTI TALI CHE v[k]<v[k-1], con i k N-1 COSI' ALL'i-ESIMA ITERAZIONE VIENE SISTEMATO DEFINITIVAMENTE LELEMENTO DI INDICE i-1 (attraverso una sequenza di scambi con elementi adiacenti)
12 STRAIGHT EXCHANGE o BUBBLESORT Perche bubblesort? Perche gli elementi piu piccoli risalgono ad ogni iterazione dal fondo del vettore verso linizio del vettore; in particolare, lelemento piu piccolo della parte di vettore non ancora ordinata emerge fino a raggiungere la sua posizione definitiva nel vettore ordinato, quindi, la posizione immediatamente successiva allultima del sottovettore gia ordinato in precedenza. Alli-esima iterazione si fa emergere fino alla posizione i-1 lelemento che deve occupare quella posizione nel vettore ordinato. N.B.: la risalita e' fino alla posizione i-1 perche' il sottovettore v[0..i-2] e' gia' ordinato e i suoi elementi sono tutti degli elementi del sottovettore v[i-1..N-1] Un valore risale dal fondo lungo il vettore fino a che incontra un elemento minore o uguale a lui: a quel punto si ferma ed e' questo secondo valore che inizia una analoga risalita.
13 L'algoritmo L3-annex.doc fornisce la descrizione del progetto formale (con il metodo top-down e la pratica delle asserzioni) dell'algoritmo Qui ne vediamo la versione informale; La strategia ci dice che la funzione bubbleSort() e' organizzata come una coppia di cicli innestati tra loro Il ciclo esterno serve a estendere la parte (il prefisso) del vettore gia' ordinata Il ciclo interno serve a fare emergere nella cella del vettore immediatamente successiva alla parte gia' ordinata l'elemento minimo della parte di vettore ancora non ordinata Bubblesort: la strategia
14 Situazione all'i-esima iterazione del ciclo esterno: v[0]..v[i-2]tratto del vettore gia' globalmente (quindi definitivamente) ordinato in modo non decrescente. Non solo gli elementi del sottovettore v[0]..v[i-2] sono ordinati tra loro: ciascuno di essi e' di tutti gli elementi del sottovettore v[i-1]..v[N-1] i-1indice della posizione del vettore per la quale voglio far emergere l'elemento minimo all'interno del sottovettore non-ordinato v[i-1]..v[N-1] Bubblesort: l'algoritmo v[0].. v[i-2] ordinatov[i]v[N-1]... vettore v i v[i-1]
15 Quando termina il ciclo esterno? Occorre che il vettore sia completamente ordinato. Il che avviene quando tutti gli elementi del sottovettore v[0]..v[N-2] sono globalmente ordinati. Cioe' quando i = (N-2)+2 = N. N.B.: poiche' l'elemento v[N-1] e' maggiore o uguale di tutti gli elementi del sottovettore v[0]..v[N-2] si ha che l'intero vettore v[0]..v[N-1] e' ordinato. Bubblesort: l'algoritmo v[0].. v[N-2] ordinatov[N-1] vettore v i=N
16 Situazione alla k-esima iterazione del ciclo interno: v[0]..v[i-2]tratto del vettore gia' globalmente (quindi definitivamente) ) ordinato in modo non decrescente. i-1indice della posizione del vettore per la quale voglio far emergere l'elemento minimo all'interno del sottovettore non-ordinato v[i-1]..v[N-1] scanindice della posizione fino alla quale ho gia' processato (dal fondo) il sottovettore non ordinato v[i-1]..v[N-1], facendo emergere nella posizione scan l'elemento minimo del suffisso v[scan]..v[N-1] Bubblesort: l'algoritmo v[0].. v[i-2] ordinatov[scan]v[N-1]... vettore v scan v[i-1]...
17 Quando termina il ciclo interno? Quando in v[i-1] e' emerso l'elemento minimo del sottovettore (non ordinato) v[i-1]..v[N-1]. Quindi quando e': scan = i-1 Bubblesort: l'algoritmo v[0].. v[i-2] ordinatov[scan]v[N-1]... vettore v scan v[i-1]...
18 Dalla descrizione della strategia discendono: la seguente scomposizione del programma gli invarianti P1 e P2 le condizioni di controllo dei due cicli innestati void bubbleSort(int N, int v[]) { 1 S0; 2 // P1= {P; i N; ( j: 0<j i-2) v[j-1] v[j]; // ( j, k: 0 j i-2, i-1 k N-1) v[j] v[k]} while (i <= N-1) { 3 S1; 4 // P2={P1; i<=N-1; (i-1) scan N-1; // ( j: scan j N-1) v[scan] v[j] } while (scan >= i) { 5 S2; 6 } // end while 7 i += 1; 8 } // end while 9 } 10 Bubblesort: l'algoritmo
19 void bubbleSort(int N, int v[]) { 1 int i = 1; // P1= {P; i N; ( j: 0<j i-2) v[j-1] v[j]; // ( j, k: 0 j i-2, i-1 k N-1) v[j] v[k]} while (i <= N-1) { 2 int scan = N-1; 3 // P2={P1; i<=N-1; (i-1) scan N-1; // ( j: scan j N-1) v[scan] v[j] } while (scan >= i) { 4 if (v[scan-1]>v[scan]) { 5 int x = v[scan-1]; 6 v[scan-1] = v[scan]; 7 v[scan] = x; 8 } // end if 9 scan -= 1; 10 } // end while 11 i += 1; 12 } // end while 13 } 14 Bubblesort: l'algoritmo
20 Correttezza Lemma: il ciclo interno fa emergere l'elemento minimo del sottovettore v[i-1]..v[N-1] nella posizione i-1 Dimostrazione: per induzione matematica sul numero di cicli e quindi sul valore di scan Base dell'induzione: prima di entrare nel ciclo (riga 4) la prima volta scan N-1, ed e' quindi banalmente vero che ( j: scan j N-1) v[scan] v[j] Assumiamo per ipotesi induttiva che la proprieta' valga all'inizio di una qualunque iterazione: durante questa, se v[scan-1]>v[scan] i due elementi vengono scambiati (righe 6-8); in ogni caso, dopo l'istruzione if (riga 10) : v[scan-1] v[scan] e quindi anche, per ipotesi induttiva, ( j: scan j N-1) v[scan-1] v[j] l'invariante e' ripristinato decrementando scan. QED Bubblesort: correttezza
21 Lemma, Dimostrazione per induzione matematica: su quale quantita' si applica l'induzione? sulla lunghezza del sottovettore finale di cui v[scan] e' l'elemento minimo (data da N-1-scan ) il teorema nei termini della quantita' su cui si applica l'induzione: P(N-1-scan) = v[scan] e' l'elemento minimo del sottovettore v[scan..N-1] per quale valore di N-1-scan si ha la condizione base dell'induzione? N-1-scan=0, cioe' per un sottovettore di lunghezza 1 dimostrare direttamente il teorema nella condizione base dell'induzione v[N-1] e' l'elemento minimo del sottovettore v[N-1..N-1] esplicitare l'ipotesi induttiva e il passo induttivo ipotesi induttiva: ( j: scan j N-1) v[scan] v[j] passo induttivo: ( j: scan-1 j N-1) v[scan-1] v[j] con scan i dimostrare il passo induttivo Bubblesort: correttezza
22 Correttezza nel momento in cui il ciclo interno termina e': scan=i-1 e in v[i-1] (cioe' in v[scan] ) c'e' l'elemento minimo del sottovettore v[i-1..N-1] cioe' ( j: i-1 j N-1) v[i-1] v[j] che e' la proprieta' che deve essere garantita dall'esecuzione del ciclo interno Bubblesort: correttezza
23 Riscriviamo in modo ricorsivo la procedura di emersione: void bubbleUp (int v[], int low, 1 int high, int scan) { // pre.1 = ( j: scan j high) v[scan] v[j] // pre.2 = low scan high if (scan>low) { 2 if (v[scan] < v[scan-1]) { 3 int x = v[scan-1]; 4 v[scan-1] = v[scan]; 5 v[scan] = x; 6 } // end if 7 // ( j: scan-1 j high) v[scan-1] v[j] bubbleUp (v, low, high, scan-1); 8 } 9 // ( j: low j high) v[low] v[j] return; 10 } 11 Principio di induzione matematica e iterazione
24 Situazione con low<scan<high : v[0..low-1]tratti del vettore irrilevanti v[high+1..N-1] lowindice della posizione del vettore per la quale voglio far emergere l'elemento minimo all'interno del sottovettore non-ordinato v[low..high] scanal momento della attivazione di bubbleUp() e' garantito dalla precondizione pre.1 che v[scan] e' l'elemento minimo del sottovettore non- ordinato v[scan..high] bubbleUp()... vettore v... scan... low... high
25 Lo scopo di bubbleUp() e' di fare emergere in low l'elemento minimo del sottovettore v[low..high] In particolare quello che si vuole (ad esempio nel caso della funzione cliente bubbleSort() ) e' che la chiamata bubbleUp(v, low, high, high); effettuata senza alcuna precondizione (eccetto pre.2) sul sottovettore v[low..high], faccia emergere in low l'elemento minimo del sottovettore. In realta', quando scan=high, entrambe le precondizioni di bubbleUp() sono verificate (la prima in modo banale). Quindi possiamo riformulare la nostra tesi come: bubbleUp(), quando chiamata correttamente (con le sue precondizioni verificate), opera correttamente (per tutti i valori legali di scan, cioe' per scan=low..high ) Correttezza di bubbleUp()
26 Dimostrazione di correttezza per induzione di bubbleUp() - parte 1 su quale quantita' si applica l'induzione? sulla lunghezza del sottovettore v[low]..v[scan] il teorema nei termini della quantita' su cui si applica l'induzione: bubbleUp() posiziona in v[low] l'elemento minimo del sottovettore v[low]..v[high], per qualunque valore di scan nel range low..high purche' sia chiamata con soddisfatta precondizione pre.1 per quale valore di scan si ha la condizione base dell'induzione? scan==low dimostrare direttamente il teorema nella condizione base dell'induzione immediata dalla precondizione pre.1: in questo caso la funzione non fa (e non deve fare) niente! Correttezza di bubbleUp()... vettore v... low=scan... high
27 Dimostrazione di correttezza per induzione di bubbleUp() - parte 2 esplicitare l'ipotesi induttiva e il passo induttivo ipotesi induttiva: bubbleUp (v, low, high, scan), essendo soddisfatta la precondizione pre.1, opera correttamente per ogni valore k di scan tale che sia low k m high passo induttivo: bubbleUp (v, low, high, scan), essendo soddisfatta la precondizione pre.1, opera correttamente per il valore di scan m+1, con m+1 high (per cui in questa condizione e' scan>low ) dimostrare il passo induttivo In questo caso il corpo dell'if di riga 2 viene eseguito dopo l'esecuzione dell'if di riga 3, in ogni caso, v[scan-1] e' minore di tutti gli elementi successivi del vettore la chiamata ricorsiva di riga 8 soddisfa tutte le precondizioni della funzione e si applica su un sottovettore di lunghezza strettamente minore, per cui risulta applicabile l'ipotesi induttiva Correttezza di bubbleUp()
28 Le due versioni, iterativa e ricorsiva, della procedura di emersione sono evidentemente correlate tra loro Anche le loro dimostrazioni per induzione matematica lo sono! anche se operano in modo speculare In realta' un ciclo rappresenta una dimostrazione costruttiva del principio di induzione matematica Quando parliamo di induzione matematica su un ciclo ci riferiamo in realta' alla dimostrazione che il ciclo mantiene il proprio invariante Analogie tra struttura del ciclo e dimostrazione per induzione inizializzazione del ciclo condizione base dell'induzione invariante del ciclo ipotesi induttiva iterazione passo induttivo terminazione condizione base dell'induzione Principio di induzione matematica e iterazione
29 Correttezza bubbleSort() ordina il vettore in input in modo non decrescente Dimostrazione: per induzione matematica sul numero di cicli e quindi sul valore di i Base dell'induzione: prima di entrare nel ciclo la prima volta i vale 1, ed e' quindi banalmente vero che ( j: 0<j i-2) v[j-1] v[j]; ( j, k: 0 j i-2, i-1 k N-1) v[j] v[k] Assumiamo per ipotesi induttiva che la proprieta' valga anche all'inizio di una qualunque iterazione Durante l'iterazione, alla fine del ciclo while interno, per il lemma vale anche ( j: i j N-1) v[i-1] v[j], e quindi ( j: 0<j i-1) v[j-1] v[j]; ( j, k: 0 j i-1, i k N-1) v[j] v[k] l'invariante e' ripristinato incrementando i. Bubblesort: correttezza
30 Correttezza Al momento della terminazione del ciclo e': i==N Unitamente all'invariante questo garantisce che sia ( j: 0<j N-2) v[j-1] v[j]; ( j, k: 0 j N-2, N-1 k N-1) v[j] v[k] E quindi ( j: 0<j N-1) v[j-1] v[j]; Per cui il vettore v[] e' completamente ordinato. QED Bubblesort: correttezza
31 Terminazione dell'algoritmo LA TERMINAZIONE DELL'ALGORITMO E' GARANTITA PERCHE': E' GARANTITA LA TERMINAZIONE DEL CICLO INTERNO: scan decresce ad ogni iterazione e i rimane fisso E' GARANTITA LA TERMINAZIONE DEL CICLO ESTERNO POICHE' N E' COSTANTE i E' MONOTONA CRESCENTE AD OGNI ITERAZIONE E QUINDI LA CONDIZIONE DI CIASCUN WHILE SARA' NECESSARIAMENTE FALSIFICATA (in particolare, per il ciclo esterno, dopo N-1 iterazioni) Bubblesort: terminazione
32 Complessita' dell'algoritmo Quanti passi (confronti) richiede l'algoritmo? N-1 confronti (all'interno del ciclo interno) durante la prima iterazione del ciclo esterno (e possibilmente anche N-1 scambi, ciascuno dei quali costa 3 assegnamenti); N-2 confronti (e possibilmente scambi) durante la seconda iterazione del ciclo esterno; e cosi' via … cioe', un totale di operazioni pari a quindi l'algoritmo e' di complessita' O(n 2 ) Bubblesort: complessita'
33 Avremmo potuto riformulare la strategia dell'algoritmo di bubblesort nel modo seguente: v[0]..v[i-1] : tratto del vettore gia' globalmente (quindi definitivamente) 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] i : indice della posizione del vettore per la quale voglio far emergere l'elemento minimo all'interno del sottovettore non- ordinato v[i]..v[N-1] Riscrivere la funzione bubblesort() e i relativi invarianti secondo questa riformulazione. Bubblesort: esercizio
34 Esiste anche una versione "volgare" di bubblesort: void dummyBubbleSort(int N, int v[]) { int thereWasAnExchange = 1; // = true while ( thereWasAnExchange ) { thereWasAnExchange = 0; // = false int scan = N-1; while (scan >= 1) { if (v[scan-1]>v[scan]) { thereWasAnExchange = 1; // = true int x = v[scan-1]; v[scan-1] = v[scan]; v[scan] = x; } // end if scan -= 1; } // end while } Bubblesort: versione volgare
35 Questa versione di bubblesort ne nasconde la logica vera la sua strategia apparente e': fino a che il vettore non e' ordinato (cioe' fino a che c'e' almeno una coppia di elementi adiacenti che non rispetta la relazione d'ordine) lo scandisco tutto, scambiando tra loro ciascun elemento e il suo adiacente se non e' rispettata la relazione d'ordine la condizione di terminazione del ciclo e': la relazione d'ordine e' rispettata per tutte le coppie di elementi adiacenti (e di conseguenza il vettore e' ordinato). Questa condizione e' rilevata perche' si e' fatta un'intera passata sul vettore senza effettuare alcuno scambio: quindi non c'era nessun elemento non ordinato rispetto agli elementi adiacenti c'e' del buono in questa strategia? Bubblesort: versione volgare
36 Poiche' ogni iterazione del ciclo interno di bubbleSort(), oltre che fare emergere l'elemento minimo del sottovettore ancora da ordinare, fa anche risalire verso la superficie altri elementi, puo' succedere che il vettore risulti ordinato anche prima della terminazione del ciclo esterno Esercizio: modificare bubbleSort() integrandolo con la logica di dummyBubbleSort() per accelerarne la terminazione Bubblesort: versione volgare