面試必備——BM字符串查找算法

寫在前面

字符串的一種基本操作是子字符串查找:給定一端長度為N的文本字符串text和一個長度為M(M<N)的模式字符串pattern扯旷,在文本字符串中查找和該模式字符串相同的子字符串。在這互聯(lián)網(wǎng)時代折晦,字符串查找的需求在很多情景都需要吕粗,如在文本編輯器或?yàn)g覽器查找某個單詞胰挑、在通信內(nèi)容中截取感興趣的模式文本等等。

子字符串查找最簡單的實(shí)現(xiàn)肯定是暴力查找:

public static int search(String text, String pattern) {
    int N = text.length();
    int M = pattern.length();
    for (int i = 0; i < N-M; i++) {
        int j;
        for (j = 0; j < M; j++) {
            if (text.charAt(i+j) != pattern.charAt(j))
                break;
        }
        if (j == M) return i;
    }

    return -1;
}

可以看到,暴力查找的最壞時間復(fù)雜度為O(N*M)嘶炭,實(shí)際應(yīng)用中往往文本字符串很長(成萬上億個字符),而模式字符串很短逊桦,這樣暴力算法的時間復(fù)雜度是無法接受的眨猎。

為了改進(jìn)查找時間,人們發(fā)明了很多字符串查找算法卫袒,而今天的主角BM算法(Bob Boyer和J Strother Moore發(fā)明宵呛,簡稱BM算法)就是其中的一種。


正文

不同于暴力查找算法的逐個字符對比夕凝,BM算法充分使用預(yù)處理模式字符串的信息來盡可能跳過更多的字符宝穗。在暴力算法中,比較一個字符串都是從首字母開始码秉,逐個比較下去逮矛。一旦發(fā)現(xiàn)有不同的字符,就需要從頭開始進(jìn)行下一次比較转砖,就需要將字串中的所有字符一一比較须鼎。BM算法的核心思路在于,文本字符串從左到右檢索府蔗,模式字符串從右到左檢索晋控,當(dāng)模式字符串的一個字符pattern[j]和文本字符串的字符text[i+j]不匹配時,那么在模式字符串中查找字符text[i+j]是否存在索引k姓赤,使得pattern[k] == text[i+j]赡译,k若存在,k應(yīng)該為滿足條件的最右索引不铆。此時存在三種情景:

  • 情景1:若pattern不存在索引k(0 <= K < M)蝌焚,使得pattern[k] == text[i+j]裹唆,那么text[i, i+j]不可能跟pattern匹配,那么i向右平移j+1只洒。如圖1所示许帐。
  • 情景2:若pattern存在索引k(0 <= K < M),使得pattern[k] == text[i+j]毕谴,此時又有兩種子情景:
    • 情景2.1:k < j成畦,把text[i+j]對齊pattern[k],也即把i向右平移j-k析珊。如圖2所示羡鸥。
    • 情景2.2:k > j,顯然i不能減少忠寻,那么把i加1惧浴。如圖3所示。
圖1 情景1
圖2 情景2.1
圖3 情景2.2

通過這種字符的移動方式來代替逐個比較奕剃,正是BM算法的高效的關(guān)鍵所在衷旅!那么我們怎么知道文本字符串的字符是否存在于模式字符串中?對的纵朋,預(yù)處理柿顶。我們在查找前,先建立一張包含文本字符串的所有字符的字母表操软,這張表中記錄著字母表中的每個字符在模式字符串中出現(xiàn)的最靠右的索引嘁锯,如果在字符在模式字符串中不存在,那么值為-1聂薪。

有了這張表家乘,我們在查找時就可以高效的移動i。構(gòu)建這張表很簡單:

// 構(gòu)建right數(shù)組
int R = 256; // 字母表大小藏澳,這里用ASCII字母表舉例
int[] right = new int[R]; // 記錄字母表中的每個字符在模式字符串中出現(xiàn)的最右的索引
for (int i = 0;  i < R; i++)
    right[i] = -1;
for (int j = 0; j < M; j++)
    right[pattern.charAt(j)] = j;

構(gòu)建好表仁锯,我們只需要按上面分析的情景,出現(xiàn)字符不匹配時翔悠,通過表业崖,把i向右平移到具體位置即可。BM完整算法實(shí)現(xiàn)如下:

public static int search(String text, String pattern) {
    int N = text.length();
    int M = pattern.length();

    // 構(gòu)建right數(shù)組
    int R = 256; // 字母表大小
    int[] right = new int[R]; // 記錄字母表中的每個字符在模式字符串中出現(xiàn)的最右的索引
    for (int i = 0;  i < R; i++)
        right[i] = -1;
    for (int j = 0; j < M; j++)
        right[pattern.charAt(j)] = j;

    int skip;
    for (int i = 0; i <= N-M; i+=skip) {
        skip = 0;
        for (int j = M-1; j >= 0; j--) {
            if (pattern.charAt(j) != text.charAt(i+j)) { // 不匹配的情況
                skip = j - right[text.charAt(i + j)]; // 這里包括情景1和情景2.1蓄愁,如果情景1双炕,right[text.charAt(i + j)]為-1
                if (skip < 1) skip = 1; // 情景2.2
                break;
            }
        }
        if (skip == 0) return i; // 匹配成功
    }
    return -1; // 未找到匹配
}

由于不匹配的情況屬于大多數(shù),所以一般情況下撮抓,BM算法的時間復(fù)雜度為O(N/M)妇斤,是線性級別的!可以說是非常高效了。但它需要額外的空間字母表大小R趟济,所以BM算法是以空間換時間的。

至此咽笼,BM字符串查找算法已經(jīng)分析完了顷编,其實(shí)算是一種比較簡單的算法,學(xué)習(xí)起來很快就能搞懂~


寫在最后

參考

  1. 《算法:第四版》

相關(guān)閱讀

面試必備——KMP字符串查找算法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末剑刑,一起剝皮案震驚了整個濱河市媳纬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌施掏,老刑警劉巖钮惠,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異七芭,居然都是意外死亡素挽,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門狸驳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來预明,“玉大人,你說我怎么就攤上這事耙箍∽罚” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵辩昆,是天一觀的道長阅酪。 經(jīng)常有香客問我,道長汁针,這世上最難降的妖魔是什么术辐? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮扇丛,結(jié)果婚禮上术吗,老公的妹妹穿的比我還像新娘。我一直安慰自己帆精,他們只是感情好较屿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著卓练,像睡著了一般隘蝎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上襟企,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天嘱么,我揣著相機(jī)與錄音,去河邊找鬼顽悼。 笑死曼振,一個胖子當(dāng)著我的面吹牛几迄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播冰评,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼映胁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了甲雅?” 一聲冷哼從身側(cè)響起解孙,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎抛人,沒想到半個月后弛姜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡妖枚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年廷臼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盅惜。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡中剩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出抒寂,到底是詐尸還是另有隱情结啼,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布屈芜,位于F島的核電站郊愧,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏井佑。R本人自食惡果不足惜属铁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望躬翁。 院中可真熱鬧焦蘑,春花似錦、人聲如沸盒发。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宁舰。三九已至拼卵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蛮艰,已是汗流浹背腋腮。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人即寡。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓徊哑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親聪富。 傳聞我的和親對象是個殘疾皇子实柠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

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