Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture...

22
Elaborato di Algoritmi e Strutture Dati Traficante Nicola 11 Febbraio 2010

Transcript of Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture...

Page 1: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso

Elaborato di Algoritmi e Strutture Dati

Traficante Nicola

11 Febbraio 2010

Page 2: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso
Page 3: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso

Sommario

Sommario ........................................................................................................ 3 Indice degli algoritmi ...................................................................................... 4 Indice dei Codici.............................................................................................. 5 Indice delle Figure........................................................................................... 6 Randomized Select .......................................................................................... 7 1.1 Pseudocodice PARTITION ................................................................... 7

1.1.1 Complessità PARTITION ........................................................ 8 1.1.2 Implementazione PARTITION in C++................................... 8

1.2 Pseudocodice RANDOMIZED-PARTITION ............................................. 8 1.2.1 Complessità RANDOMIZED-PARTITION .................................. 9 1.2.2 Implementazione RANDOMIZED-PARTITION in C++ ............ 9

1.3 Pseudocodice RANDOMIZED-SELECT................................................. 10 1.3.1 Complessità RANDOMIZED-SELECT ..................................... 10 1.3.2 Implementazione RANDOMIZED-SELECT in C++ ................ 11

1.4 Testing del RANDOMIZED-SELECT .................................................... 12 Tavole Hash ad Indirizzamento Aperto ........................................................ 14 2.1 Pseudocodice HASH-INSERT.............................................................. 16 2.2 Pseudocodice HASH-SEARCH ............................................................ 16 2.3 Cancellazione.................................................................................... 16 2.4 Funzioni di Hashing ......................................................................... 17 2.5 Analisi della complessità .................................................................. 17 2.6 Testing ............................................................................................. 18

Page 4: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso

Indice degli algoritmi

Algorithm 1Algorithm 1Algorithm 1Algorithm 1 PARTITION(A,p,r) ..................................................................7 Algorithm 2Algorithm 2Algorithm 2Algorithm 2 RANDOMIZED-PARTITION(A,p,r)............................................9 Algorithm 3Algorithm 3Algorithm 3Algorithm 3 RANDOMIZED-SELECT(A,p,r,i) ............................................. 10 Algorithm 4Algorithm 4Algorithm 4Algorithm 4 HASH-INSERT(T,k)............................................................... 16 Algorithm 5Algorithm 5Algorithm 5Algorithm 5 Hash-Search(T,k) ................................................................ 16

Page 5: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso

Indice dei Codici

Code 1Code 1Code 1Code 1 int PARTITION(int A[],int p,int r) ................................................... 8 Code 2Code 2Code 2Code 2 int RANDOMIZED-PARTITION(int A[], int p, int r) .......................... 9 Code 3Code 3Code 3Code 3 int RANDOMGEN(int low, int hi) .................................................... 9 Code 4Code 4Code 4Code 4 int RANDOMIZED-SELECT(int A[], int p, int r, int i)..................... 11

Page 6: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso

Indice delle Figure

Figura 1: Grafico RANDOMIZED-SELECT nel caso medio ........................... 13 Figura 2: Grafico RANDOMIZED-SELECT nel caso pessimo......................... 13 Figura 3: Limite terorico per inserimenti e ricerche (Alpha costante) ...... 19 Figura 4: Ricerca — Risultati sperimentali (Alpha costante)..................... 19 Figura 5: Inserimento — Risultati sperimentali (Alpha costante).............. 20 Figura 6: Limite teorico per inserimenti e ricerche (Alpha Variabile) ...... 21 Figura 7: Ricerca — Risultati sperimentali (Alpha Variabile) ................... 21 Figura 8: Inserimento — Risultati sperimentali (Alpha Variabile) ............ 22

Page 7: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso

Capitolo 1

Randomized Select

L’algoritmo RANDOMIZED-SELECT calcola l’iesima statistica d’ordine di un array, basando il proprio funzionamento sull’algoritmo RANDOMIZED-PARTITION.

