C++ Hashfunktion mit Verkettung

Aufbau und Erklärung einer Hashfunktion mit Verkettungsstrategie in C++



Hashverfahren
Das Hashverfahren ist ein Suchalgorithmus, mit dem man in linearer Zeit O(n) einen Sucheintrag X finden kann, und zwar egal an welcher Stelle sich dieser Eintrag X im Datenbestand befindet. Das Hashverfahren stellt dadurch den schnellst möglichen Suchalgorithmus dar den es gibt!


Technik
Eine Hashsuchfunktion wird so aufgebaut, dass der zu suchende Eintrag dem Index eines Feldes entspricht. Wenn dieser Index bekannt ist, kann die Adresse im Speicher ganz schnell hochgerechnet werden. Um den Eintrag im Feld zu speichern bzw. den Index im Feld zu ermitteln, muss dieser durch die Hashfunktion vorher berechnet werden. Die Berechnung selber funktioniert ist ganz einfach, und zwar >>k mod m<<.

k = der einzufügende Eintrag
m = Anzahl der Feldeinträge/Feldgröße
mod = Modulo (Restwert) Operation

Hashberechnung [Bild nicht erreichbar]

 [Bild nicht erreichbar]

Konflikte
Da ein Eintrag als Index dargestellt wird, welcher durch die Hashfunktion berechnet wird, kann es vorhommen, dass zwei verschiedene Datensätze den selben Index im Feld erhalten. Dies ist natürlich tunlichst zu vermeiden. eine Methode ist, das Feld so groß zu machen, dass es eine Primzahl darstellt. Durch dies Methode >>Streut<< die Hashfunktion sehr gut, jedoch noch nicht perfekt. Da somit noch immer Konklikte auftreten können, gibt es mehrere Verfahren, wie:
* Konflikte in verketteter Liste zu speichern
* Offenes Hashverfahren

In unserem Programmbeispiel verwenden wir das einfache verketten (siehe Bild)

 [Bild nicht erreichbar]

Hashfunktion
Nun kommen wir zur Programmierung der Hashfunktion. Das Programmbeispiel ist mit einem Schablonenmechanismus realisiert, sollten Sie noch nie damit gearbeitet haben, dann lassen Sie sich von der speziellen Schreibweise des Klassenrumpfs und der Methoden nicht verwirren.

// Klassendeklaration
 
template <class KeyType, class ValueType>
class DictionaryAsHashTable : public Dictionary<KeyType,ValueType>
{
public:
    DictionaryAsHashTable();
    DictionaryAsHashTable(int n = 100); // Größe des Feldes
    ~DictionaryAsHashTable ();
    bool search(const KeyType& k);
    bool insert(const KeyType& k);
    bool remove(const KeyType& k);
    int getNumber() { return size; }
    ValueType& get();
    void print();
 
 
private:
    int h( const string& k );
    int h( const int k );
    struct Entry // Struktur eines Eintrages
    {
        KeyType key;
        ValueType *value;
    };
 
    struct HashEntry // Verkettete Liste aller Einträge
    {
        Entry *entry;
        HashEntry *next;
    };
 
    HashEntry** a;
    ValueType *selected;	// Zeiger auf den Indexwert
    int size;		// Arraygroesse
};
 
// Konstruktor
 
template <class KeyType, class ValueType>
DictionaryAsHashTable<KeyType, ValueType>::DictionaryAsHashTable(int n)
{
    size = n;
    a = new HashEntry*[n];
 
    for ( int i=0; i<n; i++ )
        a[i]=0;
 
    selected = NULL;	//Setzt den Indexwert auf das gefundene/zuletzt eingefuegte Element
}
 
// Destruktor
 
template <class KeyType, class ValueType>
DictionaryAsHashTable<KeyType, ValueType>::~DictionaryAsHashTable( )
{
    for ( int i=0; i<size; i++ )
        if ( a[i] != 0)
        {
            HashEntry *tmp = a[i]; // tmp auf aktuelle Arrayposition setzen
            while ( tmp->next != 0 ) // Durchlaufe alle Knoten bis zum Ende
            {
                HashEntry *hashEntry = NULL;
 
                hashEntry = tmp->next; // HashEntry mit Entry verknüpfen
                tmp = tmp->next; // Array mit HashEntry verknüpfen
                delete hashEntry;
            }
 
            delete tmp;
        }
}
 
/**
* @desc Berechnet den Hash-Schlüssel anhand eines Strings und der Hashtabellengroesse this->size
* @param string k : String für welchen ein Hash-Schlüssel berechnet werden soll.
*/
template <class KeyType, class ValueType>
int DictionaryAsHashTable<KeyType, ValueType>::h( const string& k )
{
    int adr = 0;
    for ( unsigned int i=0; i<k.size(); i++ )
        adr = (adr*128 + k[i]) % size;
 
    return adr;
}
 
/**
* @desc Berechnet den Hash-Schlüssel anhand eines Integers und der Hashtabellengroesse this->size
* @param string k : String für welchen ein Hash-Schlüssel berechnet werden soll.
*/
template <class KeyType, class ValueType>
int DictionaryAsHashTable<KeyType, ValueType>::h( const int k )
{
    return k%size;
}
 
