BM算法(轉)

在用於查找子字符串的算法當中,BM(Boyer-Moore)算法是目前相當有效又容易理解的一種,一般情況下,比KMP算法快3-5倍。

用一幅圖說明BM算法的原理(來自,A Fast String Search Algorithm, Boyer and Moore)。

BM算法 - 光影隨行 - 光影隨行的博客

圖中pat表示模式串AT-THAT,string是需要查找的串,我們就是要在string中找pat,即AT-THAT.

BM的第一個規則(圖片上標識爲1的地方):

1、 從後向前匹配。我們看到指針是從pat的最後一個字符開始考慮的。至於爲什麼這麼考慮,我想與英文的單詞組成相關,英文單詞很多都是採用相同前綴構造的, 還有就是一個單詞的很多形式前綴也都是相同的,而後綴不同,所以這樣可以儘快的排除不符合要求的單詞,使指針在string中快速前移。

2、 如果當前指針指向的字符不在pat中,比如第一步,指針指向F,而F不在pat中,那麼顯然,pat不可能跟與F有關的任何strinig的字串匹配,於 是下一次,開始匹配位置被移動到F之後,也就是從I這個字符開始考慮,當然了,還是從後向前匹配,所以,我們看到第二步,指針向前移動了 strlen(pat),指向了-,而pat的第一個字符與I對齊。這就是文章中說的

Since 'F' is known not to occur in pat, we can ....

3、 壞字符規則:我們看到string中指針指向的字符-和pat的最後一個字符T並不相等,這時候,我們將pat右移,但是右移多少呢?我們發現pat中其 實存在字符-,所以,我們就讓pat中的字符和當前string中的-對其,這就是第3步上面我們看到的結果。爲什麼要這麼移動?這個理解了就很容易,如 果不能理解一句話還真不能說清楚。你可以想象,如果string的當前字符-能夠存在於最終和pat匹配string的子串中,那麼顯然,-需要和pat 中的某個字符匹配,和哪個呢?自然,-只能和pat中的-匹配,所以需要將它們對齊。

注意,對齊之後,不是從當前對齊的位置開始繼續匹配,而是再次從pat的最後一個位置開始匹配,所以,牢記這個規則,只要string的指針發生了跳轉,那麼,每次總是從pat的最後一個指針開始匹配的。

4、 在圖中數字4上面我們看到,string中的T跟pat的最後的字符T匹配,於是指針前移(從後向前),現在居然發現string指針指向的字符L不在 pat中,根據我們前面第一步的說法,L不可能所在的子串不可能跟pat匹配了,所以,pat繼續後移,使得pat的第一個字符與L的後一個字符對齊。

5、 好後綴規則:這一次很好,string指針指向的T與pat倒數第一個字符匹配,而且倒數第二個A也匹配,但是,倒數第三個就不行 了,string指針指向字符-,而pat指向H,現在我們說利用壞字符規則,在pat中找-,然後讓他們對齊,對,這個規則的確可以用,不錯,學習很 快。我們很快在pat中找到了-,於是我們說,pat向後移動2個位置(其實就等效於string的指針後移2個位置),使得pat的-和當前的-對齊, 然後繼續尋找。

恩,別急。先停下來,讓我們想象這樣是否是最好的?當我們將pat中的-和當前string指針指向的-對齊的時候,他們的樣子是這樣的

pat:                             AT-THAT

string:.                   ....... --AT-TH

我 們可以看到,他們不會匹配。因爲pat中-後面的字符是TH,而string中的是AT,所以,這時候將pat移動2位是沒有用的。然後,我們在pat中 只有繼續向前找-,但是,沒有了,所以,此時,pat應該移動strlen(pat)纔行(即string指針移動strlen(pat))。很好,顯 然,後一次移動的幅度要大一些,如果每次我們都能儘可能大的移動string的指針,不是能加快匹配速度嗎?

的確如此。

上面第五步中,我們說指針移動兩個位置不能解決問題,我們如何知道?是不是一定得重新匹配一次?顯然,如果需要重新匹配,我就不花時間研究了。

來看,第五步中,當string指針指向-的時候,此時,pat等待匹配的是倒數第三個字符H,這說明pat中H後面的AT已經匹配了,也就是說,如果將pat右移,那麼右移之後必須保證該位置之後依然出現AT字符,才能符合我們已經匹配的目標要求。也就是說,如果

“某字符串”

