字符串搜索之Boyer Moore


字符串搜索的操作是比較+移步(shift)古今,如何高效的移步成為了這個算法的關(guān)鍵所在
最壞的移步就是一次移一步,即暴力解法(Brute Force)嘲玫。那么優(yōu)化的解法就是一次移多步雏门。

字符串搜索算法匯總:傳送門
里面詳細介紹各種解法,真是有點多啊哪雕,囧
比較操作的時候算法中分為兩類一類是從左向右比較即從開始字符到結(jié)束字符,另一類是從右向左比較即從結(jié)束字符到開始字符

本文主要學習Boyer Moore算法
維基百科上說鲫趁,Boyer Moore算法是1977年Robert S. Boyer和J Strother Moore發(fā)明的斯嚎。
阮一峰有篇很好的對BoyerMoore算法的科普文:傳送門
其中詳述了一個Moore教授的例子
BoyerMoore關(guān)于移步最核心的就是兩個概念
壞字符移步(bad-character shift)和好后綴移步(good-suffix shift),弄明白這兩個概念就明白了Boyer Moore.
在需要移步的時候考察這兩個移步的步數(shù)挨厚,誰的值更大就選擇誰來作為移步的步數(shù)
為了生成壞字符移步和好后綴移步堡僻,我們只需要對搜索的子(短)字符串進行處理,而不需要對目標(長)字符串進行處理疫剃。
glibc中關(guān)于子串搜索的函數(shù)strstr的這兩個字符串的參數(shù)名字很有意境钉疫,前者叫needle,后者叫haystack巢价,大海撈針啊牲阁,哈哈。

壞字符移步的生成比較容易理解:
先定義一個包含所有字符(考慮ASCII字符集壤躲,以及ASCII字符集擴展)城菊,一共256個字符

#define ALPHABET_LEN 256
int badCharacter[ALPHABET_LEN];

然后掃描needle串,生成壞字符移步碉克。未在needle串中出現(xiàn)的壞字符的移步都是needle串長度即strlen(needle)

    for (i = 0; i < ALPHABET_LEN; i++) {
        badcharacter[i] = needlelen;
    }

在needle串中出現(xiàn)過的壞字符的移步利用公式:needlelen - 1 - idx求得凌唬,idx代表從左到右對needle串進行掃描的索引,范圍是從0到(needlelen - 1)漏麦。

badcharacter[needle[i]] = needlelen - 1 - i;

考慮到在haystack串中移步時容易理解客税,移步時以最后一個字符作為移步的起點位置况褪,所以上面公式求得的移步值,在移步時要減去字符相比最后字符位置的相對位置即減去(needlelen + 1 + idx)霎挟,idx代表從右到左對needle串進行掃描的索引,范圍是從(needlelen - 1)到0麻掸。

badcharacter[haystack[j+i]] - needlelen + 1 + i

好后綴移步生成:
參考維基百科的C語言實現(xiàn)
經(jīng)過2次掃描needle串
第一次掃描是找是否有needle開始位置的前綴字符串能夠與后綴字符串匹配的情況酥夭,并設(shè)置相應(yīng)字符的好后綴移步
匹配類似下面這種場景

ABCDAB
444461
// true if the suffix of word starting from word[pos] is a prefix
// of word
int is_prefix(uint8_t *word, int wordlen, int pos) {
    int i;
    int suffixlen = wordlen - pos;
    // could also use the strncmp() library function here
    for (i = 0; i < suffixlen; i++) {
        if (word[i] != word[pos+i]) {
            return 0;
        }
    }
    return 1;
}
for (p = needlelen - 1; p >= 0; p--) {
    if (is_prefix(needle, needlelen, p + 1)) {
        last_prefix_index = p + 1;
    }
    goodsuffix[p] = last_prefix_index + needlelen - 1 - p;
}

第二次掃描是找是否有needle中的字符串能夠與字符串后綴最大值長度字符匹配的情況,并設(shè)置相應(yīng)字符位置的好后綴移步
匹配類似下面這種場景

