KMP算法詳解

問題描述

指定文本串:aabaabaaf和模式串:aabaaf

使用KMP算法判斷模式串是否在文本串中出現(xiàn)過阅茶?

假定模式串的長度小于文本串

思路解析

BF算法的問題是:模式串已經(jīng)匹配到最后一位了發(fā)現(xiàn)不一樣矾柜,需要將文本串和模式串的指針都往后退跺讯,導(dǎo)致有很多的重復(fù)匹配,效率很低。

KMP算法的思路是间聊,在發(fā)現(xiàn)某個(gè)字符不匹配的時(shí)候族购,充分利用前面已經(jīng)匹配過的結(jié)果壳贪,不要把“搜索指針”回退到已經(jīng)比較過的位置,而是把模式串往后移動(dòng)到合適的位置繼續(xù)比較寝杖。KMP算法只需要對(duì)文本串搜索一次违施,時(shí)間復(fù)雜度是O(n)。

檢索過程圖解

image

如上圖所示瑟幕,在遇到不匹配的時(shí)候醉拓,依靠“部分匹配表”可知伟姐,最后一個(gè)匹配字符a對(duì)應(yīng)的部分匹配值是2,可以根據(jù)下面的公式計(jì)算出模式串的移動(dòng)位數(shù)亿卤。

移動(dòng)位數(shù) = 已經(jīng)匹配的字符串長度 - 對(duì)應(yīng)的部分匹配值

aabaa是已經(jīng)匹配的字符串愤兵,長度是5;aabaa對(duì)應(yīng)的部分匹配值是2排吴,因此將模式串往右移動(dòng)3位秆乳,就可以繼續(xù)匹配了,KMP就是通過這個(gè)“部分匹配表”避免了搜索指針的回退钻哩。

部分匹配表

要理解部分匹配表屹堰,需要了解兩個(gè)概念:前綴子串、后綴子串

  • 前綴子串:包含首字母街氢,不包含尾字母的所有子串
  • 后綴子串:包含尾字母扯键,不包含首字母的所有子串

例如:單詞"bread"

  • 前綴子串:"b","br","bre","brea"
  • 后綴子串:"d","ad","ead","read"

部分匹配表中的數(shù)值含義是:當(dāng)前這個(gè)字符前面的字符串中,相等的前綴子串和后綴子串的最大的長度珊肃。計(jì)算下aabaa的部分匹配表:

  • a:前綴子串集合和后綴子串集合都是空集荣刑,相等的前后綴子串的最大長度是0
  • aa:前綴子串集合是[a],后綴子串集合是[a]伦乔,相等的前后綴子串的最大長度是1
  • aab:前綴子串集合是[a,aa]厉亏,后綴子串集合是[b,ab],相等的前后綴子串的最大長度是0
  • aaba烈和,前綴子串集合是[a,aa,aab]爱只,后綴子串集合是[a,ba,aba],相等的前后綴子串的最大長度是1
  • aabaa招刹,前綴子串集合是[a,aa,aab,aaba]恬试,后綴子串集合是[a,aa,baa,abaa],相等的前后綴子串的最大長度是2
  • aabaaf疯暑,前綴子串集合是[a,aa,aab,aaba,aabaa]忘渔,后綴子串集合是[f,af,aaf,baaf,abaaf],相等的前后綴子串的最大長度是0

“部分匹配表”的實(shí)質(zhì)是缰儿,模式字符串中有時(shí)會(huì)有重復(fù)的子串畦粮,例如:aabaa的左右兩邊都有aa,那么該字符串的部分匹配值就是2乖阵,那么在發(fā)現(xiàn)aabaaf中的f不匹配的時(shí)候宣赔,只需要將aabaaf這個(gè)模式串往右移動(dòng)3個(gè)位置,就可以讓第一個(gè)aa來到第二個(gè)aa的位置瞪浸。

image

代碼實(shí)現(xiàn)

package org.javaadu.string;

import java.util.List;

public class StringSearchDemo {

    public static void main(String[] args) {
        String text = "aabaabaaf";
        String pattern = "aabaaf";

        boolean kmpRes = kmpSearch(text, pattern);
        System.out.println("kmpRes:" + kmpRes);
    }

    /**
     * 利用KMP算法求解pattern是否在text中出現(xiàn)過
     *
     * @param text    文本串
     * @param pattern 模式串
     * @return pattern在text中出現(xiàn)儒将,則返回true,否則返回false
     */
    public static boolean kmpSearch(String text, String pattern) {
        //部分匹配數(shù)組对蒲,就是很多算法實(shí)現(xiàn)中的next數(shù)組
        int[] partMatchTable = buildPartMatchTable(pattern);
        //text中的指針
        int i = 0;
        //pattern中的指針
        int j = 0;

        while (i < text.length()) {
            if (text.charAt(i) == pattern.charAt(j)) {
                //字符匹配钩蚊,則兩個(gè)指針同時(shí)后移
                i++;
                j++;
            } else if (j > 0) {
                //字符失配贡翘,則利用next數(shù)組,異動(dòng)j指針砰逻,避免i指針回退
                j = partMatchTable[j - 1];
            } else {
                //pattern中的第一個(gè)字符就失配了
                i++;
            }

            if (j == pattern.length()) {
                //搜索成功
                return true;
            }
        }
        return false;
    }

