1. 程式人生 > >算法——字符串匹配之BM算法

算法——字符串匹配之BM算法

當前位置 current main 每次 子串 org img -1 eight

前言

Boyer-Moore算法是一種基於後綴匹配的模式串匹配算法(簡稱BM算法),後綴匹配就是模式串從右到左開始比較,但模式串的移動依舊是從左到右的。在實踐中。BM算法效率高於前面介紹的《KMP算法》,算法分為兩個階段:預處理階段和搜索階段;預處理階段時間和空間復雜度都是是O(m+sigma)sigma是字符集大小。一般為256在最壞的情況下算法時間復雜度是O(m*n);在最好的情況下達到O(n/m)

BM算法實現

BM算法預處理過程

BM算法有兩個規則分別為壞字符規則(Bad Character Heuristic)和好後綴規則

(Good Suffix Heuristic)。這兩種規則目的就是讓模式串每次向右移動盡可能大的距離。BM算法是每次向右移動模式串的距離是,依照好後綴算法和壞字符算法計算得到的最大值。下面給出基本概念:

壞字符:輸入文本字符串中的字符與模式串當前字符不匹配時,則文本字符串的該字符稱為壞字符;

好後綴:是指在遇到壞字符之前,文本串和模式串已匹配成功的字符子串;

以下是壞字符和好後綴的圖示:

技術分享

壞字符規則:當輸入文本字符串中的某個字符跟模式串的某個字符不匹配時。模式串須要向右移動以便進行下一次匹配,移動的位數 = 壞字符在模式串中相應的位置 - 壞字符在模式串中最右出現的位置。此外,假設模式串中不存在"壞字符"。則最右出現位置為-1;所以壞字符規則必定有兩種情況。以下會進行討論。


好後綴規則:當字符失配時,後移位數 = 好後綴在模式串中相應的位置 - 好後綴在模式串上一次出現的位置,且假設好後綴在模式串中沒有再次出現,則為-1。

依據模式串是否存在好後綴或部分好後綴,能夠分為三種情況,以下會逐一討論。

壞字符規則

壞字符規則有兩種情況,例如以下圖所看到的:

技術分享

好後綴規則

若文本字符串和模式串匹配了一個好後綴u, 以下依據模式串其它位置是否存在好後綴進行不同的移動。假如,模式串pat的後u個字符和文本串txt都已經匹配了。可是下一個字符是壞字符,則須要移動模式串又一次匹配。若在模式中依舊存在同樣的後綴或部分後綴, 那把最長的後綴或部分後綴移動到當前後綴位置。若模式串pat不存在其它的好後綴,則直接右移整個pat。因此好後綴規則有三種情況,例如以下圖所看到的:

技術分享

好後綴規則和壞字規則的大小通過模式串的預處理數組的簡單計算得到。

壞字符算法的預處理數組是bmBc[]。好後綴算法的預處理數組是bmGs[]

計算壞字符數組bmBc[]

Case1:若模式串存在壞字符,若模式串存在多個壞字符時,選取最右邊的那個字符。bmBc[‘b‘]表示字符b在模式串中最右出現的位置。

比如以下模式串中出現壞字符b的位置分別為j,k,i;則選取最右位置i作為bmBc[‘b‘]的值。

技術分享 Case2:字符在模式串中沒有出現。如模式串中沒有字符b,則bmBc[‘b‘] = -1

壞字符數組bmBc[]源代碼實現例如以下:

void PreBmBc(const string &pat, int m, int bmBc[])
{
    int i = 0;
	// Initialize all occurrences as -1, include case2
    for(i = 0; i < MAX_CHAR; i++)
        bmBc[i] = -1;
   // case1:Fill the actual value of last occurrence of a character
    for(i = 0; i < m; i++)
        bmBc[pat[i]] = i;

}

計算好後綴數組bmGs[]

求解好後綴數組之前先求解好後綴數組長度的輔助數組suff[]技術分享表示以i為邊界,與模式串後綴匹配的最大長度,例如以下圖所看到的:

技術分享

suff[i]就是求pat中以i位置字符為後綴和以最後一個字符為後綴的公共後綴串的長度(包含當前位置字符)。以下舉例說明:

i  : 0 1 2 3 4 5 6 7
	 | | | | | | | |
pat: b c a b a b a b
/*
當i=m-1=7時,則suff[7]=8;
當i=6時。以pat[6]為後綴的後綴字符串為bcababa,以最後一字符b為後綴的後綴字符串為bcababab
	     則不存在公共最長子串,即suff[6]=0;
當i=5時,以pat[5]為後綴的後綴字符串為bcabab,以最後一字符b為後綴的後綴字符串為bcababab
	     則公共最長子串abab,即suff[5]=4;
當i=4時,以pat[4]為後綴的後綴字符串為bcaba,以最後一字符b為後綴的後綴字符串為bcababab
	     則不存在公共最長子串,即suff[4]=0;
.......
當i=0時,以pat[0]為後綴的後綴字符串為b,以最後一字符b為後綴的後綴字符串為bcababab
	     則公共最長子串b。即suff[0]=1;
*/


