ボイヤー-ムーア文字列検索アルゴリズム

ボイヤー-ムーア文字列検索アルゴリズム（Boyer-Moore String Search Algorithm）は、効率的な文字列検索アルゴリズムの一種。Robert S. Boyer と J Strother Moore が 1977年に開発した。ボイヤー-ムーア法とも呼ばれる。
このアルゴリズムでは検索文字列（パターン）の前処理を行い、検索対象テキストの前処理は行わない。したがって、テキストについて何度も検索を行わない場合に適している（他のアルゴリズムではテキスト側に前処理を施し、繰り返し検索を行うことで前処理のコストを償却する）。テキスト上の全文字をチェックする必要はなく、前処理で得た情報を活用してスキップしながら処理していく。一般にパターン文字列が長いほど検索が高速化される。検索文字列とテキストの間での不一致が発生するたびに、不一致であったという情報を最大限に利用して照合しなくてもいい位置を可能な限り排除することで、効率を向上させている。

**記号の定義**
テキスト 1=T = "ANPANMAN" に対して 1=k = 3 から 1=k = 8 までパターン 1=P = "PAN" を配置した様子。この場合、1=k = 5 の位置で一致する。
文字列 S に対する操作を以下のように表す：- math|Si： 文字列 S の i 番目の文字- math|Si..j： 文字列 S の i から j 番目までの部分文字列（i 文字目、j 文字目をそれぞれ含む）
文字列 S に含まれる文字の個数を文字列の長さと定義する。また、文字列 S の先頭を含む部分文字列をプレフィックス、末尾を含む部分文字列をサフィックスと定義する。- len(S)：S の長さ- math|S1..i, 1 ≤ i ≤ len(S)：S のプレフィックス- math|Si..len(S), 1 ≤ i ≤ len(S)：S のサフィックス
検索文字列をパターンと呼び、P で表す。被検索文字列をテキストと呼び、T で表す。また T 上での P の位置を T の先頭から数えた P の末尾の文字の位置と定義し、k で表す。- T：テキスト（被検索文字列）- P：パターン（検索文字列）- k：テキスト T 上でのパターン P の最後の文字の位置（1=len(P) ≤ k ≤ len(T)）
「P が T に一致する」とは P と文字列として等価な部分文字列 math|Ti..j が存在することをいう。より具体的に、P が位置 k で T と一致するとは、k 番目から前方 len(P) 文字分を取り出した部分文字列 math|T(k − len(P) + 1)..k が P と文字列として等価であることをいう。
本項では特に断りない限り、m を len(T), n を len(P) の意味で使う。

**アルゴリズム**
ボイヤー-ムーア法は T における P の出現を検索するもので、異なる位置で明示的に何度も文字を比較することで検索する。全部で m − n + 1 ある位置について力まかせ探索するのではなく、P を前処理して得た情報を使ってなるべく位置をスキップする。
まず、1=k = n として P が T の先頭に配置されるようにする。そして P の n 番目と T の k 番目から文字を照合しはじめ、インデックスを順次小さくして照合していく。つまり、P の最後尾から先頭に向かって照合していく。この照合は不一致が発生するか P の先頭に到達するまで行われ（その場合は一致が見つかったことになる）、その後いくつかの規則に従って位置を右にシフトできる最大値を求める。新たな位置で再び同様の照合を行い、T の最後尾に到達するまでそれを繰り返す。
シフト規則は、P の前処理で生成したテーブル群を参照することで実装されており、定数時間でシフト量が決定される。

**シフト規則**

**不一致文字規則**

**解説**
パターン NNAAMAN での不一致文字規則の適用例
不一致文字規則は照合が失敗した位置の T 内の文字に注目する。P のその位置から左側にその文字が存在する場合、その位置までスキップさせて不一致となった文字が一致するよう提案する。P の左側にその文字が存在しない場合、不一致の発生した文字の次から P が配置されるような位置を提案する。

**前処理**
不一致文字規則のためのテーブルの正確な形式によって前処理の詳細は異なるが、単純な定数時間の参照では次のようになる。まず2次元のテーブルを作る。その際、第1の添字は文字 c のアルファベット順であり、第2の添字はパターン内の文字の位置 i である。このテーブルを参照すると、P 内で c が存在する位置 j の最大値（ただし j 
パターン ANAMPNAM での一致サフィックス規則の適用例
一致サフィックス規則は概念上も実装上も不一致文字規則より格段に複雑である。ボイヤー-ムーア法が最後尾から照合を始めるのはこの規則のためである。形式的には次のように説明される。
T に対して P がある位置に置かれ、T の部分文字列 t が P のサフィックスと一致しているが、その左隣の文字で不一致になったとする。そこで、t の左端からの部分文字列 t が P のサフィックス以外の部分にないかを捜す。このとき、P のサフィックスの t の左隣の文字と P 内の t の左隣の文字が違うものでなければならない。そして、P 内の部分文字列 t が T の部分文字列 t と一致する位置に P をシフトする。t が存在しなければ、P の左端が T における t の左端を過ぎた位置になるようシフトし、T 内の t のサフィックスとパターンのプレフィックスが一致するように配置する。そのようなシフトが不可能な場合、P の長さ n のぶんだけシフトする。P 全体が一致した場合、P のサフィックスとプレフィックスに一致があればそれを考慮してシフト量を最小にする。そのような一致がない場合は、P の長さ n のぶんだけシフトする。

