您的位置:首页 > 其它

字符串算法(KMP+MANACHER+EX_KMP)总结

2016-08-08 19:02 477 查看
字符串算法:

1、 KMP算法

2、 MANACHER算法

3、 EX_KMP算法

KMP算法代码:

求Next数组源代码:

next[0]=next[1]=0; k=0;
for (i=2;i<=m;i++)
{ while (k>0&&T[i]!=T[k+1]) k=next[k];
if (T[i]==T[k+1]) k++;
next[i]=k;
}


KMP算法匹配源代码:

k=0; //k表示S串当前位匹配到T串第k位
for (i=1;i<=n;i++)
{
while (k>0&&S[i]!=T[k+1]) k=next[k];  //跳到可匹配处
if (S[i]==T[k+1]) k++;
if (k==m) { k=next[k]; ans++; }
} //ans表示出现次数


MANACHER算法代码:

ans[0]=ans[1]=0; p=1;
for (i=2;i<=n;i++) //枚举回文中点
{ ans[i]=max(0, min(ans[2*p-i],p+ans[p]-i) );  //不超出最右端就直接取
while (S[i-ans[i]]==S[i+ans[i]]) ans[i]++; //暴力扩展
if (i+ans[i]>p+ans[p]) p=i; } //更新最右断点


EX_KMP算法代码:(与MANACHER相似)

计算Next数组源代码:

next[0]=m;
for (i=0;T[i]==T[i+1];i++);
next[1]=i; p=1;
for (i=2;i<m;i++) {
u=p+next[p]; //u为最右端位置
if (i+next[i-p]<u) next[i]=next[i-p];//如果答案可以直接取
else { //如果答案需要探索
for (j=max(u-i,0);T[i+j]==T[j];j++);
next[i]=j; p=i;
}
}


与S串匹配源代码:

for (i=0;i<m&&S[i]==T[i];i++);
ex[0]=i; p=0;
for (i=1;i<n;i++) {
u=p+ex[p]; //u为最右端位置
if (i+next[i-p]<u) ex[i]=next[i-p];  //如果答案可以直接取得
else { //如果答案需要探索
for (j=max(u-i,0);j<=m&&S[i+j]==T[j];j++);
ex[i]=j; p=i;
}
}


字符串读入:

一般情况使用KMP、MANACHER算法前,字符串从STRING[1]开始记录,EX_KMP从STRING[0]开始记录。

KMP的应用:

1、 求字符串T在S中出现的次数

poj3461 Oulipo

做法:直接用KMP进行匹配。

2、求字符串中的循环节



poj2185 Milking Grid

做法:行方向和列方向分别找出最小循环节长R和C,面积S就是R*C。

HDU3746 Cyclic Nacklace

做法:

若有完整的循环节如图中例1,则无需添加。

否则就有残缺的循环节如图中例2,则求出补齐所需的花费。

具体代码片段如下:

int cir=Len-next[Len]; //循环节长
if (Len%cir==0&&cir!=Len) //是否完整
printf("0\n");
else    printf("%d\n",cir-Len%cir);


MANACHER的应用

求回文长度

两种情况:

1、如字符串ababac 最长回文就是以S[3]为中点,长度为5的回文串。

2、如字符串babbac 最长回文就是以S[3]和S[4]两个字符为中点,长度为4的回文串。

情况2可以通过预处理转换为情况1。方法:每两个字符间插入一个从未在原串出现过的相同字符,如’#’、’|’、’*’…同时为了防止数组越界,头尾分别加入两个不同的奇怪字符。

用此方法处理的字符串babbac为 @#b#a#b#b#a#c#!

Len=Len*2+3

回文长度为ans[i]-1 (不包括添加的字符,可证明)

HDU3068 最长回文

做法:直接做MANACHER。

HDU3613 Best Reward

做法:做MANACHER同时判断,若回文串包括左端点,则记录pl[ans[i]-1]=true 表示前ans[i]-1个字符组成回文串。 若包括右端点,则记录pr[ans[i]-1]=true 表示后ans[i]-1个字符组成回文串。

具体代码片段如下:

if (i-ans[i]==1) pl[ans[i]-1]=1;
if (i+ans[i]==Len) pr[ans[i]-1]=1;


EX_KMP的应用

给出一个长度n的字符串S[0..n-1]

和一个长度m的字符串T[0..n-1]

问S的哪个后缀和T具有最长的公共前缀

HDU4333 Revolving Digits

做法:可以把数字再复制一遍,用EX_KMP匹配,求出每一个后缀与原数相同的数字个数Next[i],如果大于原长则相同,否则比较下一个不同的数字。

注意:要求比较的是不同的数字,如果有循环节,则需要除去循环节个数(KMP)。

附三个完整模板:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int kmp()
{
char t[100],s[100]; scanf("%s%s",t+1,s+1);
int next[100],ans=0,k;

int len1=strlen(t+1),len2=strlen(s+1);

next[0]=next[1]=k=0;

for (int i=2;i<=len1;i++)
{
while (k>0&&t[k+1]!=t[i]) k=next[k];
if (t[k+1]==t[i]) k++;
next[i]=k;
}

k=0;
for (int i=1;i<=len2;i++)
{
while (k>0&&t[k+1]!=s[i]) k=next[k];
if (t[k+1]==s[i]) k++;
if (k==len1) {
ans++; k=next[k];
}
}
printf("%d",ans);
return 0;
}

int manacher()
{
char t[100],s[100]; scanf("%s",t+1);
int len=2; s[1]='@'; s[2]='#';
for (int i=1;i<strlen(t);i++)
{
s[++len]=t[i]; s[++len]='#';
} s[++len]='!';

int ans[100],p=1; memset(ans,0,sizeof(ans));

for (int i=2;i<=len;i++)
{
ans[i]=max(0,min(ans[2*p-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;
}
for (int i=2;i<=len-1;i++)  printf("%c%d ",s[i],ans[i]-1);

return 0;
}

int Ex_kmp()
{
char t[100],s[100]; scanf("%s%s",t,s);
int next[100],ex[100];
int len1=strlen(t),len2=strlen(s);

next[0]=len1;
for (next[1]=0;t[next[1]]==t[next[1]+1];next[1]++);

int p=1;
for (int i=2;i<len1;i++)
{
int u=p+next[p];
if (i+next[i-p]<u) next[i]=next[i-p];
else {
int j;
for (j=max(u-i,0);t[i+j]==t[j];j++);
next[i]=j; p=i;
}
}

int i;
for (i=0;i<=len1&&s[i]==t[i];i++);
ex[0]=i; p=0;
for (i=1;i<len2;i++)
{
int u=p+ex[p];
if (i+next[i-p]<u) ex[i]=next[i-p];
else {
int j;
for (j=max(u-i,0);t[j]==s[i+j];j++);
ex[i]=j; p=i;
}
}

for (int i=0;i<len2;i++) printf("%d ",ex[i]);

return 0;
}

int main()
{
//kmp();
//manacher();
//Ex_kmp();
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  字符串