Quest’ultimo ad ogni iterazione divide la sequenza da ordinare in due parti, corrispondenti alle sottosequenze degli elementi minori e maggiori di un elemento particolare (il pivot), scelto in maniera random tra tutti gli elementi del vettore.

L’applicazione di tale algoritmo alla RANDOMIZED-SELECT prevede che, scelto a caso un elemento come pivot, si divida la sequenza in due parti, corrispondenti agli elementi maggiori e minori del pivot. A questo punto, se il pivot si trova in posizione i-esima significa che ci sono esattamente i-1 elementi minori, e quindi l'elemento cercato è il pivot stesso; altrimenti si continua ricorsivamente la ricerca solo nella sottosequenza che lo contiene.

L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso generale in cui si voglia trovare l'i-esimo elemento più piccolo di una sequenza; il caso della ricerca della mediana si ottiene assegnando ad i il

valore n/2.

1.1 Pseudocodice PARTITION

Si riporta di seguito lo pseudocodice della PARTITION, procedura base per permutare gli elementi di un array sul posto attorno al pivot.

Algorithm 1Algorithm 1Algorithm 1Algorithm 1 PARTITION(A,p,r)

x � A[r]

i � p-1

for j � p to r-1

do if A[j]<=x

then i � i+1

scambia A[i] ↔ A[j]

scambia A[i+1] ↔ A[r]

Page 8: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso

return i+1

Tale procedura esegue una partizionamento dell’array in tre sotto-

vettori. Per effettuare tale partizionamento viene preso in considerazione l’ultimo elemento dell’array che assume il ruolo di elemento pivot. Tutti gli altri elementi dell’array vengono confrontati con il pivot, e se minori o uguali vengono posti nella parte sinistra del vettore, se maggiori nella parte destra. Dopo aver eseguito tali confronti viene poi posto nella posizione opportuna l’elemento pivot.

1.1.1 Complessità PARTITION

Il tempo di esecuzione di PARTITION è Θ(n) dato che ogni iterazione del ciclo for prevede un numero costante di operazioni ed il numero totale di iterazioni è Θ(n).

1.1.2 Implementazione PARTITION in C++

Di seguito l’implementazione in C++ della funzione PARTITION.

Code 1Code 1Code 1Code 1 int PARTITION(int A[],int p,int r)