**前処理**
一致サフィックス規則には2つのテーブルを必要とする。1つは通常使用し、もう1つは前者が意味のある結果を返さない場合や一致が起きた場合に使う。前者のテーブルを L、後者のテーブルを H とする。これらの定義は次の通りである。
各 i について Li は、文字列 Pi..n が P のサフィックスに一致し、そのサフィックスの前の文字が Pi-1 と同じでない場合の最大の値を格納する。そのような条件を満たす位置がない場合 Li にはゼロを格納する。
Hi には P のプレフィックスでもある Pi..n の最大サフィックスの長さを格納する（もしあれば）。そのような一致が存在しない場合 Hi をゼロとする。
どちらのテーブルも構築には O(n) の時間と O(n) の領域を必要とする。提案されるシフト量は n - Li または n - Hi で、H は Li がゼロとなるか、P 全体が一致した場合にのみ使われる。

**ガリル規則**
1979年、Zvi Galil はボイヤー-ムーア法に単純だが重要な改良を施した。追加されたガリル規則はシフト量を決めるものではなく、各位置での照合を高速化するものである。位置 k1 で P と T を照合して T 上の文字 c まで照合し、次にシフトした位置 k2 によりパターンの先頭の位置が c と k1 の間になったとき、P のプレフィックスは部分文字列 T(k2 - n)..k1 と必ず一致する。したがってこの際の文字照合は T の k1 の位置まででよく、k1 より前の照合は省略できる。ガリル規則はボイヤー-ムーア法の効率を向上させるだけでなく、最悪ケースでも線型時間であることを保証するのに必須である。

**性能**
オリジナルの論文では、パターンがテキスト内に存在しない場合のボイヤー-ムーア法の最悪ケースは O(n+m) だとされている。これは1977年、ドナルド・クヌース、James H. Morris、Vaughan Pratt が初めて証明した。さらに1980年、Leonidas J. Guibas と Andrew Odlyzko が最悪ケースの文字比較回数の上限を 5m 回以下だと証明した。1991年、Coleは最悪ケースの比較回数の上限が 3m 回以下であることを証明した。
パターンがテキスト内に出現する場合、オリジナルのアルゴリズムの最悪ケースは O(nm) となる。これはパターンもテキストも同じ文字の羅列の場合に容易に発生する。ただし、ガリル規則を加えるとあらゆるケースで線型時間となる。

**実装例**

