determinare una funzione di accesso non biunivoca ma che riduca al massimo la possibilità di collisioni (funzione hash),

Documenti analoghi
Gestione di files Motivazioni

VBA è un linguaggio di scripting derivato da Visual Basic, da cui prende il nome. Come ogni linguaggio ha le sue regole.

INFORMATICA E PROGRAMMAZIONE PROF. M. GIACOMIN ESPERIENZA IN AULA: ELABORAZIONE DI IMMAGINI A COLORI IN LINGUAGGIO C

3. Terza esercitazione autoguidata: progetto gestione voli

Esercitazione n o 3 per il corso di Ricerca Operativa

Programmazione Orientata agli Oggetti in Linguaggio Java

Universita' di Ferrara Dipartimento di Matematica e Informatica. Algoritmi e Strutture Dati. Rappresentazione concreta di insiemi e Hash table

IL CONCETTO DI FILE. È illecito operare oltre la fine del file.

4 Le liste collegate 4.0. Le liste collegate. 4 Le liste collegate Rappresentazione di liste 4.1 Rappresentazione di liste

Il Metodo Scientifico

Creare una funzione float square(float x). La funzione deve restituire il quadrato del parametro x.

Esercizio: memoria virtuale

ESAME SCRITTO DI ELEMENTI DI INFORMATICA E PROGRAMMAZIONE. 10 Settembre 2013

RICORSIVITA. Vediamo come si programma la soluzione ricorsiva al problema precedente: Poniamo S 1 =1 S 2 =1+2 S 3 =1+2+3

Caratteri e stringhe Esercizi risolti

3) Il seguente numerale A1F0 in base 16 a quale numero in base 10 corrisponde?

2. Spiegare brevemente qual è la funzione del compilatore e la sua importanza per il programmatore.

ESAME SCRITTO DI ELEMENTI DI INFORMATICA E PROGRAMMAZIONE. 27 Gennaio 2015

CAPITOLO V. DATABASE: Il modello relazionale

Soluzioni degli esercizi di riepilogo (Fondamenti di Informatica 1 Walter Didimo)

Informatica B. Sezione D. Scuola di Ingegneria Industriale Laurea in Ingegneria Energetica Laurea in Ingegneria Meccanica

Strutture. Strutture e Unioni. Definizione di strutture (2) Definizione di strutture (1)

puntatori Lab. Calc. AA 2007/08 1

Informatica 3. LEZIONE 21: Ricerca su liste e tecniche di hashing. Modulo 1: Algoritmi sequenziali e basati su liste Modulo 2: Hashing

3. La sintassi di Java

Introduzione alla programmazione in C

Esercizi sulla conversione tra unità di misura

I file Laboratorio di Linguaggi di Programmazione a.a. 2001/2002

Le variabili. Olga Scotti

2) FILE BINARI: è una sequenza di byte avente una corrispondenza uno a uno con la sequenza ricevuta dal dispositivo esterno.

Esercitazione 3. Corso di Fondamenti di Informatica

Problem solving elementare su dati vettoriali

OpenDataLazio Formia 9 aprile 2015 Laboratorio. Roberto Angeletti

Lezione 9: Strutture e allocazione dinamica della memoria

Esercizi di programmazione in C

Ricerca sequenziale di un elemento in un vettore

Vettori Algoritmi elementari di ordinamento

Politecnico di Milano - Facoltà di Ingegneria INFORMATICA A - Corso per allievi GESTIONALI - Prof. C. SILVANO A. A. 2001/ febbraio A

0.1 Esercizi calcolo combinatorio

Laboratorio di Programmazione Lezione 1. Cristian Del Fabbro

RICERCA DI UN ELEMENTO

Studente (Cognome Nome): Corso di Informatica Corso di Laurea in Ingegneria Gestionale a.a Primo scritto 11 Gennaio 2008

void funzioneprova() { int x=2; cout<<"dentro la funzione x="<<x<<endl; }

