ハッシュ実装の話
次の内部ハッシュの実装(C言語のプログラムの一部)には誤りがある。誤っている部分を正しく修正せよ。ただし、Bはハッシュ表のサイズ、hashはハッシュ関数である。ハッシュ表の要素は正の整数とし、要素の個数はハッシュ表のサイズBより小さいとする。
本当はむずかしいハッシュテーブル - sumiiの日記
ということで勉強中の身なので何が間違っているかをコードレビューしてみました。
一つ目はハッシュ値が重なった値が追加された後に、その前の要素がdeleteで削除されると追加された値がmemberで検出できなくなります。
二つ目はinsertやmemberにおいて、ハッシュテーブルhに全て値が格納された状態で無限ループになってしまいます。またdeleteに対しても存在しない値を渡すと無限ループです。
三つ目がちょっとわからないですね。元記事の話では三つバグがあるらしいのですが・・・。
で、とりあえず分かる範囲でこれらを不具合を修正してみました。また、なるべく元のコードを大きく変更しないように修正しました。
#include <stdio.h> #include <stdlib.h> // 空の要素を表す定数 #define EMPTY 0 // 削除済みを表す定数 #define DELETE -1 // 少なめに設定しておく #define B 4 int h[B]; // とりあえず常に重なるように0を返しておく unsigned int hash(int i) { // i使わないと警告出るので無理やりw return i-i; } /* ハッシュ表hを空にする */ void clear() { int i; for (i = 0; i < B; i ++) { h[i] = EMPTY; } } /* 要素xをハッシュ表hに追加 */ void insert(int x) { int i = hash(x); int j = i; while (h[i] != EMPTY && h[i] != DELETE) { i = (i + 1) % B; if ( i == j ) return; } h[i] = x; } /* 要素xをハッシュ表hから削除(xがhにない場合は考えなくて良い) */ void delete(int x) { int i = hash(x); int j = i; while (h[i] != x) { i = (i + 1) % B; if ( i == j ) return; } h[i] = DELETE; } /* 要素xがハッシュ表hにあれば1を、なければ0を返す */ int member(int x) { int i = hash(x); int j = i; while (h[i] != x) { if (h[i] == EMPTY) return 0; i = (i + 1) % B; if ( i == j ) return 0; } return 1; } int main (void) { insert(1); // 1を格納 insert(2); // 2を格納 insert(3); // 3を格納 delete(2); // 2を削除 delete(9); // 存在しない値を削除してみる insert(4); // 4を格納 insert(5); // 5を格納 insert(6); // 6はハッシュテーブル満杯なので格納されず printf("%d\n",member(1)); // 1 printf("%d\n",member(2)); // 0 printf("%d\n",member(3)); // 1 printf("%d\n",member(4)); // 1 printf("%d\n",member(5)); // 1 printf("%d\n",member(6)); // 0 { int i; for(i=0;i<B;i++) { printf("%d ",h[i]); } } return 0; }
無限ループ回避の策としてはとりあえず最初の要素をjに保存しておいてj==iになったら先頭に戻ってきたとみなし、ループを抜けるようにしました。
削除でおかしくなるバグに関してはEMPTYとは別にDELETEを用意し、それを格納させるようにしています。これによってmemberでは正しく値を取得することができるようになりました。またinsertはDELETEも対象に入れることで、再利用されるようにもしてあります。
たぶんこれでうまくいってるような気はします。間違ってる可能性も十二分にありますが・・・。