intintintint Partition((((intintintint A[],[],[],[],intintintint p,,,,intintintint r)))) {{{{ intintintint x ==== A[[[[r];];];]; //elemento di pivot intintintint i ==== p----1;;;; //inizio elementi <= pivot forforforfor ((((intintintint j====p;;;;j<<<<r;;;;j++)++)++)++) {{{{ ifififif((((A[[[[j]<=]<=]<=]<=x)))) {{{{ i====i++++1;;;; //scambia A[i] e A[j] swap ((((A[[[[i],],],],A[[[[j]);]);]);]); }}}} }}}} swap ((((A[[[[i++++1],],],],A[[[[r]);]);]);]); //restituisce l'indice di partizionamento dell'array tale per cui //A[p...q-1]<=A[q]<A[q+1...r] returnreturnreturnreturn i++++1;;;; }}}}

1.2 Pseudocodice RANDOMIZED-PARTITION

E’ possibile che nel chiamare la PARTITION l’elemento pivot scelto sia sempre il maggiore o il minore dell’array. Questo comporta notevoli problemi in tutte le procedure che dovessero richiamare ricorsivamente tale procedura.

Ne è stata perciò proposta una versione randomizzata per ottenere prestazioni paragonabili al caso medio nella maggior parte dei casi.

Page 9: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso

Algorithm 2Algorithm 2Algorithm 2Algorithm 2 RANDOMIZED-PARTITION(A,p,r)

i � Random(p,r)

scambia A[r] ↔ A[i]

return Partition(A,p,r)

In tale versione dell’algoritmo viene utilizzato il metodo del

campionamento aleatorio per selezionare l’elemento pivot. Anziché scegliere come pivot sempre A[r] (l’ultimo elemento dell’array) , viene selezionato a caso un elemento dal sotto-array A[p….r], dopodichè tale elemento viene scambiato con A[r] per poter essere utilizzato come pivot. Con tale modifica si può dunque prevedere che la ripartizione dell’array di input potrà essere ben bilanciata in media.

1.2.1 Complessità RANDOMIZED-PARTITION

Avendo aggiunto solo istruzioni a costo costante - generazione di un indice random e scambio tra elementi di un array - la complessità resta equivalente alla versione non randomizzata, quindi anche in questo caso il numero di operazioni è dell’ordine di Θ(n).

1.2.2 Implementazione RANDOMIZED-PARTITION in C++

Di seguito l’implementazione in C++ della funzione RANDOMIZED-PARTITON.

Code 2Code 2Code 2Code 2 int RANDOMIZED-PARTITION(int A[], int p, int r)

intintintint Randomized_Partition((((intintintint A[],[],[],[], intintintint p,,,, intintintint r)))) {{{{ //calcola l'indice i in maniera random nell'intervallo (p,r) intintintint i ==== RandomGen((((p,,,,r);););); //scambia A[i] e A[r] swap ((((A[[[[i],],],], A[[[[r]);]);]);]); //restituisce l'indice di partizionamento restituito dalla partition

returnreturnreturnreturn Partition((((A,,,,p,,,,r);););); }}}}

La procedura funziona allo stesso modo della PARTITION eccetto che per

lo scambio tra l’elemento finale dell’array ed uno scelto a caso al suo interno. La scelta dell’indice casuale da scambiare è affidata alla funzione

RANDOMGEN di cui si riporta di seguito l’implementazione in C++.

Code 3Code 3Code 3Code 3 int RANDOMGEN(int low, int hi)

//Generatore numeri casuali nell'intervallo [low, hi] intintintint RandomGen((((intintintint low,,,, intintintint hi){){){){ intintintint generated;;;;

Page 10: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso

intintintint range=(=(=(=(hi----low)+)+)+)+1;;;; generated ==== low++++intintintint((((range****rand()/(()/(()/(()/(RAND_MAX ++++ 1.0));));));)); returreturreturreturnnnn generated;;;; }}}}

Tale funzione ritorna in input un numero casuale compreso nell’intervallo

tra i due valori passatigli come parametri di input.

1.3 Pseudocodice RANDOMIZED-SELECT

La procedura riceve in ingresso il vettore A e 3 indici (p, r, i) che indicano rispettivamente l’indice iniziale e l’indice finale da considerare per la ricerca della statistica d’ordine - definiscono cioè un sottovettore di A - e la i-esima statistica d’ordine in tale sottovettore, ossia l’i-esimo elemento più piccolo in esso.

Di seguito si riporta lo pseudocodice della procedura.

Algorithm 3Algorithm 3Algorithm 3Algorithm 3 RANDOMIZED-SELECT(A,p,r,i)

if p=r then

return A[p]

q � Randomized-Partition(A,p,r)

k � q-p+1

if i=k then

return A[q]

if i<k then

return Randomized-Select(A,p,q-1,i)

if i>k then

return Randomized-Select(A,q+1,r,i-k)

Si osservi che quando la funzione viene invocata ricorsivamente sulla

sottosequenza dei valori maggiori del pivot, il parametro i viene decrementato di k unità; infatti, poiché si scartano i primi k elementi, l'elemento cercato si troverà ora k posizioni più avanti.

1.3.1 Complessità RANDOMIZED-SELECT

La complessità dell’algoritmo RANDOMIZED-SELECT è valutabile considerando il suo caso peggiore ed il suo caso migliore.

Consideriamo il caso, particolarmente sfortunato, in cui ad ogni iterazione venga scelto come pivot l'elemento massimo della sequenza (lo stesso vale se viene scelto sempre il minimo): la dimensione della sequenza da trattare diminuisce di un solo elemento (il pivot) ad ogni iterazione, e quindi è necessario svolgere n iterazioni. Poiché ad ogni iterazione bisogna confrontare n elementi con il pivot, la complessità totale nel caso pessimo è O(n²). Nel caso migliore, invece, il partizionamento risulta molto favorevole:

