PROGRAMMI DI RICERCA E ORDINAMENTO Ricerca lineare. Abbiamo già visto l’algoritmo che esegue la ricerca lineare di un elemento (detto chiave) in una lista o vettore, e il corrispondente diagramma di flusso:
Siamo ora in grado di tradurlo in una funzione di nome ricercaLin, che riceve in ingresso un vettore di interi, il numero dei suoi elementi e la chiave da ricercare ricercaLin(int elem[], int dim, int chiave) { int indice, trovato, i; trovato = FALSO; indice = -1; i = 0; while (i<dim && !trovato) if (elem[i] == chiave) trovato = VERO; indice = i; } i++; return(indice);
Osserviamo che il ciclo while è usato per accedere a ciascun elemento del vettore, dal primo all’ultimo, fino a che si trova una corrispondenza con la chiave desiderata. In tale caso la variabile trovato viene impostata a VERO, il che fa terminare il ciclo; altrimenti la ricerca continua fino a che s’incontra la fine del vettore. Per provare questa funzione, scriviamo una funzione pilota main() che chiami ricercaLin e visualizzi il risultato da essa fornito. Il programma completo è il seguente:
#include <stdio.h> #define VERO 1 #define FALSO 0 #define N 10 void main(void) { int a[N] = {5,10,22,32,45,67,73,98,99,101}; int voce, posto; int ricercaLin(int [], int, int); printf(“Scrivi la voce da cercare: “); scanf(“%d”, &voce); posto = ricercaLin(a, N, voce); if (posto > -1) printf(“La voce è stata trovata al posto n° %d\n”, posto+1); else printf(“La voce non è stata trovata”); } ricercaLin(int elem[], int dim, int chiave) { int indice, trovato, i; trovato = FALSO; indice = -1; i = 0; while (i<dim && !trovato) if (elem[i] == chiave) trovato = VERO; indice = i; } i++; return(indice);
Ricerca binaria. Ricordiamo che la ricerca binaria va eseguita su un vettore ordinato, e che la sua strategia è la seguente: a) si confronta la chiave con l’elemento centrale del vettore; a1) se la chiave è uguale all’elemento centrale, la ricerca termina con successo; a2) se la chiave è maggiore dell’elemento centrale essa, se è presente, si deve trovare nella parte superiore del vettore, e la ricerca continua in essa; a3) se la chiave è minore dell’elemento centrale essa, se è presente, si deve trovare nella parte inferiore del vettore, e la ricerca continua in essa.
Come abbiamo visto, lo pseudocodice che implementa questa strategia è: poni trovato = FALSO poni indice = -1 poni indice inferiore = 0 poni indice superiore = dimensione del vettore -1 comincia con il primo elemento while indice infer. <= indice super. AND non si è ancora trovata la chiave poni indice medio = media degli indici infer. e super. if la chiave è uguale all’elemento di indice medio la chiave è stata trovata altrimenti, se la chiave è > dell’elemento di indice medio poni indice inferiore = indice medio +1 altrimenti, se la chiave è < dell’elemento di indice medio poni indice superiore = indice medio -1 fine dell’if fine del while fornisci il valore di indice; Ed ecco la versione in C di questo pseudocodice:
ricercaBin(int elem[], int dim, int chiave) { int indice, trovato, inf, sup, medio; indice = -1; trovato = FALSO; inf = 0; sup = dim -1; while (inf <= sup && !trovato) medio = (int) ((inf + sup)/2); if (chiave == elem[medio]) trovato = VERO; indice = medio; } else if (chiave > elem[medio]) inf = medio + 1; else sup = medio - 1; return(indice);
La precedente funzione ricercaBin andrà poi scritta dopo la seguente funzione principale: #include <stdio.h> #define VERO 1 #define FALSO 0 #define N 10 void main(void) { int a[N] = {5,10,22,32,45,67,73,98,99,101}; int voce, posto; int ricercaBin(int [], int, int); printf(“Scrivi la voce da cercare: “); scanf(“%d”, &voce); posto = ricercaBin(a, N, voce); if (posto > -1) printf(“La voce è stata trovata al posto n° %d\n”, posto+1); else printf(“La voce non è stata trovata”); }
Osserviamo che questa funzione main() usata per la ricerca binaria è la stessa già usata per la ricerca lineare (a parte, ovviamente, il diverso nome di funzione usato nelle due istruzioni che dichiarano e chiamano la funzione).
Ordinamento a bolle. Traduciamo ora in programmi C i vari algoritmi di ordinamento già visti. Cominciamo da quello a bolle, che si basa sul seguente algoritmo: a) si confronta ciascun elemento, dal secondo all’ultimo, con il precedente, e si scambiano se necessario; b) si ripetono i confronti come prima, fermandosi la seconda volta al penultimo elemento, la terza volta al terz’ultimo, ... , l’ultima volta al secondo elemento. Perciò, nel caso di un vettore con 10 elementi, si inizia confrontando a[1] con a[0], poi a[2] con a[1], ... fino ad a[9] con a[8], eseguendo gli scambi necessari. A causa del modo con cui si effettuano i confronti e gli scambi, dopo la prima scansione del vettore (i = 0) il valore più grande sarà memorizzato nell’ultima componente del vettore (cioè in a[9]). Per questa ragione la seconda scansione (i = 1), partendo sempre dal confronto di a[1] con a[0], termina al confronto di a[8] con a[7], lasciando in a[8] il secondo valore più grande.
In tal modo saranno eseguite in tutto 9 scansioni, al termine delle quali il 9° valore in ordine di grandezza sarà memorizzato in a[1], mentre il più piccolo si troverà in a[0]. Pertanto, nel caso di un vettore a n dimensioni, questo algoritmo esegue n - 1 passate del vettore, mentre il numero di scambi eseguiti dipende dal grado di “disordine” del vettore (è 0 se il vettore è già ordinato). Lo pseudo codice è quindi il seguente: (in questa fase, le istruzioni indicate in colore non sono strettamente necessarie): poni un contatore di scambi = 0 for l’indice i che va dal 1° elemento al penultimo for l’indice j che va dal 2° elemento all’ultimo if num[j] < num[j-1] scambia num[j] con num[j-1] incrementa il contatore di scambi fine del for restituisci il contatore di scambi
Esso si traduce nella seguente funzione: int ord_bolle(int a[], int numel) { int i, j, temp, scambi = 0; for (i = 0; i < numel - 1; i++) for (j = 1; j < numel - i; j++) if (a[j] < a[j-1]) temp = a[j]; a[j] = a[j-1]; a[j-1] = temp; scambi++; } return(scambi);
che va scritta dopo la seguente funzione principale: #include <stdio.h> #define N 10 void main(void) { int a[N] = {22,5,67,98,45,32,101,99,73,10}; int i, passi; int ord_bolle(int [], int); /* prototipo */ passi = ord_bolle(a, N); /* chiamata */ printf("Il vettore ordinato in ordine crescente è:\n"); for (i = 0; i < N; ++i) printf("%d ", a[i]); printf(“\n Sono stati eseguiti %d scambi”, passi); }
Bolle migliorato. All’algoritmo a bolle si può apportare un sostanziale miglioramento modificandolo come segue: a) si confronta ciascun elemento, dal secondo all’ultimo, con il precedente, e si scambiano se necessario; b’) si ripete il procedimento fino a quando si esegue una scansione della lista senza effettuare alcuno scambio (perché in tale caso tutti gli elementi sono nell’ordine corretto). In tale modo il numero di passate da eseguire nel vettore non è più fisso (e uguale a n-1), ma varia da 1 (nel caso il vettore sia già ordinato) a n-1 (nel caso il vettore abbia il massimo grado di disordine, ossia sia ordinato in senso decrescente). La funzione che apporta questo miglioramento (e che va scritta dopo la funzione principale appena vista) è la seguente:
int ord_bolle(int a[], int numel) { int i, j, temp, passi; i = 0; do passi = 0; for (j=1; j<numel-i; j++) if (a[j] < a[j-1]) temp = a[j]; a[j] = a[j-1]; a[j-1] = temp; passi++; } i++; while (i < (numel-1) && passi != 0); return (i);
Ordinamento per selezione Vediamo ora il programma dell’ordinamento per selezione, che si basa sul seguente algoritmo: si seleziona inizialmente il più piccolo tra gli elementi a[1], ... , a[n] (o uno dei più piccoli, se ve ne sono più uguali) e lo si scambia con a[1]; 2. tra gli elementi a[2], ... , a[n] del vettore così modificato si seleziona il successivo elemento più piccolo e lo si scambia con a[2], e così si prosegue. 3. Si continua così fino alla iterazione n-1-esima, dopo la quale tutto il vettore a risulta ordinato. Ricordiamo che lo pseudo codice di questo algoritmo è il seguente:
for ogni elemento, dal primo al penultimo trova il più piccolo elemento, dal corrente all’ultimo: ponendo il valore minimo uguale all’elemento corrente; salvando l’indice dell’elemento corrente; for ogni elemento, dal corrente +1 fino all’ultimo if elemento[indice ciclo interno] < valore minimo poni valore minimo = elemento[indice ciclo interno] salva l’indice del nuovo valore minimo trovato fine dell’if fine del for scambia il valore corrente con il nuovo valore minimo Questo pseudo codice esegue l’ordinamento tramite due cicli for nidificati: Il ciclo esterno determina n – 1 passate attraverso il vettore; in ognuna di esse alla variabile min è inizialmente assegnato il valore num[i], dove i è la variabile contatore del ciclo for esterno. Dato che i comincia da 0 e termina a n - 1, ogni elemento del vettore, tranne l’ultimo, viene successivamente designato come quello corrente.
Il ciclo interno percorre ciclicamente gli elementi successivi a quello corrente per selezionare il prossimo valore più piccolo, cominciando dal valore dell’indice i+1 e continuando fino alla fine. Quando viene trovato un nuovo minimo, il suo valore e la sua posizione nel vettore sono memorizzati rispettivamente nelle variabili min e indmin. Una volta completato il ciclo interno, si effettua uno scambio solo se è stato trovato un valore minore di quello nella posizione corrente. Lo pseudo codice si traduce nella seguente funzione ordi_sele:
int ordi_sele(int num[], int dim) { int i, j, min, indmin, temp; for (i = 0; i < (dim-1); i++) min = num[i]; indmin = i; for (j = i+1; j < dim; j++) if (num[j] < min) min = num[j]; indmin = j; } if (min < num[i]) temp = num[i]; num[i] = min; num[indmin] = temp;
Essa si aspetta due argomenti: il vettore da ordinare (ossia l’indirizzo del suo primo elemento) il numero dei suoi elementi e va scritta dopo la seguente funzione principale: #include <stdio.h> #define N 10 void main(void) { int a[N] = {22,5,67,98,45,32,101,99,73,10}; int i; int ordi_sele(int [], int); ordi_sele(a, N); printf("Il vettore ordinato in ordine crescente è:\n"); for (i = 0; i < N; ++i) printf("%d ", a[i]); } Si tratta della stessa funzione impiegata per l’ordinamento a bolle (a parte l’ovvia differenza del nome della funzione chiamata).