KMP 字符串匹配算法

今天看了kmp算法,最開始看得特別混亂,最后終于看明白了,想記錄一下。
https://github.com/hym105289/KMP/blob/master/src/KMP.java

1.題目

給定兩個字符串str和match,長度分別為N和M。實現一個算法,如果字符串str中含有子串match,則返回match在str中開始的位置,不含有則返回-1.

2.暴力解法

一般匹配字符串時,我們從目標字符串str(假設長度為n)的第一個下標選取和match長度(長度為m)一樣的子字符串進行比較,如果一樣,就返回開始處的下標值,不一樣,選取str下一個下標,同樣選取長度為n的字符串進行比較,直到str的末尾(實際比較時,下標移動到n-m)。這樣的時間復雜度是O(n*m)。


復雜度O(MN)
 public static int search(String str,String match){
        int N=str.length();
        int M=match.length();
        for (int i = 0; i <= N-M; i++) {
            int j=0;
            for (j = 0; j < M; j++) {
                if (str.charAt(i+j) != match.charAt(j)) {
                    break;
                }
            }
            if (j == M) {
                return i;
            }
        }
        return -1;
    }

回溯i指針

public static int search3(String pat, String txt) {
        int j, M = pat.length();
        int i, N = txt.length();
        for (i = 0, j = 0; i < N && j < M; i++) {
            if (txt.charAt(i) == pat.charAt(j))
                j++;
            else {
                i -= j;
                j = 0;
            }
        }
        if (j == M)
            return i - M; // found
        else
            return N; // not found
    }

3.KMP

看一下暴力比較壞的情況

image.png

KMP改進的地方:每當一趟匹配過程中出現字符串不等時,利用已經得到的部分比較結果,將模式向右滑動盡可能多的距離。
比如在上述的圖片中i=6,j=5時不匹配,我們根據之間的匹配結果,可以直接將i=6和模板j=0處進行比較,而無需將i進行回溯。
關鍵問題:當主串中的第i個字符和模式串中的第j字符不匹配時,主串中的i字符應該和模式串中的哪個字符再比較?
假設此時的主串中的i字符串和模式串中的k字符進行比較,必須滿足以下兩個條件:
①已經得到的匹配結果:

②主串i能和模式串k進行比較:

由①和②可以推斷出:

這三個式子說明的問題:
橙色部分代表相同的子串

當主串S[i]!=P[j]的時候,主串i和模式串j不匹配時,主串i將和模式串k比較中的k的值,其實是取決于模式串本身的特性,與主串無關。
①求解KMP算法的第一步,就是要求出當主串和模式串j不匹配時,主串應該繼續和模式串中的哪個字符進行比較。計算模式字符串match的nextArr數組。
前綴子串:以第一個字符開始,連續但是不包括最后一個字符的字符串;
后綴子串:不能以第一個字符開始,連續但必須包括最后一個字符的字符串;
nextArr[j]:就是求從模式字符串下標從0到j的字符串的前綴子串和后綴子串的最大匹配長度。
eg:模式串match=abaabcac,模式串的長度為8:
nextArr[0]:計算match[0]之前的字符串=空,前綴子串和后綴子串的最大匹配長度=0——nextArr[0]=-1(第一個字符在它之前沒有字符規定設置為-1);
nextArr[1]:計算match[1]之前的字符串=a,前綴子串和后綴子串的最大匹配長度=0——nextArr[1]=0;
nextArr[2]:計算match[2]之前的字符串=ab,前綴子串和后綴子串的最大匹配長度=0——nextArr[2]=0;
nextArr[3]:計算match[3]之前的字符串=aba,前綴子串a和后綴子串a的最大匹配長度=1——nextArr[3]=1;
nextArr[4]:計算match[4]之前的字符串=abaa,前綴子串a和后綴子串a的最大匹配長度=1——nextArr[4]=1;
nextArr[5]:計算match[5]之前的字符串=abaab,前綴子串ab和后綴子串ab的最大匹配長度=1——nextArr[5]=2;
nextArr[6]:計算match[6]之前的字符串=abaabc,前綴子串和后綴子串的最大匹配長度=0——nextArr[6]=0;
nextArr[7]:計算match[7]之前的字符串=abaabca,前綴子串a和后綴子串a的最大匹配長度1——nextArr[7]=1;
②假設我們已經求出啊來了nextArr數組,利用兩個指針si和pi分別指向查找字符串和模式字符串的首字符,如果匹配成功,則si++,pi++,如果匹配不成功,并且是在第一個模式串字符處匹配不成功,則si++,其他位置匹配不成功,si處的字符和next[pi]的模式串字符進行匹配;這個過程最多執行N次,時間復雜度為O(n).

