【總結】理解KMP演算法思想

CN_swords發表於2017-07-10

理解KMP演算法思想

先列出其結論
KMP演算法可以解決模式串在目標串出現的位置,次數等,以及其next[]思想可以解決字串字首和字尾最長的公共部分(字首不包括尾部,字尾不包括頭部)。
其主要思想: 通過計算之前匹配過的位置,節省重複匹配的時間。


q: 博主講得如此糊里糊塗,next[]陣列到底是啥,怎麼節省其重複匹配的時間。

以下一一作講解,首先我們不談KMP,我們來做題

舉例:目標串為:“cbaabababacb”;模式串為:“babac”;
題目:找出目標串中是否存在模式串。

下面  i 'c'表第i個的c字元

最簡單列舉方式: 列舉目標串每個字元作為匹配模式串的首字母,若模式串完全匹配,此字元作為匹配模式串的首字母可行。如此列舉需要(n*m)的複雜度,然而在
cbaabababacb                                 cbaabababacb
    babac                                          babac
模式串的5'b'作為匹配模式串的首字母,匹配失敗後,發現以6'a'作為首字母肯定是不行的,之前匹配過,再次列舉6'a'發生重複匹配;
發現以7'b'作為首字母其前兩位是匹配過的,如果再從首位開始列舉就發生了重複匹配;
假如我們事先知道,6'a'作為首字母肯定是不行的;7'b'作為首字母其前兩位是匹配過的,那麼就能防止其重複匹配,從而引出KMP演算法。
對於KMP演算法: 拿目標串去對比模式串
過程如下:
1'c'-1'b'不等,
2'b'-1'b',3'a'-2'a',4'a'-3'b'不等,(可以不用返回3'a'再開始,後面做解釋)
5'b'-1'b',6'a'-2'a',7'b'-3'b',8'a'-4'a',9'b'-5'c'不等,(對於babab不能匹配成功,但是字首ba已經匹配過,那麼模式串可以跳到3'b'來計算,後面做解釋)
9'b'-3'b',10'a'-4'a',11'c'-5'a',匹配成功一對。
 
1.為什麼不用返回3'a'再開始,因為之前匹配'baa'-'bab'匹配失敗,假設目標串'aa?'(即3'a'為首字母)能與模式串'bab'匹配成功,(即目標串'aa'==模式串'ba')
那麼之前匹配成功的目標串'ba'== 模式串'ba',那麼有目標串'aa'== 目標串'ba',矛盾。同理以4'a'為首字母。
其實很顯然了,但是對於上面具體的字母存在,會有人疑惑。
我們沒有具體字母,如果 目標串'???'與模式串'???'在3'?'匹配失敗,那麼我們至少知道目標串1'?'2'?'和模式串1'?'2'?'是匹配成功的,
怎麼樣知道目標串2'?'作為首字母是否匹配,就是模式串的1'?'和2'?'是否相等,相等一位匹配成功
那麼其實就是算匹配完成的部分模式串的字首和字尾的最長公共部分長度。next陣列就是預處理這個的。
2.為什麼可以直接跳到3'b'來計算,之前匹配的'babab'-'babac'失敗,以哪一點為開始點,就如上所說
對於'baba'字首和字尾最長的公共部分 字首為模式串起點的前一位,和字尾目標串起點的前一位(其實就繼續往後,就不用回前面匹配了),節省了重複匹配的時間。

那麼其實大家也就知道next陣列的意義所在
還是提一下:next[i]陣列代表 模式串0-i部分的字首和字尾的最長公共部分的長度
如果next[0]初始化為-1,那麼next[]+1才能表示其公共部分長度。其求法有點像遞迴,
舉例:對於 “bababa”中原next[]陣列應為“-1,-1,0,1,2,3”,
在加一個字元'b'由之前已知的最長公共字首'baba'後面一位與字元對比,即(bababab)中 5'b'-7'b'。
若加的字元是'a'那麼5'b'-7'a'不等,再往前回溯,與'baba'的最長公共字首'ba'後面一位與字元對比,直到值相等或回溯到-1(即最長公共字首長度為0)。
為什麼從'baba'往前回溯,且回溯到'baba'的最長公共字首?
為了保證'bababa'的字首字尾相等,只有4,2,0長度,即next的回溯過程。







int nex[N];
void getnext(char *p,int len)
{
    nex[0] = -1;    //初始值
    int k = -1;        //根問題的next[]值。
    for(int i = 1; i < len; i++)
    {
        while(k!=-1 && p[k+1]!=p[i])
            k = nex[k];    //向前回溯,直到找到與其相等,或回溯到根
        if(p[k+1] == p[i])    //判斷是否找到與其相等
            k++;
        nex[i] = k;
    }
}


進行匹配
int kmp(char *s,char *p)
{
    int len = strlen(s);
    int lenp = strlen(p);
    int k = -1;
    int ans = 0;
    for(int i = 0; i < len; i++)
    {
        while(k!=-1 && p[k+1]!=s[i])
            k = nex[k];
        if(p[k+1]==s[i])
            k++;
        if(k == lenp-1){
            k = nex[k];
            ans++;
        }
    }
    return ans;
}

相關文章