1. 程式人生 > >HDU oj 1711 Number Sequence(KMP入門)

HDU oj 1711 Number Sequence(KMP入門)

KMP

原理

KMP演算法是一種改進的字串匹配演算法,由D.E.KnuthJ.H.MorrisV.R.Pratt同時發現,因此人們稱它為克努特——莫里斯——普拉特操作(簡稱KMP演算法)。KMP演算法的關鍵是利用匹配失敗後的資訊,儘量減少模式串與主串的匹配次數以達到快速匹配的目的。具體實現就是實現一個next()函式,函式本身包含了模式串的區域性匹配資訊。時間複雜度O(m+n)

當匹配過程中,主串中第i個字元與模式中第j個字元比較不等時,僅需將模式向右滑動至模式中第k個字元和主串中第i個字元對齊,此時,模式中頭k-1個字元的子串‘ p[1]p[2]…p[k-1]’必定與主串中第i個字元之前長度為k-1的子串‘ s[i-k+1]s[i-k+2]…s[i-1]’相等,由此,匹配僅需從模式中第k個字元與主串中第i個字元比較起繼續進行。這就是KMP演算法,不懂的話可以看下面連結裡的動圖,博主很用心的演示了KMP的匹配方式

點選開啟連結

在寫KMP的時候,最重要的就是next陣列的變化賦值,這是KMP成不成功的關鍵所在。

Next陣列:

next[i]i1開始算)代表著,除去第i個數,在一個字串裡面從第一個數到第(i-1)字串字首與字尾最長重複的個數

構造next陣列使用的基本方法是遞推,要計算當前第i位字元的next值即計算從字串開始至第i位前(不包括第i)的字串的最長字首與字尾重複數量。

規定第0(第一個字元)next值是0-1(有不同構造next函式的方法)

i+1位的next值分兩種情況討論:

①當p[i]=p[j]時,字首和字尾相等,最大長度可以延續因此next[++i]=next[++j]

②當p[i

]p[j]時,字首和字尾匹配到此不等,那麼可以利用已計算好的next值,將j回溯,直至找到可匹配的重複字首字尾。也就是j=next[j]

我們用例項看一下next陣列存的值


模式串為a時,無字首字尾所以next陣列為0.

模式串為ab時字首為a字尾為b,不相等,就是0

模式串aba時字首a和字尾a相等為1,還有一個字首為ab,一個字尾為ba,因為兩者不相等,所以next陣列不發生變化。

模式串為abacab時,就是2了,字首為ab字尾也為ab,但當前綴為aba時後綴為cab,不相等,所以不發生變化。

程式碼實現:

void Get_Next()
{
    int j, k;
    j = 0;
    k = -1;
    nextt[0] = -1;  ///我們這next陣列0代表沒有能匹配的字首和字尾
    ///如果next陣列等於1代表模式串字首字尾有一個相等
    while(j < tlen)  ///t為模式串的長度
    {
        if(k == -1 || T[j] == T[k])
        {
            nextt[++j] = ++k; ///給next陣列賦值
        }
        else
        {
            k = nextt[k];///回溯
        }
    }
}

KMP兩大操作

1)查詢模式串T在主串S中首次出現的位置

2)查詢模式串T在主串S中出現的次數

這兩大操作都是建立在next陣列的基礎上的

1)

int KMP_Index()
{
    int i = 0, j = 0;
    Get_Next();

    while(i < slen && j < tlen)///KMP是移動模式串和主串比較
    {
        if(j == -1 || S[i] == T[j])

        {
            i++;
            j++;  ///在主串中和模式串比較
        }
        else
        {
            j = nextt[j];  ///j的回溯
        }
    }
    if(j == tlen)  ///如果在S中找到完全匹配的串
    {
        return i - tlen;  ///返回開始匹配的位置(從0開始)根據題目要求返回,從0開始或從1開始
    }
    else
    {
        return -1;///否則沒找到,返回-1
    }
}

2)

int KMP_Count()
{
    int ans = 0; 
    int i, j = 0;

    if(slen == 1 && tlen == 1)///考慮特殊情況,模式串和主串長度都為1
    {
        if(S[0] == T[0])
            return 1;
        else
            return 0;
    }
    Get_Next();
    for(i = 0; i < slen; i++)///完全遍歷主串
    {
        while(j > 0 && S[i] != T[j])///如果不匹配就回溯,即相當於移動模式串直到找到下一個匹配點為止
            j = nextt[j];
        if(S[i] == T[j])
            j++;
        if(j == tlen) ///找到一個匹配
        {
            ans++;
            j = nextt[j];///回溯,找下一個匹配
        }
    }
    return ans;
}

這樣,我們的KMP就大體上明白了。下面我們來做一道題運用一下

實戰練習

HDU oj  1711 Number Sequence點選開啟連結

題目大意:

要求找模式串在主串中出現的第一個位置,因為是陣列,所以我們就只要稍微的改一點點就行了

思路:

很簡單的一道模板題,套模板就好了

話不多說直接上程式碼

AC程式碼:

#include<stdio.h>
#define maxn 1000010
#define maxa 10005
int a[maxn],b[maxa];
int nextt[maxa];
int n,m;
void Get_Next()
{
    int j,k;
    j=0;
    k=-1;
    nextt[0]=-1;
    while(j<m)
    {
        if(k==-1||b[j]==b[k])
          nextt[++j]=++k;
        else k=nextt[k];
    }
}

int KMP_Index()
{
    int i=0,j=0;
    Get_Next();
    while(i<n && j<m)
    {
        if(j==-1||a[i]==b[j])
        {
            i++;
            j++;
        }
        else j=nextt[j];

    }
    if(j==m) return i-m+1;
    else return -1;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        for(int i=0;i<n;i++)
          scanf("%d",&a[i]);
        for(int i=0;i<m;i++)
          scanf("%d",&b[i]);
        printf("%d\n",KMP_Index());
    }
    return 0;
}