1. 程式人生 > >字串的匹配 KMP演算法分析

字串的匹配 KMP演算法分析

圖片來源於  土豆洋芋山藥蛋 https://blog.csdn.net/qq_33414271/article/details/83789478

1.什麼是KMP演算法?

在主串Str中查詢模式串Pattern的方法中,有一種方式叫KMP演算法

KMP演算法是在模式串字元與主串字元匹配失配時,利用已經匹配的模式串字元子集的最大塊對稱性,讓模式串儘量後移的演算法。

2. 暴力匹配演算法

假設現在我們面臨這樣一個問題:有一個文字串S,和一個模式串P,現在要查詢P在S中的位置,怎麼查詢呢?

如果用暴力匹配的思路,並假設現在文字串S匹配到 i 位置,模式串P匹配到 j 位置,則有:

如果當前字元匹配成功(即S[i] == P[j]),則i++,j++,繼續匹配下一個字元;
如果失配(即S[i]! = P[j]),令i = i - (j - 1),j = 0。相當於每次匹配失敗時,i 回溯,j 被置為0。

程式碼如下:

#include<bits/stdc++.h>
#include<algorithm>
#include<math.h>
#include<vector>
#define inf 1e6
using namespace std;
char s[1000],t[1000];
int ViolentPipei(char *s,char *t)    //   暴力匹配
{
    int slen=strlen(s);
    int tlen=strlen(t);
    int i=0,j=0;
    while(i<slen&&j<tlen)
    {
        if(s[i]==t[j])
            i++,j++;
        else
            i=i-j+1,j=0;
    }
    if(j==tlen)
        return i-j;
    else
        return -1;
}
int main()
{
    cin>>s>>t;
    cout<<ViolentPipei(s,t)<<endl;
    return 0;
}

 舉個例子

如果給定文字串S“BBC ABCDAB ABCDABCDABDE”,和模式串P“ABCDABD”,現在要拿模式串P去跟文字串S匹配,整個過程如下所示:

    1. S[0]為B,P[0]為A,不匹配,執行第②條指令:“如果失配(即S[i]! = P[j]),令i = i - (j - 1),j = 0”,S[1]跟P[0]匹配,相當於模式串要往右移動一位(i=1,j=0)


    2. S[1]跟P[0]還是不匹配,繼續執行第②條指令:“如果失配(即S[i]! = P[j]),令i = i - (j - 1),j = 0”,S[2]跟P[0]匹配(i=2,j=0),從而模式串不斷的向右移動一位(不斷的執行“令i = i - (j - 1),j = 0”,i從2變到4,j一直為0)

    3. 直到S[4]跟P[0]匹配成功(i=4,j=0),此時按照上面的暴力匹配演算法的思路,轉而執行第①條指令:“如果當前字元匹配成功(即S[i] == P[j]),則i++,j++”,可得S[i]為S[5],P[j]為P[1],即接下來S[5]跟P[1]匹配(i=5,j=1)

    4. S[5]跟P[1]匹配成功,繼續執行第①條指令:“如果當前字元匹配成功(即S[i] == P[j]),則i++,j++”,得到S[6]跟P[2]匹配(i=6,j=2),如此進行下去

    5. 直到S[10]為空格字元,P[6]為字元D(i=10,j=6),因為不匹配,重新執行第②條指令:“如果失配(即S[i]! = P[j]),令i = i - (j - 1),j = 0”,相當於S[5]跟P[0]匹配(i=5,j=0)

    6. 至此,我們可以看到,如果按照暴力匹配演算法的思路,儘管之前文字串和模式串已經分別匹配到了S[9]、P[5],但因為S[10]跟P[6]不匹配,所以文字串回溯到S[5],模式串回溯到P[0],從而讓S[5]跟P[0]匹配。

    而S[5]肯定跟P[0]失配。為什麼呢?因為在之前第4步匹配中,我們已經得知S[5] = P[1] = B,而P[0] = A,即P[1] != P[0],故S[5]必定不等於P[0],所以回溯過去必然會導致失配。那有沒有一種演算法,讓i 不往回退,只需要移動j 即可呢?

    答案是肯定的。這種演算法就是本文的主旨KMP演算法,它利用之前已經部分匹配這個有效資訊,保持i 不回溯,通過修改j 的位置,讓模式串儘量地移動到有效的位置。