/**
* @desc Sucht Schlüssel k und speichert Wert in selected ab
*	Gibt true zurück, falls Schlüssel k gefunden wird, und sonst false.
* @param KeyType k : Deutscher Name
*/
template <class KeyType, class ValueType>
bool DictionaryAsHashTable<KeyType, ValueType>::search(const KeyType& k)
{
    int adr = h(k); // Adresse berechnen und speichern
 
    if ( adr != 0 )
    {
        HashEntry *tmp = a[adr]; // Adresse des Datensatzes ermitteln
        while ( tmp != 0 ) // Alle Einträge in der verketteten Liste durchlaufen
            if ( tmp->entry->key == k ) // Wenn Eintrag gefunden, dann abspeichern
            {
                selected = tmp->entry->value; // Adresse abspeichern
                return true;
            }
            else
                tmp = tmp->next; // zum nächsten Knoten
    }
 
 
    selected = NULL;
    return false;
}
 
/**
* @desc Fügt neuen Schlüssel k mit Wert v ein. Falls Schlüssel bereits vorhanden ist,
*	wird nicht eingefügt und false zurückgeliefert, sonst true.
* @param KeyType k : Deutscher Name
*/
template <class KeyType, class ValueType>
bool DictionaryAsHashTable<KeyType, ValueType>::insert(const KeyType& k)
{
    if ( !search(k) ) // Wenn Key nicht gefunden wurde
    {
        // Speicher für Entry und HashEntry allokieren und initialisieren
        HashEntry *tmp = a[h(k)];
        HashEntry *hashEntry = new HashEntry;
        Entry *entry = new Entry;
        entry->value = new ValueType;
        hashEntry->next = 0;
 
        if ( tmp == 0 ) // Wenn noch keine verkettete Liste existiert, erstelle diese
        {
            entry->key = k; // Entry mit Daten füllen
            hashEntry->entry = entry; // HashEntry mit Entry verknüpfen
            a[h(k)] = hashEntry; // Array mit HashEntry verknüpfen
 
            selected = entry->value;
            return true; // Beende nach Erstellung
        }
        else // Wenn verkettete Liste existiert, füge Eintrag dort hinzu
        {
            while ( tmp->next != 0 ) // Suche Ende der verketteten Liste
                tmp = tmp->next;
 
            // ValueType in verketteter Liste hinzufügen
            entry->key = k;
            hashEntry->entry = entry;
            tmp->next = hashEntry;
 
            selected = entry->value;
            return true;
        }
    }
 
 
    selected = NULL;
    return false;
}
 
/**
* @desc Löscht Schlüssel k. Falls Schlüssel gelöscht werden konnte, wird true
*	zurückgeliefert und sonst false (d.h. Schlüssel war nicht vorhanden).tmpHashEntry
* @param KeyType k : Deutscher Name
*/
template <class KeyType, class ValueType>
bool DictionaryAsHashTable<KeyType, ValueType>::remove(const KeyType& k)
{
    if ( search(k) ) // Wenn Key gefunden wurde
    {
        HashEntry *tmp = a[h(k)]; // tmp auf aktuelle Arrayposition setzen
        HashEntry *hashEntry = NULL; // Knoten der später mit ELementposition verknüpft und gelöscht wird
 
        if ( tmp->entry->key == k ) // Wenn erster Knoten der zu löschende Eintrag ist
        {
            hashEntry = tmp; // HashEntry mit Entry verknüpfen
            a[h(k)] = hashEntry->next; // Array mit HashEntry verknüpfen
 
            delete hashEntry;
            return true; // Beende nach Erstellung
        }
        else // Wenn der zu löschende Eintrag ist in einem unterknoten ist
        {
            while ( tmp->next->entry->key != k ) // Suche solange bis zu löschender Knoten gefunden wurde
                tmp = tmp->next;
 
            hashEntry = tmp->next; // HashEntry mit Entry verknüpfen
            tmp->next = hashEntry->next; // Array mit HashEntry verknüpfen
 
            delete hashEntry;
            return true; // Beende nach Erstellung
        }
    }
 
 
    selected = NULL;
    return false;
}
 
/**
* @desc liefert eine Referenz auf den ValueType des zugehoerigen
*	zuletzt gesuchten / eingeuegten Keytypes.
* @return Gibt eine Referenz mit Wert 0 zurück, wenn keine Daten vorhanden sind
*/
template <class KeyType, class ValueType>
ValueType& DictionaryAsHashTable<KeyType, ValueType>::get()
{
    //if(selected != NULL)
    return *selected;
}
 
/**
* @desc Listet alle ELemente auf.
*/
template <class KeyType, class ValueType>
void DictionaryAsHashTable<KeyType, ValueType>::print()
{
    // Alle Eintraege ausgeben
    cout << "\n\nKey\t\t\tValue" << endl;
    cout << "-------\t\t\t--------" << endl;
    for (int i = 0; i < size-1; i++)
        if ( a[i] != 0 ) // Wenn Element an Position verfügbar
            for (HashEntry *tmp=a[i]; tmp!=0; tmp=tmp->next)
                cout << tmp->entry->key << "\t\t\t" << *tmp->entry->value << endl;
 
}