チェインハッシュ(Chain Hash)

概要

ハッシュテーブルと連結リストを組み合わせた構造。

ハッシュ

キーをハッシュ関数からハッシュ値を取ってグループ化する。
ハッシュ関数はキーの分布によって衝突が発生しにくくするとよい。


例:キー:int型 ハッシュ関数:剰余

unsigned int hash(int key){ return (key<0?-key:key)%16; }

例:キー:string型 ハッシュ関数:剰余

unsigned int hash(std::string key){ return key.length()%3; }


ハッシュ関数によって取り出される値をハッシュ値と呼ぶ。
ハッシュ値の値の範囲はハッシュテーブルに合わせる。
一般的に範囲を制限できる剰余が用いられることが多いです。

const int MAX_N = 128;

int gHashTable[MAX_N];

unsigned int hash(int key){ return (key<0?-key:key)%MAX_N; }


データの割り当て領域(取得や挿入する場所)はgHashTable[hash(key)]となる。
ハッシュ関数によって一意に決定されるので検索がO(1)でできる。(ハッシュ関数分の時間はかかるけど)

const int MAX_N = 10;
int gHashTable[MAX_N];

int hash(int key){ return key%10; }

gHashTable[hash(5)] = 128;   // 挿入
int a = gHashTable[hash(5)]; // 検索


しかし、問題はシノムニ(衝突)である。
例えば、x%10をハッシュ関数とするとhash(0)==hash(10)==0となってしまう。
このような衝突が発生しないようにする必要がある。


連結リストの可能性

シノムニが発生すると挿入や検索ができなくなってしまいます。
そこで、連結リストを使って対処することをチェインハッシュと呼びます。


ハッシュテーブルがそのまま値でしたが、ラッピングしてリスト構造にします。


キー:int データ:int

struct Node
{
	int key;
	int value;
	
	Node* next;
};

const int MAX_N = 64;

Node gHashTable[MAX_N]; // 先頭はダミーです


ハッシュ値が同一のキーはリスト構造にして、どんどん伸ばしていきます。
検索には、O(1) + O(m) = O(m)かかります。*mはハッシュ値あたりのシノムニ数
ハッシュ検索してから線形リスト検索になります。


ここのO(m)からも分かるように、シノムニが発生しにくいハッシュ関数を作ることが大きなテーマになります。


また、リスト構造ではなくて2分木で構成するともっと効率がよくなります。
(ヒープにしてみるといいね)

サンプルコード


キー:string型
データ:int型


宣言とハッシュテーブル・ハッシュ関数

#include <iostream>
#include <string>

#define SAFE_DELETE(p) { delete p; p=0; }

using namespace std;

//---------------------------------------------------------
// chain hash
//---------------------------------------------------------
struct Node { string key; int data; Node* next; };

const int MAX_N = 32;

Node gHashTable[MAX_N];

//---------------------------------------------------------
// function
//---------------------------------------------------------
unsigned int hash(const string key)
{
    string::const_iterator it  = key.begin();
    string::const_iterator end = key.end();

    unsigned int h = 0;

    while( it != end ){ h += (*it); ++it; }

    return h % MAX_N;
}


初期化とクリア処理

void init()
{
    for(int i=0;i<MAX_N;i++) gHashTable[i].next = 0;
}

void clear()
{
    Node* p;
    Node* q;

    for(int i=0;i<MAX_N;i++)
    {
        p = gHashTable[i].next;

        while( p )
        {
            q = p->next;
            SAFE_DELETE(p);
            p = q;
        }

        gHashTable[i].next = 0;
    }
}


更新挿入

int update(string key,int data)
{
    unsigned int h = hash(key);

    //-----------------------------------------------------
    // search and update
    //-----------------------------------------------------
    for(Node* p=gHashTable[h].next;p;p=p->next)
    {
        if( p->key == key ){ p->data = data; return 1; }
    }

    //-----------------------------------------------------
    // not found and insert
    //-----------------------------------------------------
    Node* ptr = new Node();

    ptr->key  = key;
    ptr->data = data;

    ptr->next = gHashTable[h].next;

    gHashTable[h].next = ptr;

    return 0;
}


検索

bool find(const string key,int& data)
{
    unsigned int h = hash(key);

    for(Node* p=gHashTable[h].next;p;p=p->next)
    {
        if( p->key == key )
        {
            data = p->data;
            return true;
        }
    }

    return false;
}


削除

bool remove(const string key)
{
    unsigned int h = hash(key);

    for(Node* p=&gHashTable[h];p->next;p=p->next)
    {
        if( p->next->key == key )
        {
            Node* q = p->next->next;
            SAFE_DELETE(p->next);
            p->next = q;

            return true;
        }
    }

    return false;
}


使い方

int main()
{
    init();

    update("hello",128);
    update("olleh",256);

    int data;

    while(true)
    {
        cout << ">>";

        string input1; cin >> input1;

        if( input1 == "quit"   ) break;

        else if( input1 == "update" )
        {
            cout << "key:";  string key; cin >> key;
            cout << "data:"; int data;   cin >> data;
            update(key,data);

            cout << "updated" << endl;
        }

        else if( input1 == "find" )
        {
            cout << "key:"; string key; cin >> key;

            if( find(key,data) ) cout << "find " << data << endl; else cout << "not found" << endl;
        }

        else if( input1 == "remove" )
        {
            cout << "key:"; string key; cin >> key;

            if( remove(key) ) cout << "removed" << endl; else cout << "not found" << endl;
        }
        else
        {
            cout << "command is quit, update, find and remove" << endl;
        }


    }

    clear();

    return 0;
}