QuickSort Algoritmo di ordinamento sul posto che ha tempo di esecuzione : - O(n 2 ) nel caso peggiore - O(n log n) nel caso medio Nonostante le cattive prestazioni nel caso peggiore, rimane il miglior algoritmo di ordinamento in media Ne esistono diverse varianti e miglioramenti
QuickSort È basato sulla metodologia Divide et Impera: Dividi: L array A[p r] viene partizionato (tramite spostamenti di elementi) in due sotto-array non vuoti A[p q] e A[q+1 r] in cui: ogni elemento di A[p q] è minore o uguale ad ogni elemento di A[q+1 r] Impera: i due sottoarray A[p q] e A[q+1 r] vengono ordinati ricorsivamente con QuickSort Combina: i sottoarray vengono ordinati separatamente, quindi non è necessaria alcuna combinazione. A[p r] è già ordinato.
QuickSort QuickSort(A, p, r) IF p < r % controllo base di ricorsione THEN q = Partiziona(A,p,r) QuickSort(A, p, q) QuickSort(A, q+1, r)
Partiziona Procedura Partiziona: 1. l algoritmo sceglie un pivot, cioè l'elemento dell array che fungerà da elemento di confine (per il suo valore e non per la posizione) tra i due sotto-array 2. Sposta i valori > pivot verso l estremità destra dell array e i valori < pivot verso quella sinistra. 3. La posizione di confine q dipenderà dal valore scelto come pivot: tale valore è la posizione in cui si troverà alla fine della procedura l elemento più grande minore o uguale al pivot
Partiziona Partiziona(A,p,r) x = A[p] i = p-1 j = r+1 WHILE true DO REPEAT j = j-1 UNTIL A[j] <= x REPEAT i = i+1 UNTIL A[i] >= x IF i < j THEN scambia A[i] con A[j] ELSE return j
Partiziona La procedura Partiziona è quindi tale che: Se il numero di elementi dell array minori o uguali all elemento A[p], scelto come pivot, è pari a 1 (cioè A[p] è l elemento minimo) (risp. n-1), allora le dimensioni delle partizioni restituite sono 1 per la partizione di sinistra e n - 1 per quella di destra (risp. n-1 e 1) Altrimenti le partizioni tenderanno ad essere più bilanciate
Partiziona Partiziona(A,p,r) x = A[p] i = p - 1 j = r + 1 (1) WHILE true DO REPEAT j = j - 1 UNTIL A[j] <= x REPEAT i = i + 1 UNTIL A[i] x IF i < j THEN scambia A[i] con A[j] ELSE return j (1)
Partiziona Partiziona(A,p,r) x = A[p] i = p - 1 j = r + 1 (1) WHILE true DO REPEAT j = j - 1 UNTIL A[j]<= x REPEAT i = i + 1 UNTIL A[i] x IF i < j THEN scambia A[i] con A[j] ELSE return j (n)
QuickSort: analisi Il tempo di esecuzione di QuickSort dipende dal bilanciamento delle partizioni restituite dall algoritmo partiziona Il caso migliore si verifica quando le partizioni sono perfettamente bilanciate, entrambe di dimensione n/2 Il caso peggiore si verifica quando una partizione è sempre di dimensione 1 (la seconda è quindi di dimensione n-1)
QuickSort: analisi Caso Migliore: le partizioni sono sempre di uguale dimensione: T(n) = 2T(n/2) + (n) - e per il caso 2 del teorema master: T(n) = (nlog n)
QuickSort: analisi Caso Peggiore: La partizione sinistra ha sempre dimensione 1 mentre quella sinistra ha sempre dimensione n 1 (o viceversa): -T(n) = T(1) + T(n - 1) + (n) poiché T(1)=1 otteniamo -T(n) = T(n-1) + (n) - L equazione di ricorrenza può essere risolta facilmente col metodo iterativo n = -T(n)=T(n-1)+ (n) = (n 2 ) k=1 n Θ k = Θ k=1 k
QuickSort: analisi Caso Medio: Si può mostrare che nel caso medio il QuickSort ha tempo di esecuzione (n log n), dove n è il numero di elementi da ordinare - Anche quando la bipartizione risulta molto sbilanciata nella maggior parte delle chiamate alla procedura Partiziona
Partiziona in C int partiziona( int a[], int l, int r) { int i, j; double pivot; pivot = a[l]; i = l-1; j = r+1; while(1) { do ++i; while( a[i] < pivot && i <= r ); do --j; while( a[j] > pivot ); if( i >= j ) break; swapq(&a[i], &a[j]); } return j; }
Ricerca Per ricerca si intende il procedimento di localizzazione di una particolare informazione in un elenco di dati. Il problema della ricerca in termini generali : dato un insieme D = {a 1,a 2,...,a n } di n elementi distinti e un elemento x (elemento chiave), determinare se x appartiene all'insieme. Il metodo di ricerca dipende da come le informazioni sono organizzate, esistono due possibili approcci : Ricerca Sequenziale serve per ricercare i dati in un vettore NON ordinato Ricerca Binaria o Dicotomica serve nel caso in cui i dati nel vettore siano già ordinati
Ricerca Binaria Se gli elementi sono stati precedentemente ordinati (supponiamo in maniera crescente), si può effettuare una ricerca più efficiente che individua l elemento cercato senza dover scandire tutti gli elementi del vettore. L algoritmo di ricerca binaria dopo ogni confronto scarta metà degli elementi del vettore su cui si effettua la ricerca, restringendo il campo di ricerca. 2 3 6 12 16 21 24 26 28 30 36 41 50 inf med sup 2 3 6 12 16 21 24 26 28 30 36 41 50 x = 28 2 3 6 12 16 21 24 26 28 30 36 41 50
Ricerca Binaria La ricerca binaria è un algoritmo divide et impera (divide and conquer), cioè è un algoritmo che divide il problema in due o più sotto-problemi dello stesso tipo (nel nostro caso ancora vettori ordinati), ma di taglia inferiore, quindi più facili da risolvere Struttura ricorsiva del problema: - ciascuno dei sotto-problemi è ancora un problema di ricerca binaria Fase di ricombinazione: le soluzioni dei sotto-problemi in generale devono essere ricombinate per ottenere una soluzione del problema iniziale - Nel caso della ricerca binaria non è necessaria la ricombinazione
Ricerca Binaria L algoritmo può essere descritto mediante i seguenti passi : 1. Si individua l elemento che sta a metà del vettore (mediano) 2. Si confronta la chiave x con tale elemento. Se l elemento individuato non è uguale a quello cercato si prosegue in due modi possibili : se x > elemento mediano la ricerca continua solo nel sottovettore destro se x < elemento mediano la ricerca continua solo nel sottovettore sinistro 3. Il procedimento continua iterativamente dimezzando ogni volta la taglia del vettore, e arrestandosi quando l'elemento mediano è uguale ad x (esito positivo) o quando non è più possibile suddividere (esito negativo)
Ricerca Binaria La ricerca termina con successo quando l elemento mediano V[i] considerato ad un certo passo è proprio uguale alla chiave x. La ricerca termina con insuccesso quando la parte di vettore considerata è costituita da un solo elemento diverso dalla chiave. Osservazione : Per il calcolo del valore mediano abbiamo bisogno di tre indici che individuino l'inizio, la fine e la metà del vettore considerato ad ogni passo
Esercizio 1. Implementare in C la funzione di ricerca binaria. In particolare, implementare due prototipi: int binsearch(int* vett, int dim, int key); int binsearch(char** vett, int dim, char* key); Le funzioni devono restituire 1 se il valore key è presente, 0 altrimenti Assumere che il vettore vett sia già ordinato in maniera crescente (nel secondo caso secondo l'ordine lessicografico) 2. Implementare in C l'algoritmo QuickSort.
Esercizio 3. Scrivere un programma C che confrontare i tempi di esecuzione degli algoritmi heapsort, quicksort, insertionsort e bubblesort, su diversi input. Considerare i seguenti casi: a. Vettore di dimensione n già ordinato in maniera crescente. b. Vettore di dimensione n già ordinato in maniera decrescente. c. Vettore di dimensione n generato casualmente con valori tra 1 e 1000. d. Ripetere i punti a, b, c, con n=20000,30000 e 40000