Algoritmi di Ordinamento D O T T. I N G . L E O N A R D O R I G U T I N I D I PA R T I M E N T O I N G E G N E R I A D E L L’ I N F O R M A Z I O N E UNIVERSITÀ DI SIENA VIA ROMA 56 – 53100 – SIENA U F F. 0 5 7 7 2 3 4 8 5 0 - 7 1 0 2 [email protected] H T T P : / / W W W. D I I . U N I S I . I T / ~ R I G U T I N I / Algoritmo di ordinamento Metodo per ordinare un insieme di oggetti. Il problema dell’ordinamento è un problema classico dell’informatica: - - Valenza didattica: problema in sè molto semplice e chiunque è in grado di comprendenre i termini generali Valenza in ambito applicativo: si ritrova spesso come sottoproblema in molti casi reali Sono stati proposti molti algoritmi diversi ed eleganti: - Se presentati in opportuna successione permettono di evidenziare gli aspetti fondamentali della progettazione e della costruzione di un algoritmo efficiente Algoritmo di ordinamento Il problema dell’ordinamento (sorting) può essere posto nei seguenti termini: - Dati un insieme di elementi qualsiasi A={a1,a2,…,an}, su cui sia possibile definire una relazione d’ordine totale ≤ si richiede diprodurre una permutazione degli elementi in modo che aih ≤ aik per ogni h,k=1,2,….,n e h ≤ k - Una relazione d’ordine totale è una relazione riflessiva, antisimmetrica e transitiva definita su ogni coppia di elementi dell’insieme - La soluzione è unica a meno di elementi uguali: Algoritmi di ordinamento Soluzioni via via sempre più sofisticati ma con complessità computazionale sempre più bassa Algoritmi di ordinamento I primi 3 algoritmi analizzat – SELECTION SORT, INSERTION SORT e BUBBLE SORT – sono estremamente elementari e consentono un’implementazione assai semplice: - il costo da pagare alla semplicità di questi algoritmi sta ovviamente nell’elevata complessità computazionale,che inquesti casi è O(n2). L’algoritmo QUICKSORT consente di raggiungere una complessità di O(nlog2n) nel caso medio, mentre nel caso più sfavorevole ritorna ad una complessità di O(n2). Algoritmi di ordinamento Gli algoritmi MERGESORT e HEAPSORT consentono di raggiungere una complessità di O(nlog2n) anche nel caso peggiore: - è possibile dimostrare che il limite inferiore alla complessità computazionale del problema dell’ordinamento mediante confronti (senza dunque poter sfruttare altre informazioni sull’insieme da ordinare) è proprio pari a nlog2n, possiamo concludere che tali algoritmi sono ottimi. Algoritmi di ordinamento Gli algoritmi COUNTING SORT e BUCKET SORT sono invece basati su altri criteri e strategie, diverse dal confronto fra i valori degli elementi dell’insieme da ordinare, e sfruttano pertanto altre informazioni sui dati in input: - Grazie a questo consentono di ridurre ulteriormente la complessità nel caso peggiore, arrivando ad una complessità lineare di (n). Sotto a tale soglia è impossibile scendere,dal momento che per ordinare un insieme di n elementi è necessario esaminarli tutti almeno una volta. Ci possiamo concentrare soltanto nello studio della complessità nel caso peggiore, dal momento che è il parametro più conservativo per la valutazione dell’efficienza di un algoritmo. Selection sort Selection sort Un algoritmo decisamente intuitivo ed estremamente semplice. Nella pratica è utile quando l’insieme da ordinare è composto da pochi elementi e dunque anche un algoritmo non molto efficiente può essere utilizzato con il vantaggio di non rendere troppo sofisticata la codifica del programma che lo implementa. L’idea di fondo su cui si basa l’algoritmo è quella di ripetere per n volte una procedura in grado di selezionare alla i-esima iterazione l’elemento più piccolo nell’insieme e di scambiarlo con l’elemento che in quel momento occupa la posizione i Selection sort In altre parole: - - - alla prima iterazione dell’algoritmo verrà selezionato l’elemento più piccolo dell’intero insieme e sarà scambiato con quello che occupa la prima posizione; alla seconda iterazione è selezionato il secondo elemento più piccolo dell’insieme, ossia l’elemento più piccolo dell’insieme “ridotto”costituito dagli elementi {a2,a3,...,an}ed è scambiato con l’elemento che occupa la seconda posizione; Si ripete fino ad aver collocato nella posizione corretta tutti gli n elementi. Selection Sort void selection_sort(int[] A, int n) { int i=0; for (i=0;i<n;i++) { int j=0; for (j=i+1;j<n;j++) { if (a[j] < a[i]) { int temp=a[j]; a[j]=a[i]; a[i]=temp; } } } Selection Sort Il numero di operazioni è: (n-1) + (n-2) + … + 2 + 1 n(n-1)/2 O(n2) Un inconveniente dell'algoritmo di ordinamento per selezione è che il tempo di esecuzione dipende solo in modo modesto dal grado di ordinamento in cui si trova il file. La ricerca del minimo elemento durante una scansione del file non fornisce informazioni circa la posizione del prossimo minimo nella scansione successiva. Verificare che esso impiega più o meno lo stesso tempo sia su file già ordinati che su file con tutte le chiavi uguali, o anche su file ordinati in modo casuale. Selection Sort Nonostante l'approccio brutale adottato, ordinamento per selezione ha un'importante applicazione: poiché ciascun elemento viene spostato al più una volta, questo tipo di ordinamento è il metodo da preferire quando si devono ordinare file costituiti da record estremamente grandi e da chiavi molto piccole. Per queste applicazioni il costo dello spostamento dei dati è prevalente sul costo dei confronti e nessun algoritmo è in grado di ordinare un file con spostamenti di dati sostanzialmente inferiori a quelli dell'ordinamento per selezione. Insertion sort Insertion Sort Ordinamento a inserimento, è un algoritmo relativamente semplice per ordinare un array. Non è molto diverso dal modo in cui un essere umano, spesso, ordina un mazzo di carte. Algoritmo in place, cioè ordina l'array senza doverne creare una copia, risparmiando memoria. Insertion Sort Ad ogni istante (iterazione), il vettore è costituito da una parte iniziale ordinata (che aumenta di volta in volta) e da la parte rimanente che contiene i valori da ordinare. Per ogni valore ancora da inserire, viene fatta una ricerca binaria nella parte ordinata del vettore e vengono spostati in avanti tutti gli elementi per liberare la posizione Nella posizione liberata viene inserito il valore Insertion Sort void insertion_sort(int[] A,int n) { int i=0,j=0; for (i=1;i<n;i++) { int x=a[i], s=1, d=i-1; while (s<=d) { int m=(s+d)/2; if (x<a[m]) d=m-1; else s=m+1; } for (j=i-1,j>=s;j--) a[j+1]=a[j]; a[s]=x; } } Insertion Sort Complessità: - - Caso peggiore O(n2) Caso migliore O(n) vettore già ordinato Molto vantaggioso per vettori quasi ordinati Bubble sort Bubble Sort L’algoritmo BUBBLE SORT (ordinamento a bolle) so basa sull’idea di far emergere pian piano gli elementi più piccoli verso l’inizio dell’insieme da ordinare facendo sprofondare gli elementi maggiori verso il fondo: - un po’ come le bollicine in un bicchiere di acqua gassata da qui il nome di ordinamento a bolle. La strategia adottata è quella di scorrere più volte la sequenza da ordinare, verificando ad ogni passo l’ordinamento reciproco degli elementi contigui, ai e ai+1, ed eventualmente scambiando la posizione di quelle coppie di elementi non ordinate. Bubble Sort Procedendo dall’inizio alla fine della sequenza, al termine di ogni scansione si è ottenuto che l’elemento massimo è finito in fondo alla sequenza stessa, mentre gli elementi più piccoli hanno cominciato a spostarsi verso l’inizio della sequenza stessa. Dunque alla fine della prima scansione possiamo essere certi che l’elemento massimo ha raggiunto la sua posizione corretta nell’ultima posizione della sequenza: - la scansione successiva potrà fermarsi senza considerare l’ultimo elemento dell’insieme e riuscendo così a collocare nella posizione corretta (la penultima) il secondo elemento più grande dell’insieme; Si ripete fino ad aver completato l’ordinamento dell’intera sequenza. Bubble Sort void bubble_sort(int[] A, int n) { bool scambio=true; int ultimo=n-1,i=0; while (scambio) { scambio=false; for (i=0;i<ultimo;i++) { if (a[i]>a[i+1]) { int temp=a[i]; a[i]=a[i+1]; a[i+1]=temp; scambio=true; } } ultimo--; } } Bubble Sort Il numero di operazioni è: (n-1) + (n-2) + … + 2 + 1 n(n-1)/2 O(n2) Quick Sort Quick Sort Quicksort è un algoritmo di ordinamento ricorsivo in place che si basa sul paradigma divide et impera. La base del suo funzionamento è l'utilizzo ricorsivo della procedura partition: - preso un elemento da un array si pongono gli elementi minori a sinistra rispetto a questo e gli elementi maggiori a destra - La stessa procedura poi è richiamata in modo ricorsivo sui due sotto insiemi Il Quicksort, termine che tradotto letteralmente in italiano indica ordinamento rapido, è l'algoritmo di ordinamento che ha, in generale, prestazioni migliori tra quelli basati su confronto. Approccio ricorsivo L'idea base può esprimersi agevolmente in termini ricorsivi. - - Ad ogni stadio si effettua un ordinamento parziale di una sequenza di oggetti da ordinare. Assunto un elemento come perno dello stadio, si confrontano con esso gli altri elementi e si posizionano alla sua sinistra i minori e a destra i maggiori, senza tener conto del loro ordine. Dopo questo stadio si ha che il perno è nella sua posizione definitiva. Successivamente si procede in modo ricorsivo all'ordinamento parziale delle sottosequenze di elementi rimasti non ordinati, fino al loro esaurimento. Quick Sort void sort(int[] array, int begin, int end) { int pivot, l, r; if (end > begin) { pivot = array[begin]; l = begin + 1; r = end+1; while (l < r) { if (array[l] < pivot) l++; else { r--; swap(array[l], array[r]); } Quick Sort l--; swap(array[begin], array[l]); sort(array, begin, l); sort(array, r, end); } } Swap Scambia x con y e viceversa. Parametri passati per riferimento. void swap(int & x, int & y) { int temp=x; x=y; y=temp; } Complessità Caso peggiore vettore ordinato in modo inverso: - O(n2) Caso migliore/medio: - O(n log2 n) Merge Sort Merge Sort Il merge sort è un algoritmo di ordinamento molto intuitivo e abbastanza rapido, che utilizza un processo di risoluzione ricorsivo. L'idea alla base del merge sort è il procedimento Divide et Impera, che consiste nella suddivisione del problema in sottoproblemi via via più piccoli. Il merge sort opera quindi dividendo l'insieme da ordinare in due metà e procedendo all'ordinamento delle medesime ricorsivamente. Quando si sono divise tutte le metà si procede alla loro fusione (merge appunto) costruendo un insieme ordinato. Due fasi Fase 1: divide - L'insieme di elementi viene diviso in 2 metà. Se l'insieme è composto da un numero dispari di elementi, viene diviso in 2 sottogruppi dei quali il primo ha un elemento in meno del secondo. Fase 2: impera - Supponendo di avere due sequenze già ordinate. Per unirle, l'algoritmo mergesort estrae ripetutamente il minimo delle due sequenze in ingresso e lo pone in una sequenza in uscita. Merge void merge (int[] a, int left, int center, int right) int i = left, j=center + 1,k=0; int[] b; while ((i <= center) && (j <= right)) { if (a[i] <= a[j]){ b[k]=a[i]; i=i + 1; } else { b[k] = a[j]; j=j + 1; } k=k + 1; } Merge (continua) for (k =left ;k<right;k++) { a[k] = b[k - left]; } Merge Sort void mergesort (int[] a, int left, int right) { int center; if (left < right) { center = (left + right) / 2; mergesort(a, left, center); mergesort(a, center+1, right); merge(a, left, center, right); } Merge Sort Complessità: - Caso peggiore O(n log n) Richiede memoria aggiuntiva per O(n)