Massimi e minimi vincolati in R 2 - Esercizi svolti

Esercitazione # 6. a) Fissato il livello di significatività al 5% si tragga una conclusione circa l opportunità di avviare la campagna comparativa.

Inizializzazione, Assegnamento e Distruzione di Classi

Laboratorio di Algoritmi e Strutture Dati

Regola del partitore di tensione

Esercitazioni di Reti Logiche. Lezione 1 Rappresentazione dell'informazione. Zeynep KIZILTAN zkiziltan@deis.unibo.it

Illustrazione 1: Telaio. Piantanida Simone 1 G Scopo dell'esperienza: Misura di grandezze vettoriali

Esempio: Array di struct

Il comando provoca il salvataggio dello stato e la terminazione dell esecuzione.

Fondamenti di Informatica T-1, 2009/2010 Modulo 2 Prova d Esame 5 di Giovedì 15 Luglio 2010 tempo a disposizione 2h30'

Files in C++ Fondamenti di Informatica. R. Basili. a.a

Esercizi svolti durante le ore di Informatica e Sistemi automatici nelle classi del Liceo Scientifico Tecnologico del Liceo Milli di Teramo

Bontà dei dati in ingresso

argomenti Hashing Richiamo sul concetto di dizionario Tabelle hash ! Hashing ! Funzioni hash per file ! Insieme di coppie del tipo <elemento, chiave>

Appello di Informatica B

RICORSIONE - schema ricorsivo (o induttivo) si esegue l'azione S, su un insieme di dati D, mediante eventuale esecuzione di

Esempi di esercizi d esame

I puntatori e l allocazione dinamica di memoria

Le operazioni di allocazione e deallocazione sono a carico del sistema.

Ingegneria del Software

Le Stringhe. Un introduzione operativa. Luigi Palopoli

Breve riepilogo della puntata precedente:

Programmazione Dichiarativa. Programmazione Logica. SICStus PROLOG PROLOG. Bob Kowalski: "Algoritmo = Logica + Controllo"

Algebra di Boole: Concetti di base. Fondamenti di Informatica - D. Talia - UNICAL 1. Fondamenti di Informatica

/** * VETTORE DINAMICO elementi */ private Vector elementi; /** * METODO COSTRUTTORE */ public coda() { elementi=new Vector(); }

DESCRIZIONE CREAZIONE APP Si suddivide in 4 fasi di lavoro: 1. PIANIFICAZIONE; 2. PROGETTAZIONE; 3. SVILUPPO; 4. DISTRIBUZIONE.

1 Automi Cellulari (Rev )

Esercitazione Informatica I AA Nicola Paoletti

Algoritmi di Ricerca. Esempi di programmi Java

Appunti del corso di Informatica 1 (IN110 Fondamenti) 3 Modelli di calcolo

Dynamic Linking. Introduzione Creazione di una libreria dinamica Uso di una libreria dinamica

Nascita di Java. Che cos e Java? Caratteristiche di Java. Java: linguaggio a oggetti

COMPITO DI LABORATORIO DI PROGRAMMAZIONE Luglio Soluzione degli Esercizi

Fondamenti di Informatica 2


Unità Didattica 3 Linguaggio C. Generalità sulle Funzioni. Variabili locali e globali. Passaggio di parametri per valore.

(1) - - (4) R4 = R7 * R7 (4) (2) (3) 5 - (4) rinviata perché in WAW con (3) 6 (3) e (4) ritirabili ma attendono il completamento di (2) (2) (3) (4)

Programmazione I / Informatica generale Prova scritta 11 Giugno 2008

L algoritmo di ricerca binaria. Daniele Varin LS Ing. Informatica Corso di Informatica teorica Docente: prof. Paolo Sipala

Insegnare relatività. nel XXI secolo

CARATTERI E STRINGHE Caratteri e stringhe Funzioni della libreria standard I/O Funzioni della libreria di gestione delle stringhe