BABCDAB
6666461
int suffix_length(uint8_t *word, int wordlen, int pos) {
    int i;
    // increment suffix length i to the first mismatch or beginning
    // of the word
    for (i = 0; (word[pos-i] == word[wordlen-1-i]) && (i < pos); i++);
    return i;
}
for (p = 0; p < needlelen - 1; p++) {
    int slen = suffix_length(needle, needlelen, p);
    if (needle[p - slen] != needle[needlelen - 1 - slen]) {
        goodsuffix[needlelen - 1 - slen] = needlelen - 1 - p + slen;
    }
}

最后用教授的例子作為結(jié)束

HERE IS A SIMPLE EXAMPLE
EXAMPLE
GsMoveStep=1
BcMoveStep=7
HERE IS A SIMPLE EXAMPLE
       EXAMPLE
GsMoveStep=1
BcMoveStep=2
HERE IS A SIMPLE EXAMPLE
         EXAMPLE
GsMoveStep=6
BcMoveStep=3
HERE IS A SIMPLE EXAMPLE
               EXAMPLE
GsMoveStep=1
BcMoveStep=2
HERE IS A SIMPLE EXAMPLE
                 EXAMPLE

代碼:

#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define ALPHABET_LEN 256
#define NOT_FOUND needlelen
#define max(a, b) ((a < b) ? b : a)

#define PRINT_TABLE(table, len) \
do {\
    for(int i = 0; i < len; i++)\
    printf("%d,", table[i]);\
    printf("\n");\
}while(0)

void make_badcharacter(int *badcharacter, uint8_t *needle, int32_t needlelen) {
    int i;
    for (i = 0; i < ALPHABET_LEN; i++) {
        badcharacter[i] = NOT_FOUND;
    }
    for (i = 0; i < needlelen - 1; i++) {
        badcharacter[needle[i]] = needlelen - 1 - i;
    }
}

// true if the suffix of word starting from word[pos] is a prefix 
// of word
int is_prefix(uint8_t *word, int wordlen, int pos) {
    int i;
    int suffixlen = wordlen - pos;
    // could also use the strncmp() library function here
    for (i = 0; i < suffixlen; i++) {
        if (word[i] != word[pos+i]) {
            return 0;
        }
    }
    return 1;
}

// length of the longest suffix of word ending on word[pos].
// suffix_length("dddbcabc", 8, 4) = 2
int suffix_length(uint8_t *word, int wordlen, int pos) {
    int i;
    // increment suffix length i to the first mismatch or beginning
    // of the word
    for (i = 0; (word[pos-i] == word[wordlen-1-i]) && (i < pos); i++);
    return i;
}

void make_goodsuffix(int *goodsuffix, uint8_t *needle, int32_t needlelen) {
    int p;
    int last_prefix_index = needlelen - 1;

    // first loop
    for (p = needlelen - 1; p >= 0; p--) {
        if (is_prefix(needle, needlelen, p + 1)) {
            last_prefix_index = p + 1;
        }
        goodsuffix[p] = last_prefix_index + needlelen - 1 - p;
    }
    PRINT_TABLE(goodsuffix, needlelen);
    // second loop
    for (p = 0; p < needlelen - 1; p++) {
        int slen = suffix_length(needle, needlelen, p);
        if (needle[p - slen] != needle[needlelen - 1 - slen]) {
            goodsuffix[needlelen - 1 - slen] = needlelen - 1 - p + slen;
        }
    }
    PRINT_TABLE(goodsuffix, needlelen);
}