Page 11: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso

verrà, dunque eseguita una chiamata ricorsiva di RANDOMIZED-SELECT su n/2 elementi. La ricorrenza che descrive il tempo di esecuzione risulta quindi:

T(n)=T(n/2) + Θ(n) dove a=1 e b=2

Calcolando 1log =abn e confrontandolo con f(n) possiamo risolvere la ricorrenza facendo riferimento al terzo caso del teorema Master. Bisogna dunque verificare la condizione di stabilità:

a f(n/b)<=cf(n) per n sufficientemente grande e c<1 n/2<=cn da cui c>=1/2 Quindi la soluzione della ricorrenza è T(n)=Θ(n). Ovvero nel caso

migliore l’algoritmo ha una complessità che è lineare con n. In realtà con delle considerazioni probabilistiche e statistiche, è possibile dimostrare che l’algoritmo ha una complessità lineare anche nel caso medio.

1.3.2 Implementazione RANDOMIZED-SELECT in C++

Di seguito si riporta il codice implementativo in C++ della RANDOMIZED-SELECT.

Per quanto riguarda l’intero file .cpp si rimanda ad i files allegati.

Code 4Code 4Code 4Code 4 int RANDOMIZED-SELECT(int A[], int p, int r, int i)

intintintint Randomized_Select ((((intintintint A[],[],[],[],intintintint p,,,,intintintint r,,,,intintintint i)))) {{{{ //se nell'array c'è un solo elemento restituisce A[p] ifififif ((((p========r)))) returnreturnreturnreturn A[[[[p];];];]; //calcola l'indice di partizionamento in maniera random usando Randomized Partition intintintint q====Randomized_Partition((((A,,,,p,,,,r);););); //calcola il numero di elementi presenti nella partizione bassa più il pivot intintintint k====q----p++++1;;;; //se i==k l'elemento cercato è proprio il pivot ifififif ((((i========k)))) returnreturnreturnreturn A[[[[q];];];]; //se i<k l'elemento trovato si trova nella sottosequenza inferiore ifififif((((i<<<<k)))) returnreturnreturnreturn Randomized_Select((((A,,,,p,,,,q----1,,,,i);););); //altrimenti l'elemento trovato si trova nella sottosequenza superiore //la chiamata ricorsiva avrà come indice i-k in quanto sono già stati trovati //k elementi più piccoli di A[p...r] elseelseelseelse returnreturnreturnreturn Randomized_Select((((A,,,,q++++1,,,,r,,,,i----k););););

Page 12: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso

}}}}

La procedura riceve in ingresso il vettore A, i due indici interni p ed r

che ne definiscono una sottoparte e l’indice i relativo alla i-esima statistica d’ordine da ricercare nel vettore (per considerare tutto il vettore la procedura và chiamata con p=0 e r=dim[A]-1). In output vi è l’i-esimo elemento più piccolo di A.

Per ulteriori dettagli si rimanda ad i commenti interni al codice.

1.4 Testing del RANDOMIZED-SELECT

Per il testing automatico sono state previste due distinte istanze del problema, per esemplificare il caso medio e quello peggiore.

Il caso medio prevede che il vettore sia popolato da elementi casuali scelti tra 0 e la sua dimensione, e che per ogni vettore venga sempre scelta come i-esima statistica d’ordine la sua mediana.

Per esemplificare il caso peggiore, che si ricorda si ha quando ad ogni chiamata ricorsiva della RANDOMIZED-SELECT il vettore ha dimensione ridotta costantemente di una unità rispetto a quello dell’input originario, il vettore di ingresso viene popolato di elementi tutti ugali e viene sempre scelta la prima statistica d’ordine.

