データ構造とアルゴリズム
第十回 (2009年12月
11日)
文字列照合のアルゴリズム
http://www.sw.it.aoyama.ac.jp/2009/DA/lecture10.html
Martin J. Dürst
duerst@it.aoyama.ac.jp
© 2008-09 Martin
J. Dürst 青山学院大学
目次
- 前回のまとめ
- 問題の概要
- 素朴な実装
- Rabin-Karp のアルゴリズム
- Knuth-Morris-Pratt のアルゴリズム
- Boyer-Moore のアルゴリズム
前回のまとめ
- ハッシュ法のではハッシュ関数を使って
- 結果として、辞書の検索、挿入、削除が全て
O(n) で可能
- 激突の場合に、チェイン法や開番地法などを使用
- Ruby
などのプログラム言語でハッシュは非常に便利なデータ構造
- Ruby 内のハッシュの実相はチェイン法を使用
文字列照合の概要
長い文書の中に短いパターンを見つける
- 文字 n 個の長さの「文書」(text t)
- 文字 m 個の長さの「パターン」(pattern
p)
- 文書の中にパターンを見つける (有無・場所・数)
- 場所は普通 shift という
- shift s の t の部分文字列を
ts と書く
- 文字列 t の s 個目の文字を
t[s] と書く
文字列照合の状況
- n と m の関係 (一般には m ≪
n)
- 検索の回数・パターンの数
- 文字の数
- ビット列: 2
- 遺伝子: 4 又は ~20
- 欧米などの文書: 26~256
- 東アジア: 数千
素朴な実装
- text の全ての長さ m
の部分文字列をパターンと比較
- 部分列の数が n-m+1
- 部分列とパターンの比較は O(m)
- 超単純な実装は必ず O(nm)
- 比較は速い段階で打ち切られる可能性が高い
- 一般の場合、計算量は O(n)
に近い
- 最悪の場合、計算量は O(nm)
実例: text = aaa....aaaab, pattern = aa..ab
Rabin-Karp のアルゴリズムの概要
- ハッシュ関数を使用
- text の全ての長さ m
の部分列のハッシュ値を計算
- pattern のハッシュ値と比較
- ハッシュ値が一着の場合、実体で確認
- 単純な実装は O(nm)
ハッシュ関数の工夫
- shift s+1 のハッシュ値を shift s
のハッシュ値から簡単に計算できればよい
- パターンのハッシュ値は hf (p) =
(p[0]·bm-1+p[1]·bm-2+...+p[m-2]·b1+p[m-1]·b0)
mod d
(b はアルファベットの基数、d
は適切に選んだ数字)
- 文書内の候補のハッシュ値は
hf (ts) =
(t[s]·bm-1+t[s+1]·bm-2+...+t[s+m-2]·b1+t[s+m-1]·b0)
mod d
hf (ts+1)
=
(t[s+1]·bm-1+t[s+2]·bm-2+...
+t[s+m-1]·b1+t[s+m]·b0)
mod d
- mod 演算の性質の利用で
hf (ts+1) = ((hf
(ts) - t[s]·bm-1) ·
b + t[s+m]) mod d
= hf (ts+1) = ((hf
(ts) - t[s]·bm-1 mod
d) · b mod d +
t[s+m]) mod d
Rabin-Karp のアルゴリズムの実例
パターン: 081205
文書: 28498608120598743297
Excell による Rabin-Karp のアルゴリズムの例: ARabin-Karp.xls
Knuth-Morris-Pratt のアルゴリズムの概要
- 素朴な実装だと同じ文字は何回も比較対象
- 基本的なアイディア:
今までの比較の知識の活用
- パターン内の比較によって、パターンの移動の距離を事前に算出
Knuth-Morris-Pratt のアルゴリズムの計算量
- 比較の結果、次の二つの内一つが起きる:
- パターンの (一文字以上) 右への移動
(最大 n-m 回)
- 比較の対象の一文字右への移動
(最大 n 回)
- 合計の回数はおよそ 2n で、計算量は
O(n)
- 準備以外、計算量は m に依存しない
- 利点: 文字列を完全に左から右へと検索
Knuth-Morris-Pratt のアルゴリズムの実例
Boyer-Moore のアルゴリズムの概要
- 比較はパターンの末尾から
- 比較される文字列の値を考慮
- パターンの移動の間隔を拡大
アイディアの詳細
パターンの移動に二つの「目安」を使用:
- パターンの内部比較
(Knuth-Morris-Pratt の「逆方向版」)
- 不一着の文書の文字のパターン内の最も右の位置
どちらかシフトが大きい方を使用
Boyer-Moore のアルゴリズムの実例
Boyer-Moore のアルゴリズムの計算量
- 最悪の場合、Knuth-Morris-Pratt と同等で
O(n)
- m
がアルファベットの大きさに比べて比較的小さい場合、
多くの場合 O(n/m)
文字列照合と文字コード
- 文字コードによって文字の数が多い
- アルゴリズムの実装は文字よりバイトのレベルが簡単
- 文字コードによってバイトレベルの実装が不可能
- 可能: UTF-8
- 不可能:
iso-2022-jp
(JIS), Shift_JIS
(SJIS), EUC-JP
(EUC)
文字コードごとのバイトパターン
- UTF-8: 0xxxxxxx, 110xxxxx 10xxxxxx, 1110xxxx 10xxxxxx 10xxxxxx など
- EUC-JP: 0xxxxxxx, 1xxxxxxx 1xxxxxxx
- Shift_JIS: 0xxxxxxx, 1xxxxxxx xxxxxxxx
- iso-2022-jp: 0xxxxxxx, 0xxxxxxx 0xxxxxxx
来年度への展望
- プログラムなどの字句解析は高度な文字列照合
- 有限オートマトンが使える
- 正規表現で指定可能
- これは3年前期の「言語理論とコンパイラ」のテーマ
- Ruby などのプログラム言語でも正規表現が使える
まとめ
- 文字列照合の単純な実装は最悪で
O(nm)
- Rabin-Karp のアルゴリズムは O(n)
で、ハッシュ関数の使用で2次元にも使用可能
- Knuth-Morris-Pratt のアルゴリズムは O(n)
で、入力の順にしか文書を見ない
- Boyer-Moore のアルゴリズムは多くの場合
O(n/m)