Algoritmi di ricerca

advertisement
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
Scarica