In comune le due procedure prevedono il test dell’algoritmo su vettori con dimensione da 100 alla massima disponibile (limitata superiormente dallo stack di sistema che deve tenere traccia di tutte le chiamate ricorsive per “srotolare” la ricorsione e che quindi limita la dimensione massima dell’array di input — 65000 per il caso medio e 6000 per il caso peggiore) con passo 100 e la ripetizione di ogni prova 3 volte con successiva media dei valori temporali per avere una migliore stima e per evitare problemi di “picchi” dovuti essenzialmente a condizioni di input casualmente particolarmente sfavorevoli o, in maniera più marcata, ad eventuali context-switch del sistema operativo durante l’esecuzione dell’algoritmo.

I risultati ottenuti sono stati salvati in formato CSV ed i grafici sono riportati qui di seguito.

Page 13: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso

Figura 1: Grafico RANDOMIZED-SELECT nel caso medio

Figura 2: Grafico RANDOMIZED-SELECT nel caso pessimo

Page 14: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso

Capitolo 2

Tavole Hash ad Indirizzamento

Aperto

Una tavola Hash è una generalizzazione della nozione di array ordinario. L’indirizzamento diretto in un array ordinario sfrutta la possibilità di

esaminare una posizione arbitraria in un array nel tempo O(1). L’indirizzamento diretto è però applicabile solo quando ci si può permettere di allocare un array che ha una posizione per ogni possibile chiave.

Quando il numero di chiavi effettivamente memorizzate è piccolo rispetto al numero totale di chiavi possibili, le tavole hash diventano un’alternativa efficiente per indirizzare direttamente un array, in quanto le tavole hash usano array di dimensione proporzionale al numero di chiavi effettivamente memorizzate. In questo caso per effettuare l’indirizzamento invece di utilizzare la chiave direttamente come indice, si calcola quest’ultimo a partire dalla chiave mediante una funzione hash.

Una tavola hash è una struttura dati efficace per implementare le operazioni di dizionario, cioè Ricerca, Inserimento e Cancellazione. Sebbene infatti la ricerca di un elemento in una tavola hash, nel caso peggiore, richieda un tempo Θ(n), lo stesso tempo richiesto da un operazione di ricerca in una lista concatenata, sotto opportune ipotesi il tempo atteso per tale operazione è O(1). Ciò dimostra che l’hashing è una tecnica estremamente pratica ed efficace, in quanto le operazioni fondamentali sui dizionari richiedono in media un tempo O(1).

In precedenza si è già accennata la problematica relativa all’utilizzo dell’indirizzamento diretto: se l’universo delle chiavi U è esteso, memorizzare una tavola T di dimensione |U| può essere impraticabile. Se l’insieme delle chiavi K effettivamente utilizzate è molto più piccolo di U, allora la maggior parte della memoria allocata per la tavola T risulterebbe sprecata. In questo caso è molto più conveniente utilizzare una tavola hash, la quale alloca una tavola di dimensione |K|, con evidente miglioramento nell’utilizzo di memoria.

Mentre però con l’indirizzamento diretto, l’elemento con chiave k viene inserito nella k-esima locazione, con l’hashing l’elemento sarà memorizzato nella cella h(k), utilizzando una funzione hash per determinare la locazione.

La funzione hash ha dunque il compito di ridurre l’intervallo degli indici dell’array.

Page 15: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso

Con questa tecnica però può capitare che due elementi siano assegnati ad una stessa cella. Tale evento si chiama collisionecollisionecollisionecollisione. La soluzione ottimale a tale problematica, sarebbe evitare completamente le collisioni, ma ciò è impossibile.

Una possibile soluzione è quella del concatenamento: ad ogni cella viene associata una lista concatenata. Quando un elemento viene assegnato ad una determinata cella, viene inserita nell’apposita lista. Tale soluzione ha anche i vantaggio di rendere estremamente semplice l’implementazione delle operazioni di dizionario. Le prestazioni di tale soluzione dipendono fortemente dalla funzione hash scelta. Una buona funzione hash deve soddisfare l’ipotesi di hashing uniforme semplicehashing uniforme semplicehashing uniforme semplicehashing uniforme semplice: ogni chiave ha la stessa possibilità di essere mandata in una qualsiasi delle m celle, indipendentemente dalla cella in cui viene mandata qualsiasi altra chiave. Tale condizione è però difficilmente verificabile nella pratica. Spesso è però possibile utilizzare delle tecniche euristiche per realizzare funzioni hash con buone prestazioni.

