• 検索結果がありません。

組合せ爆発の対処

N/A
N/A
Protected

Academic year: 2021

シェア "組合せ爆発の対処"

Copied!
42
0
0

読み込み中.... (全文を見る)

全文

(1)
(2)

探索とは?

キー 一致するものを探す ・・・・ ・・・・ ・・・・ ・・・・ ・・・・ : : :: :: レコード フィールド

(3)

線形探索アルゴリズム(1)

n=10, i =0, target=54 a[i] : target i++ START END = ≠ i : n return i return -1 < ≧ 前提:配列aにn個のデータが保存 処理:target と同じデータが蓄えら れている配列要素の添え字を返し, ない場合は-1を返すフローチャー トを記せ

(4)

key data table[0] table[1] tabel[2] table[3] : : table[n-1] table[n] : : table[199] 75 福崎慎也 101 渡邊滋之 17 大野綾子 28 川島祐毅 : : : : 64 仲野弘幸 番人 (sentinel) 87 番人による少し早い線形探索

(5)

target = 54, i =0, a[n] = target a[i] : target i=i+1 START END = ≠ i : N return i return -1 < ≧

番人を利用した線形探索アルゴリズム

※ループ毎に iとn-1の比較が不要

(6)

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つの線形探索アルゴリズムの比較

(7)

二分探索法(バイナリサーチ)

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 探索範囲

(8)

二分探索アルゴリズム

(半分ずつ捨てるのがポイント)

サイズ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 ②

(9)

線形探索の計算量(比較の回数)は 最良1、最悪n、平均n/2 データ数nに対して O(n) 二分探索の計算量(比較の回数)は 2k-1≦n<2のとき k回 つまり、データ数nに対して 約O(logn)

(10)

線形探索の計算量は O(n) 二分探索の計算量は O(log2 n) n=1,000だったら? n=1,000 log2n=約10 (なぜなら210=1,024) 100倍違う! 定数係数が少しくらい違ったって、 勝負は明らか!

(11)

ビッグ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

(12)

O(1) O(logN) O(N) O(NlogN) O(N2) O(2n)

ビッグオーの

グラフ化

(13)

データの登録も考えると

登録(n要素当り) 探索(1回当り) 線形探索 O(n) O(n) 二分探索 O(n2) O(log n) クイックソートで O(n log n)

(14)

登録1回あたりの探索回数をSとすると、 線形探索 O(n)+S・O(n) 二分探索 O(n log n)+S・O(log n) n<<Sでないと、二分探索は有利に ならない! 頻繁にデータ集合が変わるような応用には 二分探索は適さない

(15)
(16)

ハッシュ法

ハッシング

hashing

)ともいう

hash: 切りきざむ 挿入・探索・削除がO(1)でできる つまり、データの個数nに依存しない 理想の探索技法!?

(17)

学生番号から氏名などを求めたい

2003年度に入学した学生だけを考えると、 70310001~70310101 でも、一般にキーはこのように順序よく 並んでいない direct access という 配列の0番目から100番目に氏名を格納 → (学生番号下3桁-1)番目の 配列要素を見ればよい

(18)

英和辞書

• 5万語の英和辞書の全体をメモリにのせて使 いたい • 各単語のインデクス番号が分かれば,O(1)で ある単語の意味を知ることができる インデクス番号 内容 1 2 3 …. hash:切り刻む 50,000 どうすれば 各単語の インデクス番号が 分かるか?

(19)

語を数に変換する

• ASCII(アスキー)コード – 大文字,小文字,数字,記号などを0から255まで の数で表現 – a:97, b:98, …, z:122 • 大文字,数字,記号などを使わないとしたら – スペースを0として,a:1, b:2, c:3, …, z:26の27文 字で表現できる

(20)

語を数に変換する 方法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), ….

(21)

語を数に変換する 方法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兆以上!!

(22)

語を数に変換する

方法2:桁位置を利用する(べき乗化)

fira firb firc fird

fire

firf firg

125146 125147 125148 125149 125151 125152

単語ではない

実在する単語

(23)

ハッシュ法

• 巨大な範囲の数を実用的なサイズの配列の 添え字(インデクス)に変換 • 簡単な方法としては,モジュロ演算子(%)を 使う – %nは0からn-1までの数を作りだす (値域:0~3) 23 % 4 = 3 13052 % 4 = 0 38 % 4 = 2 配列のインデクス = 巨大な数 % 配列サイズ

(24)

ハッシュ関数(hash function)

キーの値xの集合 添字(ハッシュ値) h(x)の集合 0,1,2, ・・・,99 × × × × × × ・・・ 26 100 h(x) 大きな値域の数を小さな値域の数へと ハッシュ(切り刻む)する。文字列を 一定範囲の整数に変換すること。

(25)

ハッシュ関数の例 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

(26)

ハッシュ表(テーブル)

ハッシュ値の例 文字列 ハッシュ値 one 22 two 46 three four five six seven eight nine ten 0 1 ….. 26 five 27 ten 28 29 eight ….. ハッシュ関数を使って データを挿入した配列

(27)

