kmp算法及對next數組的理解

kmp算法詳解:https://blog.csdn.net/qq_41661809/article/details/81415687
這裏有算法實現的動圖->https://blog.csdn.net/qq_37969433/article/details/82947411

KMP算法要解決的問題就是在字符串(也叫主串S)中的模式(pattern)定位問題。說簡單點就是我們平時常說的關鍵字搜索。如果它在主串中出現,就返回它的具體位置,否則返回-1(常用手段)。

在暴力的解法中,我們從左至右一個個匹配模式串,失配就回溯到起點的下一位,然後重新匹配。但這種思路如果遇到這種情況:在主串“SSSSSSSSSSSSSA”中查找“SSSSB”,每次比較到最後一個才知道不匹配,然後回溯,這個的效率是顯然是最低的。(O(n*m))

(盜圖+1 (*゚∀゚)=3 )
在這裏插入圖片描述
像馬拉車算法中利用了迴文串左右對稱的特點一樣,kmp利用已經部分匹配這個有效信息,保持i指針不回溯,通過修改j指針,讓模式串儘量地移動到有效的位置,從而大幅度優化了最初暴力的思路。(因爲前面和模式串匹配過了,我們可以先處理模式串,然後直接判斷回溯到哪個位置最終纔可能匹配成功,不可能匹配成功的就不用回溯到那個位置了)

如上圖所示的應該是最容易理解的情況了,匹配到第3位時,發現A和E不匹配,但前面1~2的子串中都不含A,(模式串的首位字符是A)所以下一次可以直接從第3位開始重新匹配,如下圖:(j:3–>0)
在這裏插入圖片描述
當前面的子串中含有模式串的首字母呢?
在這裏插入圖片描述
如上圖,若此時失配,前面1~8的子串中,可以從3,4,6的位置開始匹配,但只有從位置6開始才能匹配到第8位(即第j-1位),最終纔有可能匹配成功。也就是說j回溯到的位置k要滿足P[0 ~ k-1] == P[j-k ~ j-1]。所以k是“ABCAABABC”的前綴串和後綴串相同時可以達到的最大長度
在這裏插入圖片描述
然後就剩下最後一個問題:怎麼求這些位置j對應的k,使之滿足P[0 ~ k-1] == P[j-k ~ j-1]。

next數組

通過上面的結論可以知道,下圖中next[15]=6,
在這裏插入圖片描述
1、當P[15]==P[6]時,
在這裏插入圖片描述
next[16]=7,
也就是說,當P[15]==P[next[15]]時,P[16]=P[15]+1
—————當p[jj]==p[next[jj]]時,next[jj+1]=next[jj]+1

2、當P[jj]!=P[next[jj]]時呢,如下圖P[16]='B’時,P[16]!=P[7]
在這裏插入圖片描述
或者說P[7]與P[16]失配了。P[7]失配,那就往前找:

在這裏插入圖片描述
突然想起之前一個dp題:Largest Rectangle in a Histogram(DP!),一直往回走,直到找到比它的高度低的矩形,但一直沒有理解爲什麼它們的時間複雜度是O(m+n)
在這裏插入圖片描述
求next數組代碼:

char s[manx],p[manx];
//s爲主串,p爲模式串,從0位開始存
int net[manx],ls,lp;
int getnext()
{
    int j=0,k=-1;
    net[0]=-1;
    //因爲每次找到符合條件的k值時,next[j+1]=k+1;但j最少可以回溯到0,所以net[0]要初始化爲-1
    while(j<lp-1)
    //最後處理的一個數是next[j+1],所以j循環上限爲lp-2(j是先加,再更新next[j]的值)
    {
    	//直到P[k]==P[j]時,得到P[j+1]的值
        if(k==-1||p[k]==p[j])
        {
            j++;
            k++;
            net[j]=k;
        }
        else
            k=net[k];
    }
}

next數組處理完後,就可以在i不回溯的情況下匹配模式串了:

int kmp()
{
    getnext();
    int i=0,j=0;
    while(j<lp&&i<ls)
    //j==lp表示寂靜匹配成功了
    //i==ls表示主串已經遍歷完了
    {
        if(j==-1||s[i]==p[j])
            i++,j++;
        else
            j=net[j];
    }
    if(j==lp) return i-lp;
    //看下圖就知道爲什麼是i-lp了
    else return -1;
}

在這裏插入圖片描述
例題:P3375 【模板】KMP字符串匹配
題目給出兩個字符串s1、s2,要求輸出s2在s1中所有出現的位置。並輸出子串的前綴數組 next。
代碼:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
//#define e 2.718281828459045
#define INF 0x3f3f3f3f
#define mid ((l + r)>>1)
#define chl root<<1
#define chr root<<1|1
using namespace std;
typedef unsigned long long ull;
typedef long long LL;
const int manx=1e6+10;
char s[manx],p[manx];
int net[manx],ls,lp;
void getnext()
{
    int j=0,k=-1;
    net[0]=-1;
    //處理到了next[lp],方便後面匹配出所有情況
    while(j<lp)
    {
        if(k==-1||p[k]==p[j])
            net[++j]=++k;
        else
            k=net[k];
    }
}
void kmp()
{
    getnext();
    int i=0,j=0;
    //因爲要找到所有位置,所以不用加j<lp的條件
    while(i<ls)
    {
        if(j==-1||s[i]==p[j])
            i++,j++;
        else
            j=net[j];
        if(j==lp)
            printf("%d\n",i-j+1);
    }
}
int main()
{
    scanf("%s%s",s,p);
    ls=strlen(s);
    lp=strlen(p);
    kmp();
    for(int i=1;i<lp;i++)
        printf("%d ",net[i]);
    printf("%d\n",net[lp]);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章