スライド 1: ハッシュテーブル
スライド 2
スライド 3: 探索アルゴリズム
スライド 4: 配列による探索
スライド 5: ハッシュ法
スライド 6: ハッシュ法
スライド 7: 衝突
スライド 8: 衝突への対処法
スライド 9: 連鎖法(チェイン法)
スライド 10: 開放番地法のデータ挿入手順
スライド 11: 開放番地法
スライド 12: 線形走査法
スライド 13: 均一ハッシュ法
スライド 14: チェイン法と開放番地法の比較
スライド 15: チェイン法のハッシュ関数
スライド 16: チェイン法のハッシュ関数
スライド 17: ハッシュ法の欠点
スライド 18
スライド 19
スライド 20
スライド 21
スライド 22
ハッシュテーブル
ハッシ
ュ法に
よる高
速なデー
タ探索の
原理と、
衝突への
対
処法
【学習内容の構成】
1.
探索ア
ルゴリズムの
比較
:線形探索
O(n)
、二分探索
O(log n)
、ハッシュ法
O(1)
の計算量の違い
2.
ハッシ
ュ法の
仕組み
:ハッシュ関数による
キー値から配
列添字への変換
3.
衝突への対処法:
連鎖法(リストによる連結)と開放番地
法(テーブル内の別場所への格納)
•
前提:配列、リスト、ポインタの理解
•
意義:大規模データの高速検索手法の習得
探索アルゴリズム
•
表の中に入っているデータ(表の中のデータの個数を
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 *val
ue;
/*
レコードの中身
*/
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;
/*
次のレコード参照
*/
}
}