ハッシュ法 アルゴリズム論 第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の値
© Copyright 2024 Paperzz