uint8_t* boyer_moore (uint8_t *haystack, uint32_t haystacklen, uint8_t *needle, uint32_t needlelen) {
    int i,j;
    int badcharacter[ALPHABET_LEN];
    int *goodsuffix = (int *)malloc(needlelen * sizeof(int));
    make_badcharacter(badcharacter, needle, needlelen);
    make_goodsuffix(goodsuffix, needle, needlelen);

    // The empty needletern must be considered specially
    if (needlelen == 0) return haystack;

    j = 0;
    while (j <= haystacklen - needlelen) {
        printf("%s\n", haystack);
        for(int s = 0; s < j; s++) printf(" ");
        printf("%s\n", needle);
        for (i = needlelen - 1; i >= 0 && haystack[j+i] == needle[i]; --i);
        if (i < 0) {
            free(goodsuffix);
            return (haystack + j);
        }
        else {
            printf("GsMoveStep=%d\n", goodsuffix[i] - needlelen + 1 + i);
            printf("BcMoveStep=%d\n", badcharacter[haystack[j+i]] - needlelen + 1 + i);
            int k = max(badcharacter[haystack[j+i]] - needlelen + 1 + i, goodsuffix[i] - needlelen + 1 + i);
            j += k;
        }
    }
    free(goodsuffix);
    return NULL;
}
int main(int argc, char const *argv[])
{
    char *s1 = "HERE IS A SIMPLE EXAMPLE";
    //char *s2 = "BABCDAB"
    char *s3 = "EXAMPLE";
    //char *idx1 = (char *)boyer_moore((uint8_t*)s1, (uint32_t)strlen(s1), (uint8_t*)s2, (uint32_t)strlen(s2));
    char *idx2 = (char *)boyer_moore((uint8_t*)s1, (uint32_t)strlen(s1), (uint8_t*)s3, (uint32_t)strlen(s3));
    //printf("%s\n", idx1);
    printf("%s\n", idx2);
    return 0;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末脊奋,一起剝皮案震驚了整個濱河市熬北,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌诚隙,老刑警劉巖讶隐,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異久又,居然都是意外死亡巫延,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門地消,熙熙樓的掌柜王于貴愁眉苦臉地迎上來炉峰,“玉大人,你說我怎么就攤上這事脉执√劾” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵半夷,是天一觀的道長婆廊。 經(jīng)常有香客問我,道長巫橄,這世上最難降的妖魔是什么淘邻? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮湘换,結(jié)果婚禮上列荔,老公的妹妹穿的比我還像新娘。我一直安慰自己枚尼,他們只是感情好贴浙,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著署恍,像睡著了一般崎溃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上盯质,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天袁串,我揣著相機與錄音概而,去河邊找鬼。 笑死囱修,一個胖子當著我的面吹牛赎瑰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播破镰,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼餐曼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鲜漩?” 一聲冷哼從身側(cè)響起源譬,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎孕似,沒想到半個月后踩娘,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡喉祭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年养渴,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泛烙。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡厚脉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胶惰,到底是詐尸還是另有隱情傻工,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布孵滞,位于F島的核電站中捆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏坊饶。R本人自食惡果不足惜泄伪,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望匿级。 院中可真熱鬧蟋滴,春花似錦、人聲如沸痘绎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽孤页。三九已至尔苦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背允坚。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工魂那, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人稠项。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓涯雅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親展运。 傳聞我的和親對象是個殘疾皇子活逆,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容

  • 字符串匹配(查找)算法是一類重要的字符串算法(String Algorithm)。有兩個字符串, 長度為m的hay...
    曾會玩閱讀 3,816評論 4 8
  • 字符串匹配(查找)算法是一類重要的字符串算法(String Algorithm)乐疆。有兩個字符串, 長度為m的hay...
    曾會玩閱讀 598評論 0 2
  • 參考文章 知乎:如何更好的理解和掌握 KMP 算法?從頭到尾徹底理解KMPKMP 算法(1):如何理解 KMP(原...
    Mjolnir1107閱讀 978評論 0 0
  • 1划乖、BF算法 假設(shè)現(xiàn)有一字符串贬养,“BBC ABCDAB ABCDABCDABDE”將其稱為給定串挤土,相應(yīng)有一匹配串“...
    橙小汁閱讀 679評論 5 13
  • 引言 字符串匹配一直是計算機科學領(lǐng)域研究和應(yīng)用的熱門領(lǐng)域,算法的改進研究一直是一個十分困難的課題误算。作為字符串匹配中...
    潮汐行者閱讀 1,632評論 2 6