    private static int[] buildPartMatchTable(String pattern) {
        //初始化
        int[] partMatchTable = new int[pattern.length()];
        int prefixLen = 0;
        next[0] = 0;
        int i = 1;

        while (i < pattern.length()) {
            //pattern[prefixLen]鸣驱,表示目前最長相等子串的最后一位
            //pattern[i],表示目前正在處理的子串的最后一位的字符
            if (pattern.charAt(prefixLen) == pattern.charAt(i)) {
                //如果它倆相等蝠咆,說明找到了更長的相等子串
                prefixLen++;

                partMatchTable[i] = prefixLen;
                i++;//處理下一個(gè)i的字符
            } else {
                //如果不相等踊东,則需要嘗試下更短1位的子串是否滿足要求,因此這里要把再次循環(huán)嘗試下:僅僅改變prefixLen的值刚操,不改變i的值
                prefixLen = next[prefixLen - 1];
                if (prefixLen == 0) {
                    //如果實(shí)在沒有合適的闸翅,則說明當(dāng)前正在處理的子串的最長相等前后綴的長度是0
                    partMatchTable[i] = 0;
                    i++;//處理下一個(gè)i的字符
                }
            }
        }
        return partMatchTable;
    }
}

參考資料

  1. https://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html
  2. https://www.bilibili.com/video/BV1AY4y157yL/?vd_source=a7cf54b1d9550c0b692539a82b982181
  3. https://www.bilibili.com/video/BV1PD4y1o7nd/?spm_id_from=333.337.search-card.all.click&vd_source=33b7a49ff9ee993637d7533b74ed12a5
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市菊霜,隨后出現(xiàn)的幾起案子坚冀,更是在濱河造成了極大的恐慌,老刑警劉巖鉴逞,帶你破解...
    沈念sama閱讀 223,207評(píng)論 6 521
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件记某,死亡現(xiàn)場離奇詭異,居然都是意外死亡华蜒,警方通過查閱死者的電腦和手機(jī)辙纬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,455評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門豁遭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叭喜,“玉大人,你說我怎么就攤上這事蓖谢∥嬖蹋” “怎么了?”我有些...
    開封第一講書人閱讀 170,031評(píng)論 0 366
  • 文/不壞的土叔 我叫張陵闪幽,是天一觀的道長啥辨。 經(jīng)常有香客問我,道長盯腌,這世上最難降的妖魔是什么溉知? 我笑而不...
    開封第一講書人閱讀 60,334評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮腕够,結(jié)果婚禮上级乍,老公的妹妹穿的比我還像新娘。我一直安慰自己帚湘,他們只是感情好玫荣,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,322評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著大诸,像睡著了一般捅厂。 火紅的嫁衣襯著肌膚如雪贯卦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,895評(píng)論 1 314
  • 那天焙贷,我揣著相機(jī)與錄音撵割,去河邊找鬼。 笑死盈厘,一個(gè)胖子當(dāng)著我的面吹牛睁枕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沸手,決...
    沈念sama閱讀 41,300評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼外遇,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了契吉?” 一聲冷哼從身側(cè)響起跳仿,我...
    開封第一講書人閱讀 40,264評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎捐晶,沒想到半個(gè)月后菲语,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,784評(píng)論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡惑灵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,870評(píng)論 3 343
  • 正文 我和宋清朗相戀三年山上,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片英支。...
    茶點(diǎn)故事閱讀 40,989評(píng)論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡佩憾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出干花,到底是詐尸還是另有隱情妄帘,我是刑警寧澤,帶...
    沈念sama閱讀 36,649評(píng)論 5 351
  • 正文 年R本政府宣布池凄,位于F島的核電站抡驼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏肿仑。R本人自食惡果不足惜致盟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,331評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望尤慰。 院中可真熱鬧馏锡,春花似錦、人聲如沸割择。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,814評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荔泳。三九已至蕉饼,卻和暖如春虐杯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背昧港。 一陣腳步聲響...
    開封第一講書人閱讀 33,940評(píng)論 1 275
  • 我被黑心中介騙來泰國打工擎椰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人创肥。 一個(gè)月前我還...
    沈念sama閱讀 49,452評(píng)論 3 379
  • 正文 我出身青樓达舒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親叹侄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子巩搏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,995評(píng)論 2 361

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

  • KMP是解決兩個(gè)字符串匹配問題的非常好的算法,算法的時(shí)間復(fù)雜是O(n)趾代。 現(xiàn)在假設(shè)場景是兩個(gè)字符串str1贯底,str...
    道禪_26ea閱讀 2,437評(píng)論 0 0
  • 一:什么是KMP算法? KMP誕生背景: KMP(Knuth-Morris-Pratt)三位大佬聯(lián)名提出撒强,故以他們...
    憤怒的謎團(tuán)閱讀 1,626評(píng)論 0 2
  • KMP算法是解決字符串匹配的常用算法之一禽捆,也就是在主串(比如aabbccdd)中的子串(bc)定位問題。子串稱為P...
    激情的狼王閱讀 1,031評(píng)論 0 1
  • 原鏈接:KMP算法詳解|CloudWong 傳統(tǒng)的字符串匹配模式(暴力循環(huán)) 子串的定位操作通常稱作串的串的匹配模...
    簡Cloud閱讀 3,916評(píng)論 1 22
  • 先說一下為啥要了解kmp算法飘哨,因?yàn)榘⒗锩嬖囉械烂嬖囶}目胚想,如果在所有字符串最快速度找出目標(biāo)字符串的位置。我用了最...
    來自遠(yuǎn)方的詩人閱讀 289評(píng)論 0 0