ハッシュテーブル
ハッシュテーブルは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の最大長がエントリの数となってしまい,n
をエントリ数とした場合,最悪計算量がO(n)
となってしまうことです。
Linear probing
Linear probingはハッシュ値の衝突に対する別の解法です。
Linear probingではLinked listとは違い,それぞれのバケットは最大で一つのエントリのみ入ります。
代わりに,ハッシュ値の衝突が起こった場合には,バケットをシーケンシャルに走査して空のバケットを探します。
例えば,上記の6エントリの例では,キー10
を挿入する場合に,ハッシュ値が5
のキー1
が既にハッシュ値に対応するバケットに入っているため,その次のバケット(ハッシュ値6
のバケット)に挿入します。
このような手順により,上記の例を上から順に挿入すると以下の図のハッシュテーブルとなります。
検索アルゴリズムは,ハッシュ値に対応するバケットから順に対応するキーを持つバケットを走査します。
例えば,上図でキー100
に対応するバケットを検索するには,100
のハッシュ値である5
のバケットから順に走査して,対応する7
のバケットで検索を終了します。
このように,単純なLinear probingの計算量はバケットの数のオーダーとなります。
しかし,Hopscotch hashingなど,検索時の計算量を削減するアルゴリズムもあります。
Linear probingの問題は,最大のエントリ数がハッシュテーブルのサイズで制限されるため,ハッシュテーブルのバケットが埋まった際にハッシュテーブルを拡張するには,ハッシュテーブル自体を再構成する必要があることです。