2016-8-6夏令营总结(kmp,回文串,扩展kmp)

今天我们用了一个上午学习了字符串中的kmp,最长回文子串和扩展kmp算法,下午在编相应的裸题。

KMP算法

我们有一个长度为n的字符串S,长度为m的字符串T,问T在S里出现了几次?
这题是我们平时最经常遇到的字符串问题。这题暴力搜索的话要o(nm)。(当然用hash只需要o(n))我们暴力搜索时会枚举S每一位为开头,然后比较S[i…i+m-1[与T[1…m]为匹配,之后又继续往后推一位后再次匹配。(如图)
《2016-8-6夏令营总结(kmp,回文串,扩展kmp)》

我们发现,其实我们在第一次往后移时,S[2]对应下来的TT[1]时重复匹配了的。因为我们一开始时(蓝色)匹配失败后已经得知S[1…2]与T[1…2]时对应匹配的,而T[2]又和T[1]互相匹配,那么就可以知道T[1]=T[2]=S[2],所以第一次后移(红色)时,T[1]死不需要匹配的,那么我们怎么必变这种重复比较匹配的情况呢?
我们发现,我们从一开始和第一次往后移的两个字符串的室友重复部分的T1[2]=T2[1],所以我们每次在T与S匹配失败后,就直接找到从开头到成功匹配部分的最后一位这一段与下一次往移的字符串的连续成功匹配部分,然后直接跳到下次可能匹配到的地方。
那么我们怎么找到下次应该跳到的地方呢?我们可以用一个next数组存下来。计算next数组时,我们可以像匹配S与T那样,直接跳到之前已经计算过得next数组,然后从之前匹配到的地方往下继续匹配。

KMP代码:(下表从0开始):

#include <iostream>
#include <fstream>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;

int T;
char s[1000007],t[10007];
int next[10007];
int m,n;

void _read()
{
    scanf("%s",t);
    m=strlen(t);
    scanf("%s",s);
    n=strlen(s);
}

void makenext()     //计算next 
{
    next[0]=-1;
    int k=-1;
    for(int i=1;i<m;i++)
    {
        while(k>=0 && t[i]!=t[k+1]) k=next[k];
        if(t[i]==t[k+1]) k++;
        next[i]=k;
    }
}

int _get()          //T与S匹配 
{
    int k=-1;
    int ans=0;
    for(int i=0;i<n;i++)
    {
        while(k>=0 && s[i]!=t[k+1]) k=next[k];
        if(s[i]==t[k+1]) k++;
        if(k==m-1) ans++,k=next[k]; 
    }
    return ans;
}

int main()
{
    scanf("%d",&T);
    for(;T>0;T--)
    {
        _read();
        memset(next,0,sizeof(next));
        makenext();
        cout<<_get()<<endl;
    }


    return 0;
}

manacher(最长回文子串)

给定一个长度为n字符串S,现在要从中找出一个回文的子串T。字符串A是回文的,当且仅当A反转后的A’和A完全相等。问T可能的最大长度?
回文串也是我们在字符串中经常遇到的问题。
暴力做法1:枚举所有子串的开头和结尾,然后判断回文——o(n^3)
暴力做法2:枚举回文串的中间点,然后向两边延伸,同时判断是否回文——o(n^2)
我们从暴力做法2中,其实就能看出回文串的性质:回文串对称中心的两边是完全对称的。这就意味着一个回文串被包含在另一个大回文串中,那么它的相对于大的回文串的对称串也会是一个回文串。这样我们就可以尽量减少时间复杂度。(如图)
《2016-8-6夏令营总结(kmp,回文串,扩展kmp)》
我们发现,找到对称后有三种情况。

  1. 像在s1中包含着对称的两个回文串s2,而且s2的左右两边的字母都还是在回文串的范围内那么s2所包含的范围就是它对称串那么多。
  2. 像在s3中的s4,因为s4有一边在回文串的边界上,所以s4要尝试向边界突破,变成s5。
  3. 像在s5中的s2,因为s2有一部分是在s5的外面,所以s2关于s5的对称串的范围要缩短到s5的边界上。
    所以我们分析出所有情况后,可以推出算法。
    首先从左到右枚举对称中心,然后看一下关于之前的最右端伸得最远的回文串的对称串似乎否对称。①如果对称穿超出最远回文串的范围,那么现在扫到的回文串就是能是先定为到最远回文串的范围,然后向最右端突破;②如果对称串没有达到范围,那么当前回文串范围就是对称穿的范围;③如果对称串的范围刚好达到了最远回文串范围,那么当前回文串就先定为到最远回文串范围,然后向最右端突破。如果突破了最右端,那么就把之前最右端的对称中心改为当前对称中心,然后继续向右扫。
    但是因为我们枚举的是中间点,所以出现回文串是偶数的情况。那么我们就可以在这一大串字符串中每隔一个字符就插入一个原串没有出现过的字符,这样就可以避免出现偶数串的情况。

manacher代码:

#include <iostream>
#include <fstream>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;


char c[110007];
char s[2*110007];
int ans[2*110007];
int slen;

int main()
{
    while(EOF!=scanf("%s",c))
    {
        memset(ans,0,sizeof(ans));
        int clen=strlen(c);
        if(clen==1)
        {
            cout<<1<<endl;
            continue;
        }
        slen=0;
        for(int i=0;i<clen;i++)     //插入避免偶数 
        {
            slen++;s[slen]='#';
            slen++;s[slen]=c[i];
        }
        s[++slen]='#';s[++slen]='$';
        s[0]='@';
        ans[0]=ans[1]=ans[2]=0;
        int p=2;
        for(int i=3;i<slen;i++)     //p为最远串的对称中心
                                    //ans表示回文串半径 
        {
            ans[i]=max(0,min(ans[p*2-i],p+ans[p]-i));   //判断情况 
            while(s[i-ans[i]]==s[i+ans[i]]) ans[i]++;   //突破 
            if(ans[i]+i>ans[p]+p) p=i;                  //更新 
        }
        int maxp=2;
        for(int i=2;i<slen-1;i++)       //求最长的串 
            if(ans[maxp]<ans[i]) maxp=i;
        cout<<ans[maxp]-1<<endl;
    }



    return 0;
}
    原文作者:KMP算法
    原文地址: https://blog.csdn.net/zhouzhenghuac/article/details/52138863
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