ハッシュ(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

(28)

異なるキーが同じハッシュ値に 写像されたら、どうするか? チェイン法 オープンアドレス法

衝突

の処理

大きく分けて

(29)

チェイン法

ハッシュ表の同じ場所に写像された データを連結リストにつなぐ ハッシュ表は連結リストの先頭を指す ポインタの配列

(30)

ハッシュ表

(31)
(32)

オープンアドレス法

ある一定の方法で,空セルを探して, そこに新たな項目を挿入する方法 ①線形探査(linear probing) ②平方探査(quadratic probing) ③ダブルハッシュ(double hashing)

(33)

h(x)=h(x) (x) (x) (x) ハッシュ表 : : : : オープンアドレス 法は、ハッシュ表の 中で仮想的な連結 リストを作るようなもの ただし、次の要素は ポインタでなく、 再ハッシュ関数に よって決まる

(34)

オープンアドレス法:線形探査

• 配列を単純にシーケンシャルに辿って

空きセルを探すやり方

0 1 ….. 26 five 27 ten 28 29 eight …..

nine = 110+105+110+101

= 426

ハッシュ値= 426%100

=26

衝突 衝突

nine

OK

(35)

再ハッシュ(rehash) k回目にアクセスする場所: h(x) xはキー、k=0,1,2,・・・,B-1 最も簡単な再ハッシュ関数は (x)=(h(x)+k) % B h(x):最初のハッシュ関数 B:ハッシュ表(配列)の大きさ

オープンアドレス法:線形探査 (2)

(36)

オープンアドレス法:線形探査の問題点

0 ….. 25 26 five 27 ten 28 nine 29 eight 30 この状態でさらにハッシュ値が 26のキーを挿入する場合 データが連続してしまい, 効率が落ちる クラスター化

(37)

オープンアドレス法:平方探査

線形探査のように,隣接するセルに挿入してい くとクラスターができやすいので,もっと離れた 場所に挿入しようというやり方 h(x)=(h(x)+k2 % B h(x):最初のハッシュ関数 B:ハッシュ表(配列)の大きさ 注意点:配列のサイズを素数にしなければ 同じ場所を探し続けることがある

(38)

オープンアドレス法:平方探査の問題点

サイズ59の配列(すべてセルが空いているとする)に, 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種クラスター化

(39)

オープンアドレス法:ダブルハッシュ

• キーの値によって探査の歩幅が異なるように する方法 • キーに対して2度目のハッシュを行い,得られ た結果をステップ幅として使う hs(x)=(C – (k % C)) % B B:ハッシュ表(配列)の大きさ C: 定数(配列サイズより小さい素数)

(40)

オープンアドレス法:ダブルハッシュの注意点 • 最初のハッシュ関数と同じであってはならない • 0が作られることのある関数であってはならない • ハッシュ表のサイズは素数でなければならない – ハッシュ表のサイズが59で,ステップ幅は? 184 % 59 = 7 → 配列の要素8へ 302 % 59 = 7 →(11-(302%11))%59 = 6, 要素14へ 420 % 59 = 7 → (11-(420%11))%59= 9, 要素17へ 538 % 59 = 7 → (11-(538%11))%59=10,要素18へ hs(x)=(11 – (k % 11)) % 59とすると

(41)

良いハッシュ関数とは

• 手早い計算

– ハッシュ法の利点はスピードなので,ハッシュ関数は高速 であるべき

• ランダムキー

– Index = key % arraySizeで得られるインデクスもランダム (均等)に分布

• ノンランダムキー

– テーブルサイズには素数を使う

– 多くのキーと配列サイズに共通の公約数がある場合,そ れらが同じ位置へハッシュされるため

(42)

ハッシュ(2)

問題1: (2) (1)の表に示した文字列を上から順番に、要素数11のハッ シュ表に格納せよ。 (3)衝突が発生した場合には、チェイン法とオープンアドレス 法でそれぞれどのように衝突が回避されるかを図で示せ。 (4) オープンアドレス法は線形探査とダブルハッシュの両方を 示すこと。線形探査とダブルハッシュのハッシュ関数は以 下のとおり。 hs(x)=(7 – (k % 7)) % 11 kはハッシュ関数hash()内の11で割った余りを求める直前の変数iの値 h(x)=(h(x)+k) % 11 k回目にアクセスする場所(K=0, 1, 2, …, 10) 線形探査のハッシュ関数 ダブルハッシュのハッシュ関数

参照

関連したドキュメント

けいさん たす ひく かける わる せいすう しょうすう ぶんすう ながさ めんせき たいせき

それでは資料 2 ご覧いただきまして、1 の要旨でございます。前回皆様にお集まりいただ きました、昨年 11

ピンクシャツの男性も、 「一人暮らしがしたい」 「海 外旅行に行きたい」という話が出てきたときに、

大阪府では、これまで大切にしてきた、子ども一人ひとりが違いを認め合いそれぞれの力

断するだけではなく︑遺言者の真意を探求すべきものであ

越欠損金額を合併法人の所得の金額の計算上︑損金の額に算入

きも活発になってきております。そういう意味では、このカーボン・プライシングとい

QRされた .ino ファイルを Arduino に‚き1む ことで、 GUI |}した ƒ+どおりに Arduino を/‡((スタンドアローン})させるこ とができます。. 1)