3. KMP演算法
   

      3.1 定義


    Knuth-Morris-Pratt 字串查詢演算法,簡稱為 “KMP演算法”,常用於在一個文字串S內查詢一個模式串P 的出現位置,這個演算法由Donald Knuth、Vaughan Pratt、James H. Morris三人於1977年聯合發表,故取這3人的姓氏命名此演算法。

假設現在文字串S匹配到 i 位置,模式串P匹配到 j 位置
如果j = -1,或者當前字元匹配成功(即S[i] == P[j]),都令i++,j++,繼續匹配下一個字元;
如果j != -1,且當前字元匹配失敗(即S[i] != P[j]),則令 i 不變,j = next[j]。此舉意味著失配時,模式串P相對於文字串S向右移動了j - next [j] 位。
換言之,當匹配失敗時,模式串向右移動的位數為:失配字元所在位置 - 失配字元對應的next 值(next 陣列的求解會在下文的3.3.3節中詳細闡述),即移動的實際位數為:j - next[j],且此值大於等於1。
    很快,你也會意識到next 陣列各值的含義:代表當前字元之前的字串中,有多大長度的相同字首字尾。例如如果next [j] = k,代表j 之前的字串中有最大長度為k 的相同字首字尾。

    此也意味著在某個字元失配時,該字元對應的next 值會告訴你下一步匹配中,模式串應該跳到哪個位置(跳到next [j] 的位置)。如果next [j] 等於0或-1,則跳到模式串的開頭字元,若next [j] = k 且 k > 0,代表下次匹配跳到j 之前的某個字元,而不是跳到開頭,且具體跳過了k 個字元。

程式碼為:

int KmpSearch(char* s, char* p)
{
    int i = 0;
    int j = 0;
    int sLen = strlen(s);
    int pLen = strlen(p);
    while (i < sLen && j < pLen)
    {
        //①如果j = -1,或者當前字元匹配成功(即S[i] == P[j]),都令i++,j++    
        if (j == -1 || s[i] == p[j])
        {
            i++;
            j++;
        }
        else
        {
            //②如果j != -1,且當前字元匹配失敗(即S[i] != P[j]),則令 i 不變,j = next[j]    
            //next[j]即為j所對應的next值      
            j = next[j];
        }
    }
    if (j == pLen)
        return i - j;
    else
        return -1;
}

.........綜上所述,得到next()陣列程式碼:

void GetNext(char* p,int next[])
{
    int pLen = strlen(p);
    next[0] = -1;
    int k = -1;
    int j = 0;
    while (j < pLen - 1)
    {
        //p[k]表示字首,p[j]表示字尾
        if (k == -1 || p[j] == p[k]) 
        {
            ++k;
            ++j;
            next[j] = k;
        }
        else 
        {
            k = next[k];
        }
    }
}

修改後的next()陣列程式碼:

//優化過後的next 陣列求法
void GetNextval(char* p, int next[])
{
    int pLen = strlen(p);
    next[0] = -1;
    int k = -1;
    int j = 0;
    while (j < pLen - 1)
    {
        //p[k]表示字首,p[j]表示字尾  
        if (k == -1 || p[j] == p[k])
        {
            ++j;
            ++k;
            //較之前next陣列求法,改動在下面4行
            if (p[j] != p[k])
                next[j] = k;   //之前只有這一行
            else
                //因為不能出現p[j] = p[ next[j ]],所以當出現時需要繼續遞迴,k = next[k] = next[next[k]]
                next[j] = next[k];
        }
        else
        {
            k = next[k];
        }
    }
}
............KMP演算法如下:

int KmpSearch(char* s, char* p)
{
    int i = 0;
    int j = 0;
    int sLen = strlen(s);
    int pLen = strlen(p);
    while (i < sLen && j < pLen)
    {
        //①如果j = -1,或者當前字元匹配成功(即S[i] == P[j]),都令i++,j++    
        if (j == -1 || s[i] == p[j])
        {
            i++;
            j++;
        }
        else
        {
            //②如果j != -1,且當前字元匹配失敗(即S[i] != P[j]),則令 i 不變,j = next[j]    
            //next[j]即為j所對應的next值      
            j = next[j];
        }
    }
    if (j == pLen)
        return i - j;
    else
        return -1;
}
此總結多數轉自於下面的部落格,詳細解答KMP演算法請看下面的部落格!!!!!
原文:https://blog.csdn.net/v_july_v/article/details/7041827