Due esempi di funzioni hash sono: metodo della divisionemetodo della divisionemetodo della divisionemetodo della divisione: calcola il valore hash come resto della divisione tra la chiave e un determinato numero primo. Tale approccio fornisce buoni risultati, purché il numero primo sia scelto in maniera tale da non essere correlato ad alcuna regolarità nella distribuzione delle chiavi. metodo delmetodo delmetodo delmetodo della moltiplicazionela moltiplicazionela moltiplicazionela moltiplicazione: prima si moltiplica la chiave k per un opportuno valore A, 0<A<1 e se ne estrae la parte frazionaria di kA. Poi si moltiplica per m (numero di celle) e se ne prende la parte intera inferiore del risultato. In questo caso si ha il vantaggio che il valore di m non è critico.

Nell’indirizzamento apertoindirizzamento apertoindirizzamento apertoindirizzamento aperto invece tutti gli elementi sono memorizzati nella stessa tavola hash: in ogni locazione è presento o un elemento dell’insieme dinamico o la costante NIL. Quando si cerca un elemento si esaminano sistematicamente le cella della tavola finché non si trova l’elemento desiderato o ci si accorge che l’elemento non è presente. Con tale tipo di indirizzamento non si hanno né liste né elementi memorizzati all’esterno della tavola. Non è necessario utilizzare, e dunque né memorizzare puntatori, con evidente risparmio di memoria. Nell’indirizzamento aperto la tavola può riempirsi al punto tale da non poter effettuare più inserimenti, perciò l’indice a=n/m che indica il fattore di riempimento della tavola non può mai superare il valore 1. Per eseguire un inserimento, si ispezionano in successione le posizione della tavola hash finché non si trova una cella vuota in cui inserire la chiave. Anziché seguire sempre lo stesso ordine 0,1,…,m-1 (che richiede un tempo di ricerca Θ(n)), la sequenza delle posizioni esaminate durante un ispezione dipende dalla chiave da inserire.

Si deve estendere dunque la funzione hash in modo da includere l’ordine di ispezione come secondo input. Si richiede che per ogni chiave k, la sequenza di ispezione <h(k,0),h(k,1),…,h(k,m-1)> sia una permutazione di <0,1,…,m-1>, in modo tale che ogni posizione della tavola hash possa essere considerata come possibile cella in cui inserire una nuova chiave mentre la tavola si riempie. Detto U l’universo di tutte le possibili chiavi, la funzione hash diventa quindi:

h : U x {0, 1, …, m-1} → {0, 1, …, m-1}

Page 16: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso

2.1 Pseudocodice HASH-INSERT

Supponendo che gli elementi della tavola hash T non presentino dati satellite, lo pseudocodice relativo all’operazione di inserimento sarà il seguente:

Algorithm 4Algorithm 4Algorithm 4Algorithm 4 HASH-INSERT(T,k)

i�0

repeat j�h(k,i)

if T[j] = NIL

then T[j] �k

return j

else i�i+1

until i=m

error ‘overflow sulla tabella hash’

2.2 Pseudocodice HASH-SEARCH

Quando si effettua la ricerca di un elemento l’algoritmo segue la stessa sequenza di ispezione che ha esaminato l’algoritmo di inserimento quando ha inserito la chiave k.

La ricerca può terminare senza successo quando trova una cella vuota, poiché la chiave dovrebbe essere stata inserita in quella locazione (questo è vero se non è prevista l’operazione di cancellazione).

La procedura di ricerca riceve in input la tavola T e la chiave k da cercare, e fornisce in output la locazione j in cui è memorizzata k, se la ricerca ha successo, NIL altrimenti.