Il tipo di dato astratto Pila

Esonero del corso di Programmazione a Oggetti

Corso di Fondamenti di Informatica

Informatica B. Sezione D. Scuola di Ingegneria Industriale Laurea in Ingegneria Energetica Laurea in Ingegneria Meccanica

Esercizio: gestione di un conto corrente

Indirizzo di una funzione. Puntatori a funzioni. Definizione di variabili. Definizione di variabili

Variabili e tipi di dato

TUTORATO di LINGUAGGI I

Librerie. Laboratorio di Informatica Antonio Monteleone 28

Fondamenti di Informatica e Laboratorio T-AB T-16 Progetti su più file. Funzioni come parametro. Parametri del main

Richiesta pagina PHP (es: index.php)

INFORMATICA - I puntatori Roberta Gerboni

Prof.ssa Laura Pagnozzi Prof. Ivano Coccorullo. Calcolo Combinatorio

SAPIENZA Università di Roma, Facoltà di Ingegneria

Appunti del corso di Informatica 1. 6 Introduzione al linguaggio C

COGNOME E NOME (IN STAMPATELLO) MATRICOLA

Transcript:

Tabelle Hash Per accedere agli elementi di una tabella è possibile utilizzare metodi di accesso diretto ai dati (detti anche metodi di accesso calcolato) che sono basati sulla possibilità di associare ciascuna elemento della tabella con una posizione di memoria mediante una funzione, detta funzione di accesso, che consente di determinare, sulla base della chiave dell'elemento (o di altre sue caratteristiche, come ad esempio le coordinate dell'elemento stesso) la corrispondente posizione. Un tipico esempio di funzione di accesso diretto, basato su un sistema di coordinate, è la matrice che consente di localizzare un elemento in base ai suoi indici. In generale una funzione di accesso è una funzione f che ad ogni chiave associa univocamente una posizione della tabella in modo che date due chiavi K i, e K j con K i K j si abbia che f(k i ) f(k j ). Nella realtà per chiavi alfanumeriche o per chiavi numeriche che possono assumere valori in un insieme molto più grande del numero effettivo di chiavi presenti, definire una funzione di accesso senza introdurre sprechi nel dimensionamento della tabella non è affatto semplice. Ad esempio, se dobbiamo memorizzare gli identificatori che si incontrano in un programma Pascal (stringhe alfanumeriche in genere di al più otto caratteri di cui il primo alfabetico) abbiamo che le possibili chiavi, cioè i nomi degli identificatori, sono almeno 26. 36 7 e non è quindi pensabile pensare di utilizzare il valore numerico corrispondente alla stringa di caratteri come indice di tabella, soptrattutto se si considera che in realtà un programma Pascal avrà al più 200 300 identificatori. Per un utilizzo pratico delle funzioni di accesso siamo costretti a perdere la caratteristica fondamentale di esse e cioè la biunivocità tra posizioni in tabella e possibili valori delle chiavi; quindi potrà avvenire che a chiavi diverse corrisponda la stessa posizione, che si verifichi, cioè, una collisione. In questo caso sarà necessario allocare una delle due chiavi collidenti (sinonimi) in un'altra posizione di memoria. I problemi che sorgono sono dunque due: determinare una funzione di accesso non biunivoca ma che riduca al massimo la possibilità di collisioni (funzione hash), individuare dei criteri per allocare sinonimi (gestione delle collisioni). Una funzione hash (di spezzettamento) è una funzione di accesso non biunivoca che, sfruttando opportunamente la struttura delle chiavi, distribuisce gli indirizzi calcolati nel modo più uniforme possibile su tutto lo spazio degli indirizzi disponibili all'interno della tabella in modo da ridurre il più possibile il numero di collisioni. Data una tabella con p posizioni disponibili e indicato con k la rappresentazione in binario di una chiave K (cioè, la stringa di bit della chiave è letta come se fosse il numero binario k), alcuni classici esempi di funzioni hash sono i seguenti: (metodo del quadrato centrale) viene determinato k 2 e vengono estratti m bit al centro del risultato, dove m = logp ; il numero risultante è ovviamente inferiore a p e viene utilizzato per indirizzare la tabella per evitare di avere indirizzi non utilizzati conviene dimensionare p uguale esattamente a 2 m ; (metodo dell'avvolgimento) la rappresentazione binaria k viene spezzata in segmenti di m bit (aggiungendo eventuali 0 all'ultimo segmento nel caso esso abbia meno di m bit), vengono sommati i vari segmenti e vengono prelevati gli m bit meno significativi della somma m è definito come nel metodo del quadrato centrale; (metodo del modulo) come indirizzo viene utilizzato il resto della divisione di k per p; l'indirizzo assume valori tra 0 e p-1 si noti che in questo caso è opportuno che p sia un numero dispari al fine di ridurre la probabilità di collisioni; ancora meglio se p è pari a un numero primo. Come esempio mostriamo come derivare k a partire da una chiave di tipo Stringa. Supponiamo per semplicità che il valore k restituito sia un intero. Sommiamo i codici ASCII dei vari caratteri