suff數組的定義:引用自《Boyer-Moore algorithm

對於技術分享;技術分享

當中m為模式串的長度。

latex=suff[m-1]=m">技術分享。所以非常easy源代碼實現例如以下:

void suffix(const string &pat, int m, int suff[])
{
    int i, j;
 
    suff[m - 1] = m;
 
    for(i = m - 2; i >= 0; i--)
    {
		j = i;
        while(j >= 0 && pat[j] == pat[m - 1 - i + j]) j--;
 
        suff[i] = i - j;
    }
}
有了上面求解的好後綴長度數組suff[]如今能夠計算好後綴數組bmGs[],依據前面好後綴的三種情況。這裏求解數組也相應三種情況:

技術分享

則能夠寫出好後綴數組bmGs[]的源碼:

void PreBmGs(const string &pat, int m, int bmGs[])
{
    int i, j;
    int suff[SIZE];  
 
    // computed the suff[]
    suffix(pat, m, suff);
 
    // Initialize all occurrences as -1, include case3
    for(j = 0; j < m; j++)
    {
        bmGs[j] = -1;
    }
 
    // Case2
    j = 0;
    for(i = m - 1; i >= 0; i--)
    {
        if(suff[i] == i + 1)
        {
            for(; j < m - 1 - i; j++)
            {
                if(bmGs[j] == -1)
                    bmGs[j] = i;
            }
        }
    }
 
    // Case1
    for(i = 0; i <= m - 2; i++)
    {
        j = m - 1 - suff[i];
		bmGs[j] = i;
    }
}

BM算法匹配過程

到此為止已經解說了BM算法的求解方法,下面給出BM算法的程序:

#include <iostream>
#include <string>

using namespace std;
 
const int MAX_CHAR = 256;
const int SIZE = 256;
static inline int MAX(int x, int y){return x < y ?

y:x;} void BoyerMoore(const string &pat, const string &txt); int main() { string txt = "abababaacbabaa"; string pat = "babaa"; BoyerMoore(pat,txt); system("pause"); return 0; } void PreBmBc(const string &pat, int m, int bmBc[]) { int i = 0; // Initialize all occurrences as -1, include case2 for(i = 0; i < MAX_CHAR; i++) bmBc[i] = -1; // case1:Fill the actual value of last occurrence of a character for(i = 0; i < m; i++) bmBc[pat[i]] = i; } void suffix(const string &pat, int m, int suff[]) { int i, j; suff[m - 1] = m; for(i = m - 2; i >= 0; i--) { j = i; while(j >= 0 && pat[j] == pat[m - 1 - i + j]) j--; suff[i] = i - j; } } void PreBmGs(const string &pat, int m, int bmGs[]) { int i, j; int suff[SIZE]; // computed the suff[] suffix(pat, m, suff); // Initialize all occurrences as -1, include case3 for(j = 0; j < m; j++) bmGs[j] = -1; // Case2 j = 0; for(i = m - 1; i >= 0; i--) { if(suff[i] == i + 1) { for(; j < m - 1 - i; j++) { if(bmGs[j] == -1) bmGs[j] = i; } } } // Case1 for(i = 0; i <= m - 2; i++) { j = m - 1 - suff[i]; bmGs[j] = i; } } void BoyerMoore(const string &pat, const string &txt) { int j, bmBc[MAX_CHAR], bmGs[SIZE]; int m = pat.length(); int n = txt.length(); // Preprocessing PreBmBc(pat, m, bmBc); PreBmGs(pat, m, bmGs); // Searching int s = 0;// s is shift of the pattern with respect to text while(s <= n - m) { j = m - 1; /* Keep reducing index j of pattern while characters of pattern and text are matching at this shift s */ while(j >= 0 && pat[j] == txt[j + s]) j--; /* If the pattern is present at current shift, then index j will become -1 after the above loop */ if(j < 0) { cout<<"pattern occurs at shift :"<< s<<endl; /* Shift the pattern so that the next character in text aligns with the last occurrence of it in pattern. The condition s+m < n is necessary for the case when pattern occurs at the end of text */ s += (s+m < n)? m-bmBc[txt[s+m]] : 1; } else {/* Shift the pattern with the Max value between bmBc[] and bmGs[] */ s += MAX(j - bmBc[txt[s+j]], j-bmGs[j]); } } }

參考資料:

http://www-igm.univ-mlv.fr/~lecroq/string/node14.html

http://blog.csdn.net/v_july_v/article/details/7041827

http://blog.jobbole.com/52830/

http://www.searchtb.com/

http://www.geeksforgeeks.org/pattern-searching-set-7-boyer-moore-algorithm-bad-character-heuristic/

http://www.ruanyifeng.com/blog/2013/05/boyer-moore_string_search_algorithm.html

http://dsqiu.iteye.com/blog/1700312

算法——字符串匹配之BM算法