2005年度 データ構造とアルゴリズム 第6回 「ハッシュ法を用いた探索」 2005年度 データ構造とアルゴリズム 第6回 「ハッシュ法を用いた探索」 西尾 信彦 nishio@cs.ritsumei.ac.jp 立命館大学情報理工学部 情報システム学科
O(log n)よりも高速な探索 配列のインデックスとキーを一致させればO(1)である!!! キーの範囲だけの大きさの配列が必要 非実用的 キーから配列のインデックスに変換する適切な関数を作成,これをハッシュ関数という 均等に分配するような関数 キーを100で割れば大きさは100分の1になる 同じハッシュ値となるレコードは同じハッシュバケットに入る
ハッシュ関数 キーのとりうる範囲を一定の範囲(0~M-1)する ハッシュの衝突は極力避けたい 例えば,整数のキーであれば剰余系を利用する 均等に分散させるため 例えば,整数のキーであれば剰余系を利用する 文字列によるキーの場合 頭の数文字で生成するのはダメ 同じ文字セットの順番が変っていたら違うハッシュ値が欲しい KnuthやWeinbergの例を参照 ハッシュ関数自体のコストがかかっていたらそもそもダメ
分離連鎖法 ハッシュ値の衝突を許容し 計算の手間はN/M? ハッシュバケットをリストで生成する 各ハッシュ値のリストの先頭からなる配列を用意する ハッシュ関数でバケットを選択して,リストを逐次検索 計算の手間はN/M? 適切なMの選び方 バケットを構成するリストを工夫する
上図のような入力ファイルを用いて,データ入力を行い,キーボードから検索キーを入力し,リストにあるかどうかを調べる Hash 分離連鎖法の例: 実行結果: bucket[0] Jamaica bucket[0] France bucket[0] China . bucket[12] Belguim bucket[12] Argentina 検索ワード:Japan Japan が見つかりました. 入力ファイル #include <stdio.h> #include <stdlib.h> #include <string.h> #define HASHSIZE 13 #define STR_MAX 256 typedef struct _listelem{ struct _listelem *next; char *s; }listelem, *list; list bucket[HASHSIZE]; char *word; unsigned long hashpjw(unsigned char* str){ unsigned long hash = 0, g; for( ; *str; ++str){ hash = (hash << 4) + *str; if((g = hash & 0xf0000000)){ hash ^= g >> 24; hash ^= g; } return hash; Afghanistan Albania Algeria Andorra Angola . Kenya Kiribati Korea 上図のような入力ファイルを用いて,データ入力を行い,キーボードから検索キーを入力し,リストにあるかどうかを調べる
Hash 分離連鎖法の例: char *search_add(char *s, int flag){ int h; list l; h = hashpjw(s) % HASHSIZE; /* ハッシュ値を算出 */ for(l = bucket[h]; l; l = l->next){ if(strcmp(l->s, s) == 0) return l->s; } if(flag == 0) return NULL; l = (list)malloc(sizeof(listelem)); l->s = s; l->next = bucket[h]; bucket[h] = l; return l->s; } void RegistryList(char *filename){ FILE *fpIn; char str[STR_MAX], tmp[STR_MAX]; int i, start; if((fpIn = fopen(filename, "r")) == NULL){ fprintf(stderr, "Cannot open file [%s].\n", filename); exit(1); } while(fgets(str, STR_MAX, fpIn) != NULL){ sscanf(str, "%s", tmp); word = (char *)malloc(sizeof(char)*strlen(tmp)); strcpy(word, tmp); /* リストへの登録を行うので,第二引数を0以外にしておく */ search_add(word, 1); fclose(fpIn); int main(int argc, char **argv){ int i; list p; char input[STR_MAX]; /* ファイルから読み込んだデータをリストに格納 */ RegistryList(argv[1]); for(i = 0; i < HASHSIZE ; i++){ for(p = bucket[i]; p != NULL ; p = p->next){ printf("bucket[%d] %s\n", i, p->s); } } printf("検索ワード:"); scanf("%s", input); /* 検索のみなので,第二引数のflagを0にする */ if(search_add(input, 0) != NULL){ printf("%s が見つかりました.\n", input); else{ printf("%s は見つかりませんでした.\n", input); }