跟 pat匹配的時候,在倒數第三個字符處出現了不匹配的現象,這時候,如果我們要移動pat的指針到一個新的位置,那麼該位置之後的字符串必須保證得和 pat第三個字符之後的串保持一致。否則,就必須移動更大的步長。注意,我說某字符串,而沒有說指這裏的string,這就是說,在倒數第三個位置不能匹 配的時候,需要移動多少位,是事先可以計算好的。很好,這是關鍵。由此類推,在任何一個位置,當字符不能和pat中的字符匹配的時候哦,我們都能知道需要 移動多少步長才會可能找到最終的匹配串。解決了一個大問題。

那麼,我們注意到了,根據壞字符規則,我們可以得到一個步長,而根據剛剛說的好後綴規則也可以得到一個,用哪個?用大的那個。

這 就是BM算法的精髓,但是我發現要很快理解也不是很容易,我在網站找了一些,都不太好理解,最後下載了A Fast String Search Algorithm, Boyer and Moore這篇文章才明白了一點,但是,如果你讀他的敘述一樣會頭大,最好看看例子,再看錶述。至於,那些公式,不必看了,那是爲了發文章寫的。

//////////////////////////////////////////////////

BM算法和KMP算法一樣,也是構造一個輔助的模式函數來加速匹配的速度。和KMP的模式函數相比BM的模式函數更加的簡單:

 

void make_next(const char p[], int next[])

{

   for(int i = 0; i < strlen(p); i++)

       next[p[i]] = i;

}

next[] 是一個和ASCII數目一樣大的數組256個數據吧。當然如果出現重複的字符,那麼記錄的就是這個字符最後出現的位置。

上面的這個模式函數就是,安照出現的字符對應的 ASCII 位置將 next[] 置爲位置序號。

 

#include <string.h>

#include <stdio.h>

#include <stdlib.h>

 

/* 輔助數組,取決於字符集和,默認的採用 ASCII字符集,256個元素*/

#define LEN 256

int BMMatcher(char *s, char *p, int index, int position[])

/*

參數說明:

char *s: 匹配串

char *p: 模式串

int index: 模式串匹配的起始位置,是匹配串的索引

int position[] 輔助數組,

*/

{

   int len = strlen(s);

   int i,j, nextindex;

   i = strlen(p)-1;//減1是因爲要去掉最後的那個'/0'

   j = index+strlen(p)-1;//第一次調用 BMMatcher 時 index = 0,因爲下面的 for 循環是從模式串的末尾開始比較,所以匹配串的初始比較位置應該是從開頭數模式串長度個位置開始。

 

   for(; i>=0; i--, j--)

   {

      if(s[j] != p[i])

        break;

   }

   if(i<0) //i<0 說明模式串已經遍歷完畢

     return 0; /*匹配成功*/

   else if(position[s[j]]>0)//當出現不匹配時,查看匹配串當前位置的字符有沒有出現在模式串中

     nextindex = index + i - position[s[j]];

//index 是當前的匹配串起始偏移量,i 是模式串還剩的比較字串數目, position[s[j]]是所出現的第一個不匹配的字符在匹配串中的位置。這樣下次比較就從匹配串中出現 s[j] 的位置開始比較

   else nextindex = index + 1;

 

   if(nextindex > LEN-strlen(p))

     return -1; /*匹配失敗,無法進行下一次匹配*/

   else

     return nextindex; /*匹配失敗,需要下一次匹配*/

 }

 

 /*測試, 匹配串 和 模式串都使用小寫字符*/

 int main()

 {

    int position[LEN]={0}; /*輔助數組*/

    char *src="it is just a test, what would you do?"; /*匹配串*/

    char *patten="what would"; /*模式串*/

    int i, nextindex, index=-2, pos=0;

 

    for(i=0; i<strlen(patten); i++) /*構造輔助數組,關鍵的一步,但是很簡單*/

       position[patten[i]]=i;

    index = BMMatcher(src, patten, 0, position);

    while(!(index==-1 || index==0)) /*循環匹配,直到匹配成功,或者匹配失敗結束*/

    {

      nextindex = index;

      index = BMMatcher(src, patten, nextindex, position);

    }

 

    if(index == -1)

       printf("Can not find it/n");

 

    if(index == 0)

       printf("Find it, the index is: %d./n", nextindex);

 

    system("PAUSE");

    return 0;

 }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章