Algorithm 5Algorithm 5Algorithm 5Algorithm 5 Hash-Search(T,k)

i�0

repeat j�h(k,i)

if T[j]=k

then return j

i�i+1

until T[j]=NIL o i=m

return NIL

2.3 Cancellazione

Per quel che riguarda l’operazione di cancellazione, questa è un’operazione complessa in quanto non ci si può limitare a porre il valore NIL all’interno

Page 17: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso

della cella da cui eliminare l’elemento, in quanto così facendo non sarebbe più possibile accedere a tutti quegli elementi che hanno incontrato nella loro sequenza di ispezione tale cella. Una possibile soluzione è marcare tali celle con l’etichetta DELETED. Si deve poi modificare anche la procedura di inserimento per prevedere la presenza di tali tipologie di celle.

2.4 Funzioni di Hashing

Bisogna ora vedere come è possibile implementare le funzioni hash che generino le sequenze di ispezione. Tali funzioni dovrebbero rispettare l’ipotesi di hashing uniformehashing uniformehashing uniformehashing uniforme: ogni chiave ha la stessa probabilità di avere come sequenza di ispezione una delle m! permutazioni <0,1,…,m-1>.

Poiché è difficile implementare il vero hashing uniforme, nella pratica si utilizzano delle approssimazioni accettabili. Tre tecniche vengono comunemente utilizzate per generare le sequenze di ispezione. Ispezione LineareIspezione LineareIspezione LineareIspezione Lineare: data una funzione hash ordinaria h’, il metodo dell’ispezione lineare usa la funzione hash:

h(k,i) = (h’(k)+i)mod m per i=0,1,…,m-1 L’ispezione lineare presenta un problema noto come addensamento primarioaddensamento primarioaddensamento primarioaddensamento primario: si formano lunghe file di celle occupate che aumentano il tempo medio di ricerca. Questo tipo di ispezione genera solo m possibili permutazioni. Ispezione QuadraticaIspezione QuadraticaIspezione QuadraticaIspezione Quadratica: la funzione hash utilizzata è:

h(k,i) = (h’(k) + c1i + c2i2)mod m

con h’ funzione hash ausiliaria, c1,c2≠0 e i=0,1,…,m-1. Questa tecnica funziona meglio dell’ispezione lineare, ma bisogna scegliere opportunamente le costanti c1 e c2 e il valore di m. Anche questa tecnica soffre di una tipologia di addensamento più lieve, detto addensamento secondario. Poiché la prima posizione determina l’intera sequenza di ispezione anche in questo caso vengono utilizzate solo m sequenze di ispezione distinte. Doppio HashingDoppio HashingDoppio HashingDoppio Hashing: è uno dei metodi migliori disponibili per l’indirizzamento aperto. Usa una funzione hash della forma:

h(k,i) = (h1(k) + ih2(k))mod m con h1 e h2 funzioni hash ausiliarie. Affinchè venga ispezionta l’intera tavola hash h2(k) deve essere relativamente primo con la dimensione m della tavola. Si può scegliere ad esempio m primo e porre:

h1(k) = k mod m h2(k) = 1 + (k mod m’) con m’=m-1

Il doppio hashing è migliore rispetto ai precedenti in quanto usa m2 sequenze di ispezione, rispetto alle m usate dalle tecniche precedenti. Le prestazioni del doppio hashing risultano molto prossime a quelle dello schema ideale dell’hashing uniforme.

2.5 Analisi della complessità

L’analisi della complessità per quel che riguarda l’indirizzamento aperto è espressa in termini del fattore di carico a=n/m, con n numero di chiavi memorizzate nella tavola ed m dimensione della tabella. Poiché con

Page 18: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso

l’indirizzamento aperto abbiamo al massimo un elemento per cella, a è sempre minore di uno, o al più uguale quando la tabella è piena — in quest’ultimo caso una operazione di inserimento genera un overflow della tabella.

Supponendo di applicare l’hashing uniforme, il numero atteso di ispezioni in una ricerca senza successo è al massimo 1/(1-a).

