Algoritmi di ricerca Ricerca lineare Si applica quando i dati in un vettore sono in un disposizione qualsiasi. Si pone un flag presente a FALSE. Poi, a partire dal primo elemento, si esegue il test se l’elemento del vettore di posizione indice coincide con il dato cercato. Se coincide, si pone il flag presente a TRUE per fermare la ricerca, altrimenti si procede fino alla fine del vettore. Al termine si restituisce il valore di presente e la posizione in cui è stato trovato il dato cercato. #include <stdio.h> #define MAX_DATI 100 typedef enum{FALSE,TRUE} boolean; int num_dati,stato,indice,p_posiz,dato; typedef struct tipo_dati { int dato; }tipo_vett; tipo_vett vett[MAX_DATI]; typedef struct tipo_dato { int dato_cercato; }tipo_elem; tipo_elem elemento; 1 boolean trovato (tipo_vett vett[], tipo_elem elemento,int n_dati, int *p_posiz); /* PROGRAMMA */ main() { indice=0; printf ("\nIntroduci il vettore, premi <ctrl>+z per finire\n"); while (((stato=scanf("%d",&dato))!=EOF)&& (indice<MAX_DATI)) { vett[indice].dato=dato; indice++; } num_dati=indice; printf ("\nQuale dato vuoi cercare?\n"); scanf ("%d",&elemento.dato_cercato); if( trovato (vett,elemento, num_dati,&p_posiz)) printf ("Dato presente in posizione %d\n",p_posiz); else printf("Dato non presente\n"); } /* FUNZIONE */ boolean trovato(tipo_vett vett[], tipo_elem elemento,int n_dati, int *p_posiz) { int indice; boolean presente; 2 indice=0; presente=FALSE; while((!presente)&&(indice<n_dati)) { if (vett[indice].dato == elemento.dato_cercato) { presente=TRUE; *p_posiz=indice; } else indice++; } return(presente); } Complessità dell’algoritmo: O(n). 3 Ricerca dicotomica Si applica quando i dati in un vettore sono ordinati. All’inizio di pongono gli estremi del vettore come limite inferiore e limite superiore della ricerca. Si analizza l’elemento in posizione mediana: se coincide con il dato cercato, si termina. Altrimenti si verifica se il dato cercato è maggiore del dato mediano, in qual caso si ripete la ricerca nella metà superiore (la posizione mediana diventa il limite inferiore), altrimenti si effettua la ricerca nella metà inferiore (la posizione mediana diventa il limite inferiore). L’iterazione termina quando è stato trovato il dato oppure quando il limite inferiore scavalca quello superiore. Esempio: sia dato il vettore 10 12 20 23 27 30 31 39 42 44 45 49 57 63 70 e sia x = 44 il dato cercato. 4 All’inizio si fa il test a metà: 10 12 20 23 27 30 31 39 42 44 45 49 57 63 70 Poiché x > 39 (elemento mediano), si sposta a destra il limite inferiore: 10 12 20 23 27 30 31 39 42 44 45 49 57 63 70 Ora x < 49 (elemento mediano attuale), si sposta a sinistra il limite superiore: 10 12 20 23 27 30 31 39 42 44 45 49 57 63 70 A questo punto il dato cercato è stato trovato. #include <stdio.h> /* BASE DATI */ #define MAX_DATI 100 typedef enum{FALSE,TRUE} boolean; int num_dati,stato,dato,indice,p_posiz; typedef struct { int dato; 5 }tipo_elem; tipo_elem vett[MAX_DATI]; tipo_elem cercato; boolean trovato (tipo_elem vett[], tipo_elem elemento, int *p_posiz,int n_dati); /* PROGRAMMA */ main() { indice=0; printf ("\nIntroduci il vettore, premi <ctrl>+z per finire\n"); while (((stato=scanf("%d",&dato))!=EOF)&& (indice<MAX_DATI)) { vett[indice].dato=dato; indice++; } num_dati=indice; printf ("\nQuale dato vuoi cercare?\n"); scanf ("%d",&cercato.dato); while(getchar() != '\n'); if( trovato(vett,cercato,&p_posiz,num_dati) ) printf ("\nDato presente in posizione %d\n", p_posiz); else printf ("\nDato non presente\n"); while(getchar() != '\n'); } /* FUNZIONE */ boolean trovato(tipo_elem vett[], 6 tipo_elem elemento, int *p_posiz,int n_dati) { int meta,limite_inf,limite_sup; boolean presente; limite_inf=0; limite_sup=n_dati-1; presente=FALSE; while ((!presente) && (limite_inf <= limite_sup)) { meta=(limite_sup+limite_inf)/2; if (vett[meta].dato == elemento.dato) /* comincio la ricerca a metà del vettore */ presente=TRUE; else { if (vett[meta].dato > elemento.dato) /* procedo usando il metodo di bisezione */ limite_sup=meta-1; else limite_inf=meta+1; } } *p_posiz = meta; return(presente); } Complessità dell’algoritmo: O(log2n). 7 Algoritmi di ordinamento Ordinamento per conteggio Si usa quando non interessa ordinare i dati in un vettore, ma piuttosto creare in un secondo vettore di uguale lunghezza la “classifica” dei dati (chi è il primo, il secondo, ecc.) Ad ogni elemento del primo vettore vett si associa l’elemento di un secondo vettore counter di uguale posizione. All’inizio tutti gli elementi di counter sono posti a 0. L’idea è quella di confrontare, a partire dall’ultimo, ciascun dato in vett con quelli che lo precedono e di contare “quanti lo precedono” (o “lo seguono”). L’algoritmo è il seguente: 1.Con i che va da 0 a N-1 1.1.Azzera counter[i] 2.Con i che va da 0 a N-2 2.1.Con j che va da i+1 a N-1 2.1.1. Se vett[i] < vett[j] 2.1.1.1. Incrementa counter[j] 2.1.2. Altrimenti 2.1.2.1. Incrementa counter[i] 8 Si osservi che il valore minore di tutti avrà conteggio pari a 0, mentre il maggiore di tutti avrà conteggio pari a N-1. Piuttosto che confrontare un elemento con tutti gli altri, si è scelto di confrontarlo solo con gli elementi che lo precedono (minor numero di confronti), ma così si è dovuto incrementare o counter[i] o counter[j] a seconda che fosse maggiore vett[i] oppure vett[j]. In questo algoritmo il numero di confronti è nc= (n-1)+(n-2)+(n-3)+…2+1 = n(n-1)/2. La prestazione dell’algoritmo è pertanto O(n2). Esempio: dopo l’ordinamento si ha: vett: 28 26 25 11 16 12 24 29 6 10 counter: 8 7 6 2 4 3 9 5 9 0 1 Ordinamento per selezione (selection sort) Si ricerca il minimo su tutto il vettore, memorizzandone la posizione, poi si scambia il minimo con il primo elemento. Pertanto il primo elemento è a posto. Si ripete l’operazione considerando tutto il vettore escluso il primo elemento, e poi così di seguito avanzando fino al penultimo elemento. A questo punto anche l’ultimo sarà a posto. #include <stdio.h> #define MAX_DATI 100 int lung_vett,stato,indice,dato; typedef int tipo_vett; tipo_vett vett[MAX_DATI]; void selection_sort(tipo_vett vett[],int lung_vett); /* PROGRAMMA */ main() { printf ("\nIntroduci un vettore di interi, premi <crtl>+z per finire\n"); while (((stato=scanf("%d",&dato))!=EOF)&& (indice<MAX_DATI)) { vett[indice]=dato; indice++; 10 } lung_vett=indice; selection_sort (vett,lung_vett); for (indice=0;indice<=lung_vett-1;indice++) printf ("%d ",vett[indice]); printf ("\n"); } /* FUNZIONE */ void selection_sort(tipo_vett vett[], int lung_vett) { int indice_rif,indice_corr,pos_min; tipo_vett min; for (indice_rif = 0; indice_rif < lung_vett-1; indice_rif++) { min=vett[indice_rif]; pos_min=indice_rif; for (indice_corr = indice_rif+1; indice_corr < lung_vett; indice_corr++) { if (vett[indice_corr]<min) { min=vett[indice_corr]; pos_min=indice_corr; } } vett[pos_min]=vett[indice_rif]; vett[indice_rif]=min; } 11 return; } In questo algoritmo il numero di confronti è nc= (n-1)+(n-2)+(n-3)+…2+1 = n(n-1)/2 Il numero di scambi è sempre n-1 Il numero di volte in cui il minimo è aggiornato dipende dalla distribuzione dei dati: si dimostra che in media sono richiesti nlogen+cn aggiornamenti. 12 Ordinamento per inserzione (insertion sort) Si utilizza un procedimento simile a quello di cui si serve un giocatore di carte per ordinare la sua mano: parte da sinistra, ad esempio, e scorre le carte. Man mano che incontra una carta fuori posto, la inserisce indietro al posto giusto, spostando in avanti le carte che devono stare alla sua destra. In questo modo la parte sinistra è sempre ordinata, e man mano questa parte ordinata cresce fino a comprendere tutte le carte. Operativamente: 1) Si cerca il minimo e lo si posiziona all’inizio (mediante scambio) come sentinella (questo passo evita di dover inserire ulteriori controlli nel seguito). 2) Finché ci sono ancora elementi da inserire nella parte ordinata: a) Prelevare il prossimo elemento x da inserire; b) Finché x è inferiore all’elemento precedente: 13 i) Spostare in avanti di una posizione l’elemento precedente, ii) estendere la ricerca all’indietro di un altro elemento ancora; c) inserire x nella posizione corrente. Esempio: Vettore iniziale: 28 26 25 11 16 12 24 29 6 10 Inizializzazione (minimo assoluto in posizione iniziale): 6 26 25 11 16 12 24 29 28 10 Azioni al primo ciclo: 6 val_rif 26 25 11 16 12 24 29 28 10 25 Alla fine del primo ciclo: 6 25 26 11 16 12 24 29 28 10 14 Azioni al secondo ciclo: 6 25 26 11 16 12 24 29 28 10 val_rif 11 Alla fine del secondo ciclo: 6 11 25 26 16 12 24 29 28 10 E così via. #include <stdio.h> #define MAX_DATI 100 int lung_vett,stato,indice,dato; typedef int tipo_vett; tipo_vett vett[MAX_DATI]; void insertion_sort(tipo_vett vett[],int lung_vett); /* PROGRAMMA */ main() { printf (“\nIntroduci un vettore di interi, premi <crtl>+z per finire\n”); 15 while (((stato=scanf(“%d”,&dato))!=EOF)&& (indice<MAX_DATI)) { vett[indice]=dato; indice++; } lung_vett=indice; insertion_sort (vett,lung_vett); for (indice=0;indice<=lung_vett-1;indice++) printf (“%d “,vett[indice]); printf (“\n”); } /* FUNZIONE */ void insertion_sort(tipo_vett vett[], int lung_vett) { int val_rif,indice_rif,indice_corr,pos_min; tipo_vett min; min=vett[0]; /* inizializzazioni */ pos_min=0; for(indice_corr=1;indice_corr<lung_vett; indice_corr++) { if (vett[indice_corr]<min) /* ricerca del minimo */ { min=vett[indice_corr]; pos_min=indice_corr; } } vett[pos_min]=vett[0]; vett[0]=min; for(indice_rif=2;indice_rif<lung_vett; indice_rif++) 16 { val_rif=vett[indice_rif]; indice_corr=indice_rif-1; while (vett[indice_corr]>val_rif) { vett[indice_corr+1]=vett[indice_corr]; indice_corr--; vett[indice_corr+1]=val_rif; } } return; } In questo algoritmo il numero di confronti, nel caso peggiore, è (n2 + n – 4)/2 e, nel caso medio, (n2 + 6n – 12)/4. La prestazione dell’algoritmo è pertanto O(n2). 17 Quicksort ricorsivo È basato sul principio di partizionamento di un vettore. Il problema di partizionare un vettore si pone così: dato un vettore ordinato a caso di n elementi, dividere gli elementi in due sottogruppi tali che gli elementi x siano in un sottogruppo, e quelli x nell’altro. Ad esempio, sia dato il vettore seguente e sia x = 17. 28 26 25 11 16 12 24 29 6 10 Possiamo partire da sinistra e cercare il primo numero 17, poi partire da destra e, tornando indietro, cercare il primo valore 17. A questo punto si possono scambiare di posto questi due valori: 10 26 25 11 16 12 24 29 6 28 A questo punto una parte del vettore è a posto. 18 Si può ripetere l’operazione precedente a partire dalle posizioni correnti (sinistra e destra). Al secondo passo si ottiene: 10 6 25 11 16 12 24 29 26 28 Si procede così finché i due cursori non si incrociano, ottenendo alfine: 10 6 12 11 16 25 24 29 26 28 L’idea di base del Quicksort è quella di partizionare il vettore in due parti e poi ripetere l’operazione alla parte sinistra e alla parte destra, in modo recursivo. Non essendo conveniente scegliere in modo accurato il valore x rispetto al quale effettuare il partizionamento, si sceglie come x il valore che si trova nella posizione di mezzo. Pertanto l’algoritmo del quicksort è: 1) Dividi i dati nella partizione sinistra e destra purché nell’insieme ci sia più di un elemento. 19 2) Ripeti il processo di partizione alla partizione di sinistra. 3) Ripeti il processo di partizione alla partizione di destra. #include <stdio.h> #include <string.h> #include <conio.h> #include <ctype.h> #define MAX 100 int vett[MAX],inf_sinistra,sup_destra,cont; void inserimento(void); void quick_sort(int vett[],int inf_sinistra,int sup_destra); void partition(int vett[],int pos_sinistra,int pos_destra,int *sup_sinistra,int *inf_destra); void stampa(void); void titolo(void); main() { char car; do{ titolo(); inserimento(); if(cont>1) { inf_sinistra=0; sup_destra=cont-1; 20 quick_sort(vett,inf_sinistra, sup_destra); } if(cont==0) printf("\n\nIl vettore non e’ stato inserito."); else if(cont==1) printf("\n\nIl vettore e’ il seguente:\n\n%d",vett[0]); else stampa(); printf("\n\n\n\nPremi R per ricaricare il programma;\n premi un altro tasto per terminare la sua esecuzione."); car=getch(); }while(toupper(car)=='R'); return; } void inserimento(void){ int n,stato; char sep; cont=0; printf("Inserisci un vettore di numeri terminato da <CTRL+Z>:\n\n"); while( (stato=scanf("%d%c",&n,&sep))!=EOF && cont<MAX) { if(stato==0) { printf("\nIl vettore inserito non e’ corretto, reinseriscilo:\n\n"); cont=0; 21 } else if(stato>0) { vett[cont]=n; cont++; } } return; } void partition(int vett[], int pos_sinistra,int pos_destra, int *sup_sinistra,int *inf_destra) { int mediano,prov,i,j; i=pos_sinistra; j=pos_destra; mediano=vett[(pos_sinistra+pos_destra)/2]; do{ while(vett[i]<mediano && i<pos_destra) i++; while(vett[j]>mediano && j>pos_sinistra) j--; if(i<=j) { prov=vett[i]; vett[i]=vett[j]; vett[j]=prov; i++; j--; } }while(i<=j); *sup_sinistra=j; *inf_destra=i; 22 return; } void quick_sort(int vett[], int inf_sinistra,int sup_destra { int sup_sinistra,inf_destra; if(inf_sinistra<sup_destra) { partition(vett,inf_sinistra,sup_destra, &sup_sinistra,&inf_destra); if((sup_sinistra-inf_sinistra)< (sup_destra-inf_destra)) { quick_sort(vett,inf_sinistra, sup_sinistra); quick_sort(vett,inf_destra, sup_destra); } else { quick_sort(vett,inf_destra, sup_destra); quick_sort(vett,inf_sinistra, sup_sinistra); } } return; } void stampa(void) { int i; printf("\n\nIl vettore ordinato con la quick sort e’:\n\n"); 23 for(i=0;i<cont;i++) printf("%d ",vett[i]); return; } void titolo(void){ textcolor(14); textbackground(1); clrscr(); printf(" QUICK SORT RECURSIVO\n\n\n"); return; } Nota: nella realizzazione dell’algoritmo, si è tenuto conto del fatto che, per limitare la profondità della ricorsione, conviene ordinare per prima la partizione più piccola. Conviene seguire lo sviluppo dell’algoritmo su un esempio. Chiamata di Quicksort su: 20 35 8 18 14 41 3 39 Dopo la prima chiamata a partition: 3 14 8 18 35 41 20 39 24 La successiva chiamata recursiva Quicksort agirà su: 3 14 8 18 35 41 20 39 Dopo la seconda chiamata a partition: 3 8 14 18 35 41 20 39 La successiva chiamata recursiva a Quicksort agirà su: 3 8 14 18 35 41 20 39 (la ricorsione termina). La successiva chiamata recursiva a Quicksort agirà su: 3 8 14 18 35 41 20 39 La successiva chiamata a partition lascia il vettore immodificato: 3 8 14 18 35 41 20 39 25 La successiva chiamata recursiva a Quicksort agirà su: 3 8 14 18 35 41 20 39 (la recursione termina). La successiva chiamata recursiva a Quicksort agirà su: 3 8 14 18 35 41 20 39 (la recursione termina). Eccetera sull’altra sezione… 26