public static int getIndex(String str, String pat){
        if (str == null || pat == null || str.length() <pat.length() || pat.length() <1) {
            return -1;
        }
        char[] s=str.toCharArray();
        char[] p=pat.toCharArray();
        int si=0,pi=0;
        int[] next=getNextArr(p);
        while (si<s.length&&pi<p.length) {
            if(s[si] == p[pi]){//字符串匹配則兩個指針不斷向前移動
                si++;
                pi++;
            }
            else if (next[pi] == -1) {//和模式字符串的第一個字符不匹配,則指向s的字符串向前移動
                si++;
            }else {
                pi=next[pi];   //匹配失敗,則重新定位模式串的該匹配字符
            }
        }
        return pi == p.length ? si-pi:-1;
    }

如何快速得到模式字符串pat的nextArr數組,并且復雜度是O(m)?
pat=abaabcac
1.對于nextArr[0]而言,由于它之前沒有字符,所以規定nextArr[0]=-1;
2.對于nextArr[1]而言,由于它之前只有一個字符,所以一定沒有匹配的前綴子串和后綴子串,nextArr[1]=0;
3.當下標pos>1時求解過程如下:
從左到右依次求解nextArr數組,先求nextArr[0],nextArr[1],nextArr[2]....最后求nextArr[m-1],這說明當我們求nextArr[i]時,其實nextArr[i-1]已經求好了,我們已經知道B處之前字符串的前綴子串和后綴子串的最大匹配區域。

橙色部分表示相同的子串

如果字符C和字符B相等,那么A之前的最長前綴子串和后綴子串匹配的區域就可以確定了,nextArr[i]=nextArr[i-1]+1;
紅框分別代表匹配最大的前綴子串和后綴子串

如果字符C!=字符B,就看字符C之前的前綴和后綴的匹配區域。
C之前的最長前綴和后綴匹配區域是n和m

m區域和n區域分別是字符C之前的字符串的最長匹配的后綴與前綴區域,這是通過next[cn]確定的,我們可以根據圖示看到相同顏色的代表匹配的字符串,那么一定可以在字符B之前的最長匹配的后綴字符串中找到和m區域相同長度的m'區域。,接下來比較字符D和字符B是否相同?
1)D==B,nextArr[i]=nextArr[cn]+1;
2 ) D != B,繼續往前跳到字符D,然后重復類似的過程,每次跳一步都會有一個新的字符和B進行比較,如果相等,則nextArr[i]就能夠確定。
如果向前跳到最左的位置,即pat[0]的位置,nextArr[0]==-1,則說明字符A之前的字符不存在匹配的前綴和后綴子串,nextArr[i]=0;

public static int[] getNextArray(char[] pat){
        if (pat.length==1) {
            return new int[]{-1};
        }
        int[] next=new int[pat.length];
        next[0]=-1;
        next[1]=0;
        int pos=2;
        int cn=0;//注意cn總是記錄著next[pos-1]的值
        while (pos<next.length) {
            if (pat[pos-1]==pat[cn]) {
                next[pos++]=++cn;
            }else if (cn>0) {
                cn=next[cn];
            }else {
                next[pos++]=0;
            }
        }
        return next;
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,034評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,413評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,449評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,165評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,559評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,781評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,327評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,084評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,278評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,495評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,927評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,172評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,010評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,241評論 2 375

推薦閱讀更多精彩內容