字符串单模板匹配学习笔记(一)kmp算法
2015-12-01 00:56
441 查看
数据结构课上学到了kmp算法,顺便就深入的学习一下相关的单模板匹配问题。为之后学习ac自动机和后缀数组等字符串算法做一个铺垫。
关于单模板匹配问题参考了:
《字符串匹配算法总结》
http://blog.csdn.net/WINCOL/article/details/4795369
【kmp】
作为经典字符串匹配算法,kmp有相当广泛的应用。所以虽然比较难以理解,并且实际效率可能不如一些字符串单模板匹配算法,却仍然是需要完全掌握,理解其操作过程的一个重要算法。
我认为,整个算法的核心在next数组的求取上。虽然匹配过程不如Sunday算法简洁高效,但是next数组的实现过程是值得研究,借鉴的。这也使得kmp算法有着更广阔的应用空间。、
那么问题来了,什么是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的匹配过程就没有什么难点了。
求next数组的代码:
除了单模板匹配,next数组还可以用来判循环节。下面是一道典型例题。
poj2406.
http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=10758
想想next数组如何应用在这里?
或者说如何来求出最小循环节?
这点是值得思考的。结论放在代码里。
【参考代码】
再来做一做其它求(判)循环节的例题:
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】
【poj2752】
【hdu3746】
关于单模板匹配问题参考了:
《字符串匹配算法总结》
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算法的C#实现方法
- JavaScript中数据结构与算法(五):经典KMP算法
- 字符串算法--KMP--Java实现
- KMP算法(转载)
- kmp算法实现
- KMP算法详解
- linux kernel data struct: KMP算法实现
- 求一个字符串中连续出现次数最多的子串
- KMP算法总结
- KMP算法
- KMP字符串匹配算法
- 字符串匹配之KMP算法
- kmp
- 【hiho一下第三周】KMP计算模式串在原串出现次数
- HDU1711 模板题-KMP
- HDU1358:Period
- hdu1711
- POJ 2406 Power Strings
- KMP next[]数组
- kmp 学习 hihocoder #1015