ハッシュ法

ハッシュ法
アルゴリズム論 第5回講義
2011年10月28日(金)
ハッシュ法
ハッシング(hashing)ともいう
hash: 切りきざむ
挿入・探索・削除がO(1)でできる
つまり、データの個数nに依存しない
理想の探索技法!?
学生番号から氏名などを求めたい
2003年度に入学した学生だけを考えると、
70310001~70310101
配列の0番目から100番目に氏名を格納
→ (学生番号下3桁-1)番目の
配列要素を見ればよい
direct access という
でも、一般にキーはこのように順序よく
並んでいない
英和辞書
• 5万語の英和辞書の全体をメモリにのせて使
いたい
• 各単語のインデクス番号が分かれば,O(1)で
ある単語の意味を知ることができる
インデクス番号
内容
1
2
3
….
hash:切り刻む
50,000
どうすれば
各単語の
インデクス番号が
分かるか?
語を数に変換する
• ASCII(アスキー)コード
– 大文字,小文字,数字,記号などを0から255まで
の数で表現
– a:97, b:98, …, z:122
• 大文字,数字,記号などを使わないとしたら
– スペースを0として,a:1, b:2, c:3, …, z:26の27文
字で表現できる
語を数に変換する
方法1:単語の各文字に対応する数の総和を
インデクス番号とする
• cats = 3 + 1 + 20 + 19 = 43
• Dic[43] = cat:ネコ,猫科の動物・・・・
ここで,単語の最大文字数を10とすると,辞書の一番最後の文字は,
(理論的には) zzzzzzzzzz(zが10個) = 26 X 10 = 260
50,000(単語あるとすれば) ÷ 260 = 192
→ サイズ260の配列を準備すれば、
1つの配列要素に192語が該当する
例えば、単語の各文字に対応する数の総和がcatと同じ43になる単語
was(23+1+19), give(7+9+22+5), tend(20+5+14+4), ….
語を数に変換する
方法2:桁位置を利用する(べき乗化)
• 数値の場合は0から9の10種類(10進数)
– 各桁は10のべき乗
• 配列1要素あたり1バイトとすると,
今回の前提では,スペース,aからzの27種類(27
進数)
•
約190TBのメモリが必要!!
– 各桁は27のべき乗
1TB = 1024 * 1024 * 1024 * 1024 =
3+1x272+20x271+19x270
cats
=
3x27
1,099,511,627,776 (約1兆バイト)
= 60,337
• zzzzzzzzzz = 26x279 +26x278 +…+26x270
= 205,891,132,094,648
200兆以上!!
語を数に変換する
方法2:桁位置を利用する(べき乗化)
単語ではない
fira firb firc fird fire firf firg
125146
125147
125148
125149
125150
125151
実在する単語
125152
ハッシュ法
• 巨大な範囲の数を実用的なサイズの配列の
添え字(インデクス)に変換
• 簡単な方法としては,モジュロ演算子(%)を
使う
– %nは0からn-1までの数を作りだす
(値域:0~3)
23 % 4 = 3
13052 % 4 = 0
38 % 4 = 2
配列のインデクス = 巨大な数 % 配列サイズ
ハッシュ関数(hash function)
キーの値xの集合
×
h(x)
×
×
×
添字(ハッシュ値)
h(x)の集合
×
・・・
×
265
0,1,2,
・・・,99
100
大きな値域の数を小さな値域の数へと
ハッシュ(切り刻む)する。文字列を
一定範囲の整数に変換すること。
ハッシュ関数の例
int hash(char *s)
{ int i = 0;
while (*s)
i += *s++;
return i % 100}
a:97…………… z:122
アスキーコードの総和を
100で割った余りを配列
添字とする
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
この関数で求まるハッシュ値
の例
文字列
one
two
three
four
five
six
seven
eight
nine
ten
ハッシュ値
22
46
ハッシュ表(テーブル)
ハッシュ関数を使って
データを挿入した配列
ハッシュ値の例
文字列
ハッシュ値
one
22
two
46
three
four
five
six
seven
eight
nine
ten
0
1
…..
26 five
27 ten
28
29 eight
…..
ハッシュ(1)
問題1:
以下のハッシュ関数を用いて、表の各文字列に対応する
ハッシュ値を求めよ。
ハッシュ関数
int hash(char *s)
{ int i = 0;
while (*s)
i += *s++;
return i % 11}
アルファベットに対応する数値
a:1, b:2, c:3, d:4, e:5, f:6, g:7, h:8, i:9, j:10, k:11,
l:12, m:13, n:14,o:15, p:16, q:17, r:18, s:19,
t:20, u:21, v:22, w:23, x:24, y:25, z:26
例:yamaguti =
(25+1+13+1+21+20+9) % 11 = 2
文字列
ハッシュ値
fukuzaki
watanabe
oono
kawashima
nakano
miura
異なるキーが同じハッシュ値に
写像されたら、どうするか?
衝突の処理
大きく分けて
チェイン法
オープンアドレス法
チェイン法
ハッシュ表の同じ場所に写像された
データを連結リストにつなぐ
ハッシュ表は連結リストの先頭を指す
ポインタの配列
ハッシュ表
0
A
B
1
2
C
3
4
D
E
5
6
7
G
8
H
9
J
I
F
チェイン法のデモ
オープンアドレス法
ある一定の方法で,空セルを探して,
そこに新たな項目を挿入する方法
①線形探査(linear probing)
②平方探査(quadratic probing)
③ダブルハッシュ(double hashing)
ハッシュ表
::
h(x)=h0(x)
h1(x)
h2(x)
h3(x)
::
オープンアドレス
法は、ハッシュ表の
中で仮想的な連結
リストを作るようなもの
ただし、次の要素は
ポインタでなく、
再ハッシュ関数に
よって決まる
オープンアドレス法:線形探査
• 配列を単純にシーケンシャルに辿って
空きセルを探すやり方
0
1
nine = 110+105+110+101
= 426
衝突
ハッシュ値= 426%100 衝突
=26
OK
…..
26 five
27 ten
28
nine
29 eight
…..
オープンアドレス法:線形探査 (2)
再ハッシュ(rehash)
k回目にアクセスする場所: hk(x)
xはキー、k=0,1,2,・・・,B-1
最も簡単な再ハッシュ関数は
hk(x)=(h(x)+k) % B
h(x):最初のハッシュ関数
B:ハッシュ表(配列)の大きさ
オープンアドレス法:線形探査の問題点
この状態でさらにハッシュ値が
26のキーを挿入する場合
データが連続してしまい,
効率が落ちる
クラスター化
0
…..
25
26 five
27 ten
28 nine
29 eight
30
オープンアドレス法:平方探査
線形探査のように,隣接するセルに挿入してい
くとクラスターができやすいので,もっと離れた
場所に挿入しようというやり方
hk(x)=(h(x)+k2) % B
h(x):最初のハッシュ関数
B:ハッシュ表(配列)の大きさ
注意点:配列のサイズを素数にしなければ
同じ場所を探し続けることがある
オープンアドレス法:平方探査の問題点
サイズ59の配列(すべてセルが空いているとする)に,
184,302,420,538というキーを
順番に挿入することを考えると
184 % 59 = 7 → 1ステップでa(7)
302 % 59 = 7 → 2ステップでa(8)
420 % 59 = 7 → 3ステップでa(11)
538 % 59 = 7 → 4ステップでa(16)
第2種クラスター化
オープンアドレス法:ダブルハッシュ
• キーの値によって探査の歩幅が異なるように
する方法
• キーに対して2度目のハッシュを行い,得られ
た結果をステップ幅として使う
hs(x)=(C – (k % C)) % B
B:ハッシュ表(配列)の大きさ
C: 定数(配列サイズより小さい素数)
オープンアドレス法:ダブルハッシュの注意点
• 最初のハッシュ関数と同じであってはならない
• 0が作られることのある関数であってはならない
• ハッシュ表のサイズは素数でなければならない
– ハッシュの表のサイズが15で,ステップ幅が5の場合は?
hs(x)=(11 – (k % 11)) % 59とすると
184 % 59 = 7 → 1ステップで配列の要素8へ
302 % 59 = 7 → 11-(302%11) = 6, 要素13へ
420 % 59 = 7 → 11-(420%11)= 9, 要素16へ
538 % 59 = 7 → 11-(538%11)=10, 要素17へ
良いハッシュ関数とは
• 手早い計算
– ハッシュ法の利点はスピードなので,ハッシュ関数は高速
であるべき
• ランダムキー
– Index = key % arraySizeで得られるインデクスもランダム
(均等)に分布
• ノンランダムキー
– テーブルサイズには素数を使う
– 多くのキーと配列サイズに共通の公約数がある場合,そ
れらが同じ位置へハッシュされるため
ハッシュ(2)
問題1:
(2) (1)の表に示した文字列を上から順番に、要素数11のハッ
シュ表に格納せよ。
(3)衝突が発生した場合には、チェイン法とオープンアドレス
法でそれぞれどのように衝突が回避されるかを図で示せ。
(4) オープンアドレス法は線形探査とダブルハッシュの両方を
示すこと。線形探査とダブルハッシュのハッシュ関数は以
下のとおり。
線形探査のハッシュ関数
hk(x)=(h(x)+k) % 11
k回目にアクセスする場所(K=0, 1, 2, …, 10)
ダブルハッシュのハッシュ関数
hs(x)=(7 – (k % 7)) % 11
kはハッシュ関数hash()内の11で割った余りを求める直前の変数iの値