Infatti sicuramente viene eseguita un’ispezione. La probabilità che venga eseguita una seconda ispezione perché l prima cella è occupata è pari ad a. La probabilità di eseguire un terzo accesso è pari ad a2. Si ottiene così una serie geometrica

∑∞

=

=++++0

32 ...1i

iaaaa

con ragione a<1, da cui il risultato. Nel caso di ricerca con successo invece il numero atteso di ispezioni sarà

al massimo

aa −1

1ln

1

2.6 Testing

Per verificare sperimentalmente i risultati teorici sono state implementate due distinte procedure in C++. In comune ovviamente hanno i codici delle funzioni di base, mentre l’unica differenza risiede nel main e in alcune funzioni di utilità.

Il primo testing è effettuato con una tavola hash a fattore di riempimento a=0,8 costante e dimensioni della tavola via via crescenti.

A tal fine, ad ogni ciclo viene creata una tabella hash vuota — si è assunto l’elemento -1 come costante NIL — e viene riempita con elementi casuali fino ad arrivare al fattore di riempimento a. Viene in seguito effettuata una ricerca ed un inserimento, calcolando per entrambe il numero di iterazioni interne delle funzioni di ispezione. Il tutto viene ciclato per un predeterminato numero di volte, incrementando ogni volta la dimensione m della tabella di hashing in modo tale che m sia sempre un numero primo, al fine di garantire una buona uniformità.

Tale test ha il fine di dimostrare che, in media probabilistica, al crescere della dimensione della tabella di hash, se il fattore di riempimento resta costante i tempi di ricerca ed inserimento sono sempre inferiori a 1/(1-a).

I risultati grafici sono mostrati di seguito.

Page 19: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso

1/(1-a)

0

1

2

3

4

5

6

2

27

1

63

1

10

21

14

47

18

71

23

09

27

41

32

29

36

77

41

53

46

49

51

19

56

47

61

31

66

37

71

27

76

43

81

79

86

99

Dimensione

1/(1

-alp

ha)

1/(1-a)

Figura 3: Limite terorico per inserimenti e ricerche (Alpha costante)

Risultati Sperimentali Ricerca

0

20

40

60

80

100

120

2

34

9

79

7

12

83

17

87

23

39

28

57

34

61

40

07

45

97

51

89

57

91

63

61

69

83

76

07

82

63

Dimensione

Nu

me

ro It

eraz

ion

i

Ricerca

1/(1-a)

Potenza (Ricerca)

Figura 4: Ricerca — Risultati sperimentali (Alpha costante)

Page 20: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso

Risultati Sperimentali Inserimento

0

50

100

150

200

250

300

350

400

450

5002

34

9

79

7

12

83

17

87

23

39

28

57

34

61

40

07

45

97

51

89

57

91

63

61

69

83

76

07

82

63

Dimensione

Nu

mer

o It

eraz

ioni

Ricerca

1/(1-a)

Potenza (Ricerca)

Figura 5: Inserimento — Risultati sperimentali (Alpha costante)

Il secondo testing è invece effettuato con una tavola hash a dimensione costante ma a fattore di riempimento crescente.

Ciò è effettuato creando una tavola hash con dimensione pari ad un numero primo ed effettuando su di essa ricerche ed inserimenti fino alla saturazione della tabella stessa.

Tale test ha lo stesso scopo del precedente, e dimostra anch’esso il limite teorico questa volta con il fattore 0<a<1.

I risultati grafici sono mostrati di seguito.

Page 21: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso

Figura 6: Limite teorico per inserimenti e ricerche (Alpha Variabile)

Figura 7: Ricerca — Risultati sperimentali (Alpha Variabile)

Page 22: Elaborato di Algoritmi e Strutture Dati - UniNa STiDuEunina.stidue.net/Algoritmi e Strutture Dati/Materiale...L'algoritmo, di cui è riportato lo pseudo-codice, è relativo al caso

Figura 8: Inserimento — Risultati sperimentali (Alpha Variabile)