探索アルゴリズム...2017/10/27 · ハッシュ関数(hash function)...
TRANSCRIPT
探索アルゴリズム
探索とは?
キー
一致するものを探す
・・・・
・・・・
・・・・
・・・・
・・・・
:: :: ::
レコード
フィールド
線形探索アルゴリズム(1) n=10, i =0,
target=54
a[i] : target
i++
START
END
=
≠
i : n
return i return -1
<
≧
前提:配列aにn個のデータが保存
処理:target と同じデータが蓄えられている配列要素の添え字を返し,ない場合は-1を返すフローチャートを記せ
key data
table[0]
table[1]
tabel[2]
table[3]
:
:
table[n-1]
table[n]
:
:
table[199]
75 福崎慎也
101 渡邊滋之
17 大野綾子
28 川島祐毅
: :
:
:
64 仲野弘幸
番人
(sentinel)
87
番人による少し早い線形探索
target = 54, i =0, a[n] = target
a[i] : target
i=i+1
START
END
=
≠
i : N
return i return -1
<
≧
番人を利用した線形探索アルゴリズム
※ループ毎に
iとn-1の比較が不要
n=10, target = 54,
i =0
a[i] : target
i=i+1
START
END
=
≠
前処理
i : N
return i return -1
<
≧
n=10, target = 54,
i =0, a[n] = target
a[i] : target
i=i+1
START
END
=
≠
前処理
i : N
return i return -1
<
≧
番人なし 番人あり
2つの線形探索アルゴリズムの比較
二分探索法(バイナリサーチ)
a[0]=1
a[1]=3
a[2]=4
a[3]=8
a[4]=13
a[5]=14
a[6]=18
a[7]=20
a[8]=21
a[9]=25
探すキーの値は 14 とする。
low→
middle→
high→
<14
a[0]=1
a[1]=3
a[2]=4
a[3]=8
a[4]=13
a[5]=14
a[6]=18
a[7]=20
a[8]=21
a[9]=25
low→
middle→
high→
>14
=middle+1
a[0]=1
a[1]=3
a[2]=4
a[3]=8
a[4]=13
a[5]=14
a[6]=18
a[7]=20
a[8]=21
a[9]=25
high→
low, middle→ =14
=middle-1
探索範囲
二分探索アルゴリズム
(半分ずつ捨てるのがポイント)
サイズnの配列key(0~n-1)
においてsを探す
① first=0, last=n-1
② mid = (first+last)/2
③ key(mid)=s -> Found
key(mid)<s -> first=mid+1 Goto ②
key(mid)>s -> last=mid-1 Goto ②
上記アルゴリズムの問題点は? 完成させよ
線形探索の計算量(比較の回数)は
最良1、最悪n、平均n/2
データ数nに対して O(n)
二分探索の計算量(比較の回数)は
2k-1≦n<2kのとき k回
つまり、データ数nに対して 約O(log2n)
線形探索の計算量は O(n)
二分探索の計算量は O(log2 n)
n=1,000だったら?
n=1,000
log2n=約10 (なぜなら210=1,024)
100倍違う!
定数係数が少しくらい違ったって、
勝負は明らか!
ビッグO のグラフ化
N 5 10 15 20 25
O(2n) 32 1024 32768 1048576 33554432
O(N2) 25 100 225 400 625
O(NlogN) 3.49 10 17.64 26.02 34.94
O(N) 5 10 15 20 25
O(logN) 0.69 1 1.17 1.30 1.39
O(1) 1 1 1 1 1
O(1)
O(logN)
O(N)
O(NlogN) O(N2) O(2n)
ビッグオーの
グラフ化
データの登録も考えると
登録(n要素当り) 探索(1回当り)
線形探索 O(n) O(n)
二分探索 O(n2) O(log n)
クイックソートで
O(n log n)
登録1回あたりの探索回数をSとすると、
線形探索 O(n)+S・O(n)
二分探索 O(n log n)+S・O(log n)
n<<Sでないと、二分探索は有利に
ならない!
頻繁にデータ集合が変わるような応用には
二分探索は適さない
ハッシュ法
ハッシュ法
ハッシング(hashing)ともいう
hash: 切りきざむ
挿入・探索・削除がO(1)でできる
つまり、データの個数nに依存しない
理想の探索技法!?
学生番号から氏名などを求めたい
2003年度に入学した学生だけを考えると、
70310001~70310101
でも、一般にキーはこのように順序よく
並んでいない
direct access という
配列の0番目から100番目に氏名を格納
→ (学生番号下3桁-1)番目の
配列要素を見ればよい
英和辞書
• 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のべき乗
• 今回の前提では,スペース,aからzの27種類(27進数) – 各桁は27のべき乗
• cats = 3x273+1x272+20x271+19x270
= 60,337
• zzzzzzzzzz = 26x279 +26x278 +…+26x270
= 205,891,132,094,648
配列1要素あたり1バイトとすると, 約190TBのメモリが必要!! 1TB = 1024 * 1024 * 1024 * 1024 = 1,099,511,627,776 (約1兆バイト)
200兆以上!!
語を数に変換する 方法2:桁位置を利用する(べき乗化)
fira firb firc fird fire firf firg 125146 125147 125148 125149 125151 125152
単語ではない
実在する単語
125150
ハッシュ法
• 巨大な範囲の数を実用的なサイズの配列の添え字(インデクス)に変換
• 簡単な方法としては,モジュロ演算子(%)を使う
– %nは0からn-1までの数を作りだす
(値域:0~3) 23 % 4 = 3
13052 % 4 = 0 38 % 4 = 2
配列のインデクス = 巨大な数 % 配列サイズ
ハッシュ関数(hash function)
キーの値xの集合 添字(ハッシュ値)
h(x)の集合
0,1,2,
・・・,99
× ×
× × ×
×
・・・
265 100
h(x)
大きな値域の数を小さな値域の数へと ハッシュ(切り刻む)する。文字列を 一定範囲の整数に変換すること。
ハッシュ関数の例
int hash(char *s)
{ int i = 0;
while (*s)
i += *s++;
return i % 100}
a:97…………… z:122
アスキーコードの総和を
100で割った余りを配列
添字とする
この関数で求まるハッシュ値
の例
文字列 ハッシュ値
one 22
two 46
three
four
five
six
seven
eight
nine
ten
a 97 b 98 c 99 d 100 e 101 f 102 g 103 h 104 i 105 j 106 k 107 l 108 m 109 n 110 o 111 p 112 q 113 r 114 s 115 t 116 u 117 v 118 w 119 x 120 y 121 z 122
ハッシュ表(テーブル)
ハッシュ値の例
文字列 ハッシュ値
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
ハッシュ関数
アルファベットに対応する数値
文字列 ハッシュ値
fukuzaki
watanabe
oono
kawashima
nakano
miura 例:yamaguti = (25+1+13+1+21+20+9) % 11 = 2
異なるキーが同じハッシュ値に 写像されたら、どうするか?
チェイン法
オープンアドレス法
衝突の処理
大きく分けて
チェイン法
ハッシュ表の同じ場所に写像された
データを連結リストにつなぐ
ハッシュ表は連結リストの先頭を指す
ポインタの配列
A B
C
D E F
G
H I
J
ハッシュ表
0
1
2
3
4
5
6
7
8
9
オープンアドレス法
ある一定の方法で,空セルを探して,
そこに新たな項目を挿入する方法
①線形探査(linear probing)
②平方探査(quadratic probing)
③ダブルハッシュ(double hashing)
h(x)=h0(x)
h1(x)
h2(x)
h3(x)
ハッシュ表
::
::
オープンアドレス
法は、ハッシュ表の
中で仮想的な連結
リストを作るようなもの
ただし、次の要素は
ポインタでなく、
再ハッシュ関数に
よって決まる
オープンアドレス法:線形探査
• 配列を単純にシーケンシャルに辿って空きセルを探すやり方 0
1
…..
26 five
27 ten
28
29 eight
…..
nine = 110+105+110+101 = 426 ハッシュ値= 426%100 =26
衝突
衝突 nine
OK
再ハッシュ(rehash)
k回目にアクセスする場所: hk(x)
xはキー、k=0,1,2,・・・,B-1
最も簡単な再ハッシュ関数は
オープンアドレス法:線形探査 (2)
𝒉𝒌 𝒙 = 𝒉 𝒙 + 𝒌 % 𝑩
𝐵:ハッシュ表 配列 の大きさ
ℎ 𝑥 :最初のハッシュ関数
オープンアドレス法:線形探査の問題点
0
…..
25
26 five
27 ten
28 nine
29 eight
30
この状態でさらにハッシュ値が 26のキーを挿入する場合 データが連続してしまい, 効率が落ちる
クラスター化
オープンアドレス法:平方探査
線形探査のように,隣接するセルに挿入してい
くとクラスターができやすいので,もっと離れた
場所に挿入しようというやり方
注意点:配列のサイズを素数にしなければ 同じ場所を探し続けることがある
𝒉𝒌 𝒙 = 𝒉 𝒙 + 𝒌𝟐 % 𝑩
𝐵:ハッシュ表 配列 の大きさ
ℎ 𝑥 :最初のハッシュ関数
オープンアドレス法:平方探査の問題点
サイズ59の配列(要素7に値が入っているとする)に, 184,302,420,538というキーを 順番に挿入することを考えると
184 % 59 = 7 → 1ステップで配列の要素8へ 302 % 59 = 7 → 2ステップで配列の要素11へ 420 % 59 = 7 → 3ステップで配列の要素16へ 538 % 59 = 7 → 4ステップで配列の要素23へ
第2種クラスター化
オープンアドレス法:ダブルハッシュ
• キーの値によって探査の歩幅が異なるようにする方法
• キーに対して2度目のハッシュを行い,得られた結果をステップ幅として使う
𝒉𝒌 𝒙 = 𝒉 𝒙 + 𝒌 ∗ 𝒉𝒔 𝒙 % 𝑩
𝒉𝒔 𝒙 = 𝑪 − (𝒙 % 𝑪) % 𝑩
𝐵:ハッシュ表 配列 の大きさ
ℎ 𝑥 :最初のハッシュ関数
𝐶:定数(配列サイズより小さい素数)
オープンアドレス法:ダブルハッシュの注意点
• 最初のハッシュ関数と同じであってはならない
• 0が作られることのある関数であってはならない
• ハッシュ表のサイズは素数でなければならない
• サイズ59の配列(要素7に値が入っているとす
る)に,184,302,420,538というキーを順番に挿入することを考えると?
オープンアドレス法:ダブルハッシュの注意点
hk x = h x + k ∗ hs x % 59
hs x = 11 − (x % 11) % 59 (𝐵 = 59, 𝐶 = 11)とすると
h 184 = 184 % 59 = 7
h 302 = 302 % 59 = 7 hs 302 = 11 − (302 % 11) % 59) = 6 h1 302 = 7 + 1 ∗ 6 % 59 = 13
→ 配列の要素13へ
h 420 = 420 % 59 = 7 hs 420 = 11 − (420 % 11) % 59) = 9 h1 420 = 7 + 1 ∗ 9 % 59 = 16
→ 配列の要素16へ
h 538 = 538 % 59 = 7 hs 538 = 11 − (538 % 11) % 59) = 10 h1 538 = 7 + 1 ∗ 10 % 59 = 17
→ 配列の要素17へ
hs 184 = 11 − (184 % 11) % 59) = 3 h1 184 = 7 + 1 ∗ 3 % 59 = 10
→ 配列の要素10へ
良いハッシュ関数とは
• 手早い計算
– ハッシュ法の利点はスピードなので,ハッシュ関数は高速であるべき
• ランダムキー
– Index = key % arraySizeで得られるインデクスもランダム(均等)に分布
• ノンランダムキー
– テーブルサイズには素数を使う
– 多くのキーと配列サイズに共通の公約数がある場合,それらが同じ位置へハッシュされるため
ハッシュ(2) 問題1:
(2) (1)の表に示した文字列を上から順番に、要素数11のハッシュ表に格納せよ。
(3)衝突が発生した場合には、チェイン法とオープンアドレス法でそれぞれどのように衝突が回避されるかを図で示せ。
(4) オープンアドレス法は線形探査とダブルハッシュの両方を
示すこと。線形探査とダブルハッシュのハッシュ関数は以下のとおり。
k回目にアクセスする場所(k=0, 1, 2, …, 10)
線形探査のハッシュ関数
ダブルハッシュのハッシュ関数
ℎ𝑘 𝑥 = ℎ 𝑥 + 𝑘 % 𝐵
ℎ𝑘 𝑥 = ℎ 𝑥 + 𝑘 ∗ ℎ𝑠 𝑥 % 𝐵
ℎ𝑠 𝑥 = 𝐶 − (𝑥 % 𝐶) % 𝐵
𝐵 = 11, 𝐶 = 7 とする