您的位置:首页 > 其它

字符串单模板匹配学习笔记(一)kmp算法

2015-12-01 00:56 441 查看
数据结构课上学到了kmp算法,顺便就深入的学习一下相关的单模板匹配问题。为之后学习ac自动机和后缀数组等字符串算法做一个铺垫。

关于单模板匹配问题参考了:

《字符串匹配算法总结》

http://blog.csdn.net/WINCOL/article/details/4795369

【kmp】

作为经典字符串匹配算法,kmp有相当广泛的应用。所以虽然比较难以理解,并且实际效率可能不如一些字符串单模板匹配算法,却仍然是需要完全掌握,理解其操作过程的一个重要算法。

我认为,整个算法的核心在next数组的求取上。虽然匹配过程不如Sunday算法简洁高效,但是next数组的实现过程是值得研究,借鉴的。这也使得kmp算法有着更广阔的应用空间。、

那么问题来了,什么是next数组?

0...i这个串的前next[i]个字符和后next[i]个字符完全匹配。
保存这样的信息的数组叫做next数组


next的实现过程,网上很多blog往往直接跳过(私以为是舍本逐末)。下面的文章详细分析了next的实现过程,可以帮助初学者理解next数组是如何实现的:

《字符串匹配的KMP算法》-阮一峰

http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html

《【经典算法】——KMP,深入讲解next数组的求解 》

http://www.cnblogs.com/c-cloud/p/3224788.html

当我们求得next数组之后,kmp的匹配过程就没有什么难点了。

匹配过程如下:
对于匹配串S,如果在q点失配,模板串已匹配了k-1个字符。
我们想让q点匹配,所以需要在模板串里找一个结点来匹配q点。
因为模板串0..k-1的前next[k-1]个字符和后next[k-1]个字符匹配,所以我们直接将串移动k-1-next[k-1]个位置,检测q点是否可以匹配。
一直重复这个过程即可。


求next数组的代码:

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

char T[1000];
int nxt[1000];

void get_next()
{
int len_T=strlen(T);
int k=0;
nxt[0]=0;
for( int q=1; q<len_T; q++  )
{
while(  k>0 && T[q]!=T[k] )k=nxt[k-1];

if(T[q]==T[k])
{
k++;
}
nxt[q]=k;
}
}

int main()
{
cin>>T;
get_next();
for(   int i=0; i<strlen(T); i++ )
{
cout<<i<<"  "<<nxt[i]<<endl;
}

return 0;
}


除了单模板匹配,next数组还可以用来判循环节。下面是一道典型例题。

poj2406.

http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=10758

想想next数组如何应用在这里?

或者说如何来求出最小循环节?

这点是值得思考的。结论放在代码里。

【参考代码】

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

char T[1000000+10];
int nxt[1000000+10];

int get_nxt(  int len )
{
nxt[0]=0;
for(  int q=1,k=0; q<len; q++ )
{
while(  k>0 && T[q]!=T[k] )k=nxt[k-1];
if(  T[q]==T[k] )k++;
nxt[q]=k;
}

if(  len%( len-nxt[len-1] )==0 )return len/(len-nxt[len-1]);
else return 1;
}

int main()
{
while( ~scanf(  "%s",&T )    )
{
int len=strlen(T);
if(   (len==1) && T[0]=='.'   )break;
printf(  "%d\n", get_nxt(len) );
// for(  int i=0; i<len; i++ )cout<<i<<"  "<<nxt[i]<<endl;
}

return 0;
}

/*
poj 2406 判循环节
当它是一个a^n形式的时候, 设t=len-next[len-1]
若 len%t==0; 则t是它的最小循环节长度。

证明:
设a^n=AAAAA...A(n个A)  //A是最小循环节
那么必有前缀  AAAA....A(n-1个A)和后缀AAAA...A ( n-1个A )相同。

所以最小循环节长度为 len-next[len-1]=t,此时必有len%t==0.

若p则q <=> 若!q则!p,所以我们可以得出结论:
若len%t!=0,则t不是a^n的最小循环节

现在我们证,当len%t==0的时候t是它的最小循环长度
令A为前t个字节
设前缀=ABC...
后缀=      A'B'C'.....
由next定义知A=A'
所以前缀可以表述为AABC......
因为后缀和前缀匹配,所以有后缀可以表述为A'A'......
依次类推得到A......A这样一个串,
所以A是最小循环节
*/


再来做一做其它求(判)循环节的例题:

poj1961

http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=10934

poj2752(理解了next的实现过程才能做这道题目):

http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=10055

hdu3746(最小覆盖子串)

http://acm.hdu.edu.cn/showproblem.php?pid=3746

理解题意理解了好久….我误以为会出现abcabcde这样的串,实际上串珠子的规则是将一个color序列循环至少两次,这保证了给定的初始序列一定是某个串循环了i次加上它的前j个珠子。那么这就变成了一道水题。

利用前面例题的结论即可做。

下面是代码:

【poj1961】

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

char T[1000000+10];
int nxt[1000000+10];
int len;

void get_nxt(  int len )
{
nxt[0]=0;
for(  int q=1,k=0; q<len; q++ )
{
while(  k>0 && T[q]!=T[k] )k=nxt[k-1];
if(  T[q]==T[k] )k++;
nxt[q]=k;
}
}

int main()
{
int kase=0;
while( ~scanf("%d",&len)  && len  )
{
printf(  "Test case #%d\n",++kase );
scanf("%s",&T);
get_nxt( len );
for(  int i=1; i<len; i++ )
{
int t=i+1-nxt[i];
if(  ((i+1)%t==0)  && (t!=(i+1))  ) printf(  "%d %d\n", i+1, (i+1)/t );
}
puts("");
}

return 0;
}


【poj2752】

#include<iostream>
#include<cstdio>
#include<cstring>
#include<stack>
using namespace std;

char T[400000+10];
int nxt[400000+10];
int len;

void get_nxt(  int len )
{
nxt[0]=0;
for(  int q=1,k=0; q<len; q++ )
{
while(  k>0 && T[q]!=T[k] )k=nxt[k-1];
if(  T[q]==T[k] )k++;
nxt[q]=k;
}
}

int main()
{
int kase=0;
while( ~scanf("%s",&T)    )
{
int len=strlen(T);
get_nxt( len );

stack<int>sta;
int k=len-1;
// for(  int i=0; i<len; i++ )cout<<i<<"  "<<nxt[i]<<endl;

for( int k=len-1; k>=0; k=nxt[k]-1  )
{
sta.push(k);
}

while( !sta.empty() )
{
printf( "%d ",sta.top()+1 );
sta.pop();
}
puts("");
}

return 0;
}


【hdu3746】

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

const int maxn=100000+50;

char S[maxn];
int nxt[maxn];

void get_nxt(  int L  )
{
nxt[0]=0;
int k=0;
for( int i=1; i<L; i++ )
{
while(  S[i]!=S[k]  && k>0  ) k=nxt[k-1];
if( S[i]==S[k] )++k;
nxt[i]=k;
}
}

int main()
{
int T;
cin>>T;
while( T-- )
{
scanf("%s",&S);
int len=strlen(S);
get_nxt(len);
int t=len-nxt[len-1];
if(  len%t==0 && len!=t  )printf("%d\n",0);
else
{
printf("%d\n",t-len%t);
}

}

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