**Python**
"""
Returns the index of the given character in the English alphabet, counting from 0.
"""
def alphabet_index(c):
return ord(c.lower()) - 97 # 'a' is ASCII character 97
"""
Returns the length of the match of the substrings of S beginning at idx1 and idx2.
"""
def match_length(S, idx1, idx2):
if idx1 == idx2:
return len(S) - idx1
match_count = 0
while idx1  a: # Optimization from exercise 1-6
zi = min(b, len(S)-i)
l = i
r = i+zi-1
else: # b ends exactly at end of existing z-box
zi = b+match_length(S, a, r+1)
l = i
r = i+zi-1
else: # i does not reside within existing z-box
zi = match_length(S, 0, i)
if zi > 0:
l = i
r = i+zi-1
return z
"""
Generates R for S, which is an array indexed by the position of some character c in the 
English alphabet. At that index in R is an array of length |S|+1, specifying for each
index i in S (plus the index after S) the next location of character c encountered when
traversing S from right to left starting at i. This is used for a constant-time lookup
for the bad character rule in the Boyer-Moore string search algorithm, although it has
a much larger size than non-constant-time solutions.
"""
def bad_character_table(S):
if len(S) == 0:
return  for a in range(26)
R = -1 for a in range(26)
alpha = -1 for a in range(26)
for i, c in enumerate(S):
alphaalphabet_index(c) = i
for j, a in enumerate(alpha):
Rj.append(a)
return R
"""
Generates L for S, an array used in the implementation of the strong good suffix rule.
Li = k, the largest position in S such that Si: (the suffix of S starting at i) matches
a suffix of S:k (a substring in S ending at k). Used in Boyer-Moore, L gives an amount to
shift P relative to T such that no instances of P in T are skipped and a suffix of P
matches the substring of T matched by a suffix of P in the previous match attempt.
Specifically, if the mismatch took place at position i-1 in P, the shift magnitude is given
by the equation len(P) - Li. In the case that Li = -1, the full shift table is used.
Since only proper suffixes matter, L0 = -1.
"""
def good_suffix_table(S):
L = -1 for c in S
N = fundamental_preprocess(S::-1) # S::-1 reverses S
N.reverse()
for j in range(0, len(S)-1):
i = len(S) - Nj
if i != len(S):
Li = j
return L
"""
Generates F for S, an array used in a special case of the good suffix rule in the Boyer-Moore
string search algorithm. Fi is the length of the longest suffix of Si: that is also a
prefix of S. In the cases it is used, the shift magnitude of the pattern P relative to the
text T is len(P) - Fi for a mismatch occurring at i-1.
"""
def full_shift_table(S):
F = 0 for c in S
Z = fundamental_preprocess(S)
longest = 0
for i, zv in enumerate(reversed(Z)):
longest = max(zv, longest) if zv == i+1 else longest
F-i-1 = longest
return F
"""
Implementation of the Boyer-Moore string search algorithm. This finds all occurrences of P
in T, and incorporates numerous ways of pre-processing the pattern to determine the optimal 
amount to shift the string and skip comparisons. In practice it runs in O(m) (and even 
sublinear) time, where m is the length of T. This implementation performs a case-insensitive
search on ASCII alphabetic characters, spaces not included.
"""
def string_search(P, T):
if len(P) == 0 or len(T) == 0 or len(T) = 0 and h > previous_k and Pi == Th:   # Matches starting from end of P
i -= 1
h -= 1
if i == -1 or h == previous_k:  # Match has been found (Galil's rule)
matches.append(k - len(P) + 1)
k += len(P)-F1 if len(P) > 1 else 1
else:   # No match, shift by max of bad character and good suffix rules
char_shift = i - Ralphabet_index(Th)i
if i+1 == len(P):   # Mismatch happened on first attempt
suffix_shift = 1
elif Li+1 == -1:   # Matched suffix does not appear anywhere in P
suffix_shift = len(P) - Fi+1
else:               # Matched suffix appears in P
suffix_shift = len(P) - Li+1
shift = max(char_shift, suffix_shift)
previous_k = k if shift >= i+1 else previous_k  # Galil's rule
k += shift
return matches

**C言語**
#include 
#include 
#define ALPHABET_LEN 255
#define NOT_FOUND patlen
#define max(a, b) ((a =0; p--) {
if (is_prefix(pat, patlen, p+1)) {
last_prefix_index = p+1;
}
delta2p = last_prefix_index + (patlen-1 - p);
}
// second loop
for (p=0; p = 0 && (stringi == patj)) {
--i;
--j;
}
if (j 

**Java**
/*   * Returns the index within this string of the first occurrence of the- specified substring. If it is not a substring, return -1.- @param haystack The string to be scanned- @param needle The target string to search- @return The start index of the substring
*/
public static int indexOf(char haystack, char needle) {
if (needle.length == 0) {
return 0;
}
int charTable = makeCharTable(needle);
int offsetTable = makeOffsetTable(needle);
for (int i = needle.length - 1, j; i = 0; --i) {
if (isPrefix(needle, i + 1)) {
lastPrefixPosition = i + 1;
}
tableneedle.length - 1 - i = lastPrefixPosition - i + needle.length - 1;
}
for (int i = 0; i = 0 && needlei == needlej; --i, --j) {
len += 1;
}
return len;
}

**派生**
 Boyer-Moore-Horspool algorithmは、ボイヤームーア法を単純化したもので、不一致文字規則しか使用しない。- Apostolico-Giancarlo algorithm は、与えられた位置での照合に際して、一致することが分かっている文字の照合をスキップすることで高速化したものである。これは、パターンの前処理でえられた情報と各位置での一致したサフィックス長についての情報を利用する。ただし、これには検索対象のテキストと同じサイズの追加のテーブルを必要とする。- "On Improving the Average Case of the Boyer-Moore String Matching Algorithm"による手法は連続した2つの文字列を用いてマッチングを行う。スキップテーブルを生成する前処理の段階では遅くなるものの生成そのものは高速であり、アルファベットやパターンが小規模であるときに高速に処理できる。

**関連項目**
 クヌース-モリス-プラット法- エイホ-コラシック法- ラビン-カープ文字列検索アルゴリズム- 接尾辞木

**脚注**

**外部リンク**
  An explanation of the algorithm (with sample C code)-  Original paper on the Boyer-Moore algorithm-  An example of the Boyer-Moore algorithm このアルゴリズムの考案者の1人J Strother Mooreのホームページ-  Richard Cole's 1991 paper proving runtime linearity (PostScript)