componenti la stringa e applichiamo la funzione modulo per evitare di avere valori superiori a 4.294.967.295. // file funzhash.h #include <string.h> #include <limits> int stringahash( char * s ) long int nmax = numeric_limits<long int>::max(); //4294967295 di solito int C = 0; for ( int i = 1; i <= strlen(s); i++ ) C = (C + s[i]) % nmax; return C; Assumendo che la lunghezza della stringa sia una costante, la complessità della funzione è anch'essa costante. Una volta ottenuto il valore k, utilizziamo il metodo del modulo per calcolare l'indirizzo nella tabella con p entrate con la formula: (k % p) + 1; Ovviamente supponiamo che p sia minore di 4.294.967.296. Per quanto riguarda la gestione delle collisioni una tecnica semplice e abbastanza utilizzata consiste nel collegare ad ogni entrata della tabella una lista che collega gli eventuali sinonimi (catena di overflow) Possiamo ora definire una tabella hash organizzata come vettore di liste di elementi e con funzione hash modulohash. Per l'effettivo utilizzo della tabella per un tipo T, è necessario definire un operatore di uguaglianza e una classe astratta ChiaveHash contenente la funzione di eguaglianza di chiave e una funzione virtuale hash che restituisca k a partire dalla chiave di T se la chiave è una stringa tale funzione può fare uso a sua volta della funzione stringahash. La funzione toint di conversione a intero deve essere sempre definita. template <class T> class ChiaveHash public: ChiaveHash ( ) virtual int hash ( const T& a )=0; virtual bool equivalenti ( const T& a1, const T& a2 )=0; ; //NB è richiesto che sia definito correttamente l'operatore == per T template <class T> class TabellaHash protected: typedef Lista<T> lisel; Vettore<lisEl> tab; int nel; ChiaveHash<T>& ch; public: TabellaHash( int p, ChiaveHash<T>& vch); ~TabellaHash(); int ncelle() const; bool ins( T& a ); bool canc( const T& a ); void svuota(); int n() const; bool cerca ( T& a ); ;

// TabellaHash.cpp: implementation of the TabellaHash<T> class. // ////////////////////////////////////////////////////////////////////// #include "TabellaHash.h" TabellaHash<T>::TabellaHash( int p, ChiaveHash<T>& vch): tab(1,p), ch(vch) nel = 0; TabellaHash<T>::~TabellaHash() for ( int i = 1; i <= ncelle(); i++ ) tab[i].svuota(); int TabellaHash<T>::nCelle() const return tab.n(); bool TabellaHash<T>::ins( T& a ) if ( cerca(a)==false ) tab[i].addincoda(a); nel++; return true; return false; bool TabellaHash<T>::canc( const T& a ) Iteratore<T> it(tab[i]); bool trovato=false; it.vaiintesta(); while (!it.isnull() &&!trovato) if (ch.equivalenti(it.getcurrentvalue(),a)) trovato=true; it.movenext(); if ( trovato ) it.cancellacorrente(); nel--; return true; return false; void TabellaHash<T>::svuota() for ( int i = 1; i <= ncelle(); i++ ) tab[i].svuota(); //funzione che cancella tutti i nodi nella lista nel = 0; int TabellaHash<T>::n() const return nel; bool TabellaHash<T>::cerca ( T& a ) Iteratore<T> it(tab[i]); bool trovato=false; it.vaiintesta(); while (!it.isnull() &&!trovato) if (ch.equivalenti(it.getcurrentvalue(),a))

