ハッシュテーブル
探索
アルゴリズム
•
表の中に入っているデー
タ(表の中のデータ
の個数を
n
個と
する)の中から,目的のデータを探索するアルゴリズム
–
線形探索
•
表中のデータを1つ1つ順に端から調べる
だけの単純な探索法
•
計算量は
O(n)
–
二分探索,木
•
キー値の比較に基づき,途中で探索候
補を絞っていくことによっ
て
,
表全部を調べることなく高速化を図
る
•
計算量は
O(log n)
–
ハッシュ法
•
平均して
O(1)
の計算量で探
索,挿入,削除の
全てを行うことがで
きる
配列による探索
•
「キー値の範囲をおおうような配列」を使う方法
(例)レコード{学籍番号,名前,生年月日}
–
キー値:
学籍番号(
1
から
100
までの値をとりうる
)
–
1
~
100
までの整数を添字とする配列を用意
(例)
配列
student[1]
~
student[100]
に,それぞれレコード
データを入れる
–
配列を使って,
キー値からレコードを取り出す
方法:
与えられたキー値(この場合,学籍番号)を添字として,そ
の配列を調べる
•
単純に考えて,「デ
ータが何個存在す
るかに関係なく,
配列を1つ調べればいい」と考えれば,
探索
1
回当り
の計算量は
O(1)
ハッシュ法
•
配列
による探索
–
配列の添字
として
使用できるのは正の整数
のみ
–
キーの
値が限られた
範囲
の
整数
である
場合
にしか使
えない
(一般的
ではない)
•
ハッシュ法
–
キーの
値を直接配列の添字とするのではなく,
キー
値をある
関数
(ハッシュ
関数
)によって
整数
に
変換
して
,
この
関数値を
添字
として
配
列を参照
する
–
キー
値
として
,
文字列
など
可能(
名前
による
検索
など
)
(例)
名前によって探索
する
場合
1.関数
Hash
に
引数
として
,検索
したい
名前
(
文字列
)
を
与
える
.
2.
関数
Hash
は,与えられた
文字列
から
なんらかの
計算
を行い,
整数
値を返す.
3.
その
整数値を配列の
添字
とする
配列
を参照
する
.
ハッシュ法
•
ハッシュ関数:
–
キー値を整数に変換する関数
–
常に,同じハッシュ関数を使うので,同じキー
の値を与えた時
,
結
果はいつも同じになる.
→
データを挿入する時も探索する時も配列の同じ場所を参照する
•
ハッシュ値
–
ハッシュ関数の計算によって得られる整数値
•
ハッシュテーブル
–
データが格納された配列
•
ハッシュ関数の計算,配列の参照にか
かる時間は,デー
タ数
n
に無関係で,
O(1)
の計算量
衝突
•
キーのとり得る値の種類に比べて,配列の添字
として使え
る値の範囲は小さい
–
配列の添字:
正の整数で,
4
バイトの整数
(
0
から
2147483648
)
–
キーのとり得る値の種類
の例:
4バイトの浮動小数なら:
-3.4e38
から
3.4e38
•
全てのキー値を,
1
対1で整数に変換することは
不可能
•
違うキーの値でも,ハッシュ値(ハッシュ関数を適
用した結果)が同じになるというこ
とが起る.
→
このような事態を衝突と呼ぶ
衝突への対処法
•
連鎖法
(
チェイ
ン法
)
–
ハッシュ関数の値
(
ハッシュ値)が同じで
あるレコード同士を
リストでつないでおく
–
ハッシュテーブルには,リストを指すポイン
ターを入れる
•
開放番地法
–
ハッシュテーブルそのものにレコード
を入れる
連鎖法
(
チェイン法
)
•
ハッシュ関数の値
(
ハッシュ
値)が同じであるレコード同士をリ
ストでつないでおく
•
下の図のようにハッシュテーブルの
各要素
には
,レコ
ードを直
接入
れるのではなく,レコードをつないだリ
ストの
先頭
を指す
ポインターを入
れる
•
探索
の際は,まずハッシュ関数を計算
して
,ハッシュテーブル
から
特定
のリストを選
んで
,
その
後リスト上を
探索
するとい
う
手順
になる
0
1
2
m
・
・
・
・
・
Hash
T
able
開放番地
法のデー
タ挿入手
順
•
まず
,ハッシュテーブルからハッシュ値が指す要素を調
べる
.
•
もしそこにデータが入っていなかったら,そこにレコー
ドを入れる.別
のレコードがすでにそこに入っていれば,ハッシュテーブルの別
の場
所を調べ,そこが
未使用
であれ
ばそこへデータを入れ,使
用済
であ
ればまた別の場所を探すという方法をとる.
•
この
方法
では
,
ハッシュ表の各要素が使
用中であるか否かを表す
印
が必要となる
0
used record
1 free
2 free
4 free
3 used record
m used record
m-1
free
mark
Hash
T
able
開放番地法
•
開放番地法
では
,要素を調べたときに使用済であった場合
に,次にどこの
場所
を調べるかということを前
もって
方針を
決めておく
–
線形走査法
•
使用済であった場合に,次の要素,
その
次
の要素
...
と
添字
を1つずつ増やしながら調べて
いく方法
–
均一ハッシュ法
•
違うハッシュ値を持つデータ同士がひとかたまりにな
らないようにする方法
線形走査法
•
使用済であった場合に,次の要素,その次の要素
...と添字
を1つずつ増やしながら調べていく方法
•
クラスターによる性能低下が起こる
–
下図では,「1から
5」に固まり(
クラスター)がで
きている
–
ハッシュ値が1のレコードを挿入しようとする
とき,1から5までは塞
がっ
ているので,6の場所に入れ
る.
–
その付近のハッシュ値を持つデータは次々に固まりに加わってい
く.
–
このように.同じ値のハッシュ値だけでなく
,違う値のハッシュ値の影
響
を受けて固まりができて
いく現
象をクラスター
という
0
1 record
2
record
3
record
4
record
5
record
6
7
m
1 record
均一ハッシュ法
•
関数を
m
個(関数
h1,h2,....hm
とし
,これらはある同じ値を
入力
しても全て違う
値を返
すとする
)
用意
して
,最初に関
数
h1
により,ハッシュテーブルを調べ
,
塞がっていれば次
に関数
h2
を適用
して
,
その
場所を調べる.さらに塞がって
いる場合,
h3,h4,...
と空いているまで関数によってその場
所をみつけていく.
•
これにより,データがバラバラに配置されてクラスターを
避ける
•
この
方法
では
,
ハッシュ
関数
の計算の
手間
が増
えて
効率
が悪くなる
チェイン法と開放番地法の比較
•
開放番地法
–
衝突が起ったときに,ハッシュテーブル内の別の場所に格納
して
いく
→
扱えるデータの総数はハッシュテーブルの大
きさに
制限
され
る(欠点)
–
データの削除
では
,
単純
にその場所の印を未使用にしてしまうと,
探索の時にその場所で止まってしまう.
→
そこで,新
たな
印
として
,削除済ということを表す印を加え
,
(使用中,未使用,削除済)の
3
種類
の印を用いると,
削除済
とい
うのは
探索に関しては,使用中と同じことになる.削除が
多くなる
と,ハッシュテーブルに無駄な部分が増
えて
,効率
が悪くなる(欠
点)
•
チェイン法
–
大
きな
欠点は無い
–
線形リストを使うため若干メモリ使用量が
増
える
チェイン法のハッシュ関数
•
ハッシュ関数は,キー値を,一様に分散させるようなものが望
ましい
ハッシュテーブルの大
きさが
m
である場
合:
–
チェイン法の効率をあげるには,データを
m
本のリストにで
きるだけ均
等に振り分けて,個々のリス
トをできるだけ短く
することが必要
•
キーの値に対して,できるだけランダムな値を返すハッシュ関
数が望ましい
•
ハッシュ関数の作り方は,キーの種類,とりうる値の範囲に影
響される
•
任意のデータに対し
て常に一様なば
らつきを持ったハッシュ
関数を作ることは不可能
チェイン法のハッシュ関数
•
キー
値が正の
整数
の場合:
–
キーの
値を
m
で割
った
余りをハッシュ関数とする
–
キー
値は
0
~
m-1
の範囲の整数に分けられる
–
同じハッシュ値となるのは,
m
の倍数
だけ
離
れた値を持つキー
•
ハッシュ関数をできるだけランダムなものに
するためには,
キーの
全てのビットの値が
結果
に
影響
することが望
まし
い
–
m
の大
きさは
,
素数
をとるのが良いとされる
–
m = 2
の
K
乗とすると,最下位
k
ビットのみで
ハッシュ値が決
まる
.
データ
自体
の傾向やハッシュ
関数
の癖が
強調
され
てばらつきが
悪くなってしまうようと言われ
る
•
キーが
文字列の時:
–
何らかの方法で文字列を整数値に変換
して
,それを
m
で割
った
余りをとるようにすることができる
ハッシュ法の欠点
•
ハッシュ関数をうまく作
って
,ハッシュテーブルのサイズ
m
を
十分大
きくとれば,
探索
,
その
他の
操作
は
高速
になる
•
対象とするデータの数があらかじめ分かってないと,表の大
きさ
m
を決めるのは難
しい
–
あまり
m
を大きくとると記憶域の無駄が大き
くなってしまう
•
表の中のデータの順序がでたらめになるので,表の中の値
を大きさの順に取り出す場合は,新
たに
整列を行う必要が
ある
.
–
このような
場合
,
整列
を
必要
としない
2
分探索木な
どと比
べて
劣
って
いる
サンプルプログラム
#include<stdio.h>
#include<stdlib.
h>
struct RECOAD{
/*
レコードを構造体で定義
*/
char *value;
/*
レコードの
中身
*/
struct RECOAD *next;
/*
次のレコードを指すポインタ
*/
}
main(){
struct RECOAD *table[10
];
int i;
char in[20
];
for (i=0
;i<
10;i++)table[i]=NULL;
/*
テーブルを初期化
*/
for (i=0
;i<
100;i++){
printf("Input
W
ord (end: %%%%%%) :");
/*
語を入力
*/
scanf("%s",in);
if (strcmp(in,"%%%")==0)break;
else Chain(in,table
);
/*
チェイン法で挿入
*/
}
while (1){
printf("Search
W
ord (end: %%%%%%) :");
/*
探す語を入力
*/
scanf("%s",in);
if (strcmp(in,"%%%")==0)break;
else Search(in,table
);
/*
探索
*/
}
}
Chain(char *in,int *table){
int k,h;
char *in2;
struct RECOAD *rec;
rec=malloc(size
of(rec));
/*
レコードを作製
*/
in2=malloc(20);
strcpy
(in2,in);
/*
文字列を格納する領域を作成
*/
rec->value=in2;
h=0;
for (k=0;k<20;k++){
/*
ハッシュ値を計算
(h=0,1,2,...,9)*/
if (in[k]==NULL)break;
h=h+in[k];
}
h=h % 10;
rec->next=
table[h];
/*
レコード挿入
*/
table[h]=rec;
}
Search(char *in,int *table){
struct RECOAD *rec;
int k,h;
h=0;
for (k=0;k<20;k++){
/*
ハッシュ値を計算
(h=0,1,2,...,9)*/
if (in[k]==NULL)break;
h=h+in[k];
}
h=h % 10;
rec=table[h
];
while (1){
if (rec==NULL){
printf ("Not Found %s\n",in);
break;
}
if (strcmp(rec->value
,in)==0
){
printf ("Found %s\n",in);
break;
}
rec=rec->next;
/*
次のレコード
参照
*/
}
}