Algoritmi di ordinamento Selection Sort Quick Sort Lower bound alla complessità degli algoritmi di ordinamento 1 Selection Sort SelectionSort(dati[]) { for (i=0; i<dati.length-1; i++) { min = <Seleziona min. in dati[i], …. , dati[dati.length-1]> <Scambia min con dati[i]; } } L’elemento minimo viene messo in posizione 0 Si itera il procedimento sulle posizioni successive 2 Selection Sort/2 SelectionSort(dati[], i) { min = <Seleziona min. in dati[i], …. , dati[dati.length-1]> <Scambia min con dati[i]; SelectionSort(dati[], i+1) ; } …… SelectionSort(dati[], 0) ; Versione ricorsiva 3 Selection Sort/3 Ordinamento del vettore di interi {5, 2, 3, 8, 1} 4 Come ordinare oggetti diversi da numeri Ordinare un vettore i cui elementi sono oggetti complessi. Es. oggetti della classe: class Persona { String cognome; String CF; public Persona (String cognome, String CF) { this.cognome = cognome; this.CF = CF; } } Come ordinare un array di tali oggetti rispetto al cognome ? 5 Come ordinare oggetti diversi da numeri/2 Occorre: 1. Dichiarare che tra gli oggetti della classe (Persona nell’esempio) è definito un ordinamento 2. Dichiarare rispetto a quale o a quali membri della classe è definito l’ordinamento (il cognome nel nostro caso) 3. Definire la regola che stabilisce l’ordinamento tra due oggetti della classe (nel nostro caso: due oggetti di tipo persona sono ordinati alfabeticamente secondo i rispettivi cognomi) In C++ si possono sovraccaricare gli operatori In Java si può dichiarare che la classe (Persona) implementa l’interfaccia Comparable (non è la sola possibilità) 6 Come ordinare oggetti diversi da numeri/3 Il passo 1 si traduce così: class Persona implements Comparable { …… } I passi 2 e 3 consistono nell’implementare l’unico metodo previsto dall’interfaccia Comparable: int compareTo(Object o) compareTo definisce le regole che stabiliscono l’ordinamento tra oggetti della classe (nel nostro caso, l’ordinamento è quello alfabetico sui cognomi) 7 Come ordinare oggetti diversi da numeri/4 Quindi: class Persona implements Comparable { String cognome; String CF; public Persona (String cognome, String CF) { this.cognome = cognome; this.CF = CF; } public int compareTo (Object pers) { return cognome.compareTo(((Persona)pers).cognome); } } Nota: occorre fare il cast perché compareTo vuole un Object 8 Selection Sort/4 public void selectionsort(Comparable[] data) { int i, j, least; for (i = 0; i < data.length-1; i++) { for (j = i+1, least = i; j < data.length; j++) if (data[j].compareTo(data[least]) < 0) least = j; swap(data, least, i); /* Scambia gli oggetti in pos. i e least */ } } Es.: versione ricorsiva 9 Quick Sort quicksort(array[]) { if (array.length>1) { Scegli bound; /* subarray1 e subarray2 */ while (ci sono elementi in array) if (generico elemento < bound) inserisci elemento in subarray1; else inserisci elemento in subarray2; quicksort(subarray1); quicksort(subarray2); } } 11 Quick Sort/2 Array < bound subarray2 subarray1 >= bound < bound2 < bound1 >= bound1 >= bound2 12 Partizionamento dell’array [8 5 4 7 6 1 6 3 8 12 10] con quicksort 13 Partizionamento dell’array [8 5 4 7 6 1 6 3 8 12 10] con quicksort 14 Quick Sort/3 void quicksort(Comparable[] data, int first, int last) { int lower = first + 1, upper = last; swap(data, first, (first+last)/2); /* Questo serve solo perché così, in pratica è spesso più veloce */ Comparable bound = data[first]; while (lower <= upper) { while (data[lower].compareTo(bound) < 0) lower++; while (bound.compareTo(data[upper]) < 0) upper--; if (lower < upper) swap(data, lower++, upper--); else lower++; /* 1 */ } /* End while */ swap(data, upper, first); if (first < upper-1) /* se first == upper-1 il sottoarray ha solo 2 elementi ed è ordinato */ quicksort(data, first, upper-1); if (upper+1 < last) quicksort(data, upper+1, last); } 15 Quick Sort/4 void quicksort(Comparable[] data) { if (data.length < 2) return; int max = 0; /* Trova max. e mettilo alla fine; serve per evitare che lower cresca oltre la dim. dell’ array (non è detto che accada ma può succedere) */ for (int i = 1; i < data.length; i++) if (data[max].compareTo(data[i]) < 0) max = i; swap(data, data.length-1, max); // largest el is now in its quicksort(data, 0, data.length-2); // final position; } 16 Analisi del Quick Sort Costo = O(No. confronti) Costo O(n2) nel caso peggiore Costo O(n log n) nel caso migliore e medio In pratica l’algoritmo è efficiente Scelta pivot fondamentale 17 Quick Sort – Caso peggiore No. confronti per sotto-array n-1 n-2 n-2 Array Array Array n-1 volte 2 1 L’elemento di pivot è sempre il minimo Costo = O(n-1+n-2+...+2+1) = O(n2) 18 Quick Sort – Caso migliore No. confronti per sotto-array n-1 n potenza di 2 per semplicità Array n/2-1 n/4-1 log n+1 volte 2 1 Costo n n n logn i n = n 2 4 n 2 i n(log n 1) 2 4 n i 0 2 19 Efficienza algoritmi di ordinamento Merge Sort (e Heap Sort): O(n log n) Quick Sort, Selection Sort, Insertion Sort: O(n2) Quick Sort: O(n log n) nel caso migliore Selection Sort: O(n2) in tutti i casi Insertion Sort: O(n) nel caso migliore Domanda: qual è l’efficienza massima (complessità minima) ottenibile nel caso peggiore -> Lower bound 20 Ordinamento – limiti inferiori Osservazione fondamentale: tutti gli algoritmi devono confrontare elementi Dati ai, ak, tre casi possibili: ai < ak, ai > ak, oppure ai=ak Si assume per semplicità che tutti gli elementi siano distinti Si assume dunque che tutti i confronti abbiano la forma ai < ak, e il risultato del confronto sia vero o falso Nota: se gli elementi possono avere lo stesso valore allora si considerano solo confronti del tipo ai <= ak 21 Alberi di decisione Albero di decisione per Insertion Sort sull’insieme < {a1, a2, a3} a1:a2 < a2:a3 > a1,a2,a3 < a1,a3,a2 > < a1:a3 > a1:a3 a2,a1,a3 a3,a1,a2 > a2:a3 < a2,a3,a1 > a3,a2,a1 Un albero di decisione rappresenta i confronti eseguiti da un algoritmo su un dato input Ogni foglia corrisponde ad una delle possibili permutazioni 22 Alberi di decisione/2 Albero di decisione per Insertion Sort sull’insieme < {a1, a2, a3} a1:a2 < a2:a3 > a1,a2,a3 < a1,a3,a2 > < a1:a3 > a1:a3 a2,a1,a3 a3,a1,a2 > a2:a3 < a2,a3,a1 > a3,a2,a1 Vi sono n! possibili permutazioni -> l’albero deve contenere n! foglie L’esecuzione di un algoritmo corrisponde ad un cammino sull’albero di decisione corrispondente all’input considerato 23 Alberi di decisione/3 Riassumendo: Albero binario Deve contenere n! foglie Il più lungo cammino dalla radice ad una foglia (altezza) rappresenta il No. confronti che l’algoritmo deve eseguire nel caso peggiore Teorema: qualunque albero di decisione che ordina n elementi ha altezza Ώ(n log n) Corollario: nessun algoritmo di ordinamento ha complessità migliore di Ώ(n log n) Nota: esistono algoritmi di ordinamento con complessità più bassa, ma richiedono informazioni aggiuntive 24 Dimostrazione teorema 1. Un albero di decisione è binario 2. Albero binario di altezza h non ha più di 2h foglie 1 1=21-1 2 2= 22-1 3 4= 23-1 h 2h-1 3. Dobbiamo avere: 2h-1 > No. foglie = n! 4. h-1 > log(n!) 25 Dimostrazione teorema/2 5. n! > (n/e)n (approssimazione di Stirling) 6. h-1 > log(n/e)n = n log(n/e) = n logn – n loge = Ώ(n log n) Corollario: gli algoritmi Merge Sort e Heap Sort hanno complessità asintotica ottima 26