a=it.getcurrentvalue(); trovato=true; it.movenext(); return trovato;

Esempio 1. Consideriamo la solita classe Dipendente in cui la chiave è codice. Vogliamo costruire una tabella hash con 100 entrate avendo come chiave il codice che è un intero. #include "dipendente.h" #include "tabhash.h" int toint (const Dipendente& d ) return d.codice; class HashDipC : public ChiaveHash<Dipendente> public: int hash( const Dipendente& d ) return d.codice; bool equivalenti(const Dipendente& d1, const Dipendente& d2) return (d1.codice==d2.codice); ; HashDipC chd; TabellaHash<Dipendente> tabdip(100, chd); Costruiamo ora una tabella hash per Dipendente con chiave nome, nell'ipotesi che non esistono due dipendenti con lo stesso nome: #include "dipendente.h" #include "tabhash.h" class HashDipN : public ChiaveHash<Dipendente> public: int hash( const Dipendente& d ) return stringahash(d.nome); bool equivalenti(const Dipendente& d1, const Dipendente& d2) return (strcmp(d1.nome,d2.nome)==0); ; void main() HashDipN hdnome; TabellaHash<Dipendente> tabdip1(100,hdnome); Dipendente d1; cin>>d1; tabdip1.ins(d1); Dipendente ricercato; cin>>ricercato.nome; if tabdip1.cerca(ricercato) cout<<ricercato; Analizziamo ora la complessità delle funzioni della tabella hash. Assumiamo con ragionevolezza che la funzione chiavehash abbia complessità costante in quanto la dimensione di T è in generale trascurabile rispetto alla dimensione della tabella. La funzione svuota ha complessità Θ(n); inoltre le funzioni cerca, ins, e canc hanno complessità nel caso migliore Θ(1) e nel caso peggiore Θ(n). E' possibile effettuare un'analisi di caso medio nell'ipotesi che la funzione hash restituisca un qualsiasi indice con la stessa probabilità 1/n. Di seguito riportiamo i numeri medi di accessi (na) in una lista necessari per determinare la chiave desiderata, uno per ogni possibile fattore di caricamento indicato come fc = n/ncelle: fc 0,2 0,5 0,7 0,9 1,0 1,1 1,5 2,0 3,0 na 1,10 1,25 1,35 1,45 1,5 1,55 1,75 2,00 2,5 Tali numeri valgono nel caso di ricerca con successo; in apparenza stranamente, per il caso di ricerca con insuccesso e fc 1, i numeri medi di accessi sono leggermente inferiori. Ciò dipende dal fatto che in questo caso sono valutati anche i casi in cui la lista dei collidenti sia vuota; ovviamente, per fc più elevati la probabilità di trovare una lista vuota si riduce praticamente a zero e, quindi, i numeri medi di accessi diventano maggiori che nel caso di ricerca con successo. Dalle tabelle si può rilevare che per fattori di caricamento contenuti (inferiore a 0,9) il numero di accessi è inferiore a 1,5, cioè possiamo affermare che le funzioni cerca, ins e canc hanno complessità media costante. Quando la tabella degrada, cioè fc diventa elevato conviene allargare la tabella per mantenere il numero di accessi praticamente uguale a 1.