panda's tech note

ハッシュテーブル

ハッシュテーブルはExact matching(完全一致)アルゴリズムとして使われています。 例えば,ネットワークアルゴリズムとしては,フォワーディングテーブル(MACアドレステーブル)などのソフトウェア実装として,ハッシュテーブルが使われます。

ハッシュ関数

ハッシュ関数は,あるデータ空間(ビット長)のビット列を別のデータ空間のビット列にマップする関数です。 例えば,MACアドレスは48ビットのアドレス空間ですが,EthernetスイッチのMACアドレステーブルは疎なので、全空間で探索・保存をせずに,一般的には多くても16Kエントリー(14ビット程度の空間)が保存されます。 つまり,ハッシュ関数を使って48ビットの空間を14ビットの空間にマップします。

いくつかのハッシュ関数はパスワードの暗号化と検証のように,一方向の暗号化関数として使われます。 このようなハッシュ関数はCryptographic hash functionと呼ばれますが,ハッシュテーブルに使うハッシュ関数はかならずしもCryptographic hash functionでなく、より軽量なハッシュ関数で良いです。通常、ハッシュテーブルには,ハッシュ値の衝突が起こった場合にLinked listやLinear probingといった衝突した値を扱う機構を備えます。

Jenkins hash functionやCRC32がハッシュテーブルのハッシュ関数にはよく利用されます。 以下のコードはJenkins hash functionのC言語でのコードです。

#define KEY_LENGTH 6 // for 48-bit keys
#define HASH_BIT_LENGTH 14 // Output address space of the hash function
uint32_t
jenkins_hash(uint32_t *key)
{
    size_t i;
    uint32_t hash;
    hash = 0;
    for ( i = 0; i < KEY_LENGTH; i++ )
        hash += key[i];
        hash += hash << 10;
        hash ^= hash >> 6;
    }
    hash += hash << 3;
    hash ^= hash >> 11;
    hash += hash << 15;
    return hash & ((1 << HASH_BIT_LENGTH) - 1);
}

ハッシュ値の衝突の調停

ハッシュ関数は異なるビット列を入力とした場合にも,出力が同じ値になることがあります。 この衝突したハッシュ値をハッシュテーブル中に正しく保存,検索するために,Linked listまたはLinear probingが使われます。

Linked list

Linked listはハッシュ値の衝突に対する単純な解法です。 以下の6個のキーを8バケット(3ビット空間)のハッシュテーブルに保存する場合を考えます。 1, 10, 100のハッシュ値が5となり,衝突が起こっています。

Key Hash value
1 5
5 3
10 5
11 1
100 5
101 0

以下の図のように,衝突の起こったハッシュ値については,対応するバケットをLinked listでつなげることで,同じハッシュ値に対して複数のキー(とデータ)を保存できるようにします。検索のときは,このLinked listに対して線形探索を行います。

linked list

この解法の問題点は,Linked listの最大長がエントリの数となってしまい,nをエントリ数とした場合,最悪計算量がO(n)となってしまうことです。

Linear probing

Linear probingはハッシュ値の衝突に対する別の解法です。 Linear probingではLinked listとは違い,それぞれのバケットは最大で一つのエントリのみ入ります。 代わりに,ハッシュ値の衝突が起こった場合には,バケットをシーケンシャルに走査して空のバケットを探します。 例えば,上記の6エントリの例では,キー10を挿入する場合に,ハッシュ値が5のキー1が既にハッシュ値に対応するバケットに入っているため,その次のバケット(ハッシュ値6のバケット)に挿入します。 このような手順により,上記の例を上から順に挿入すると以下の図のハッシュテーブルとなります。

linear probing

検索アルゴリズムは,ハッシュ値に対応するバケットから順に対応するキーを持つバケットを走査します。 例えば,上図でキー100に対応するバケットを検索するには,100のハッシュ値である5のバケットから順に走査して,対応する7のバケットで検索を終了します。 このように,単純なLinear probingの計算量はバケットの数のオーダーとなります。 しかし,Hopscotch hashingなど,検索時の計算量を削減するアルゴリズムもあります。

Linear probingの問題は,最大のエントリ数がハッシュテーブルのサイズで制限されるため,ハッシュテーブルのバケットが埋まった際にハッシュテーブルを拡張するには,ハッシュテーブル自体を再構成する必要があることです。