初等字符串匹配专题小结[KMP][Manacher][Tire Tree][AC Automation]
来源:互联网 发布:mac没有flash怎么办 编辑:程序博客网 时间:2024/06/03 04:38
刷了3天的字符串匹配题。
为了下面继续切题,小的先小结一些。
字符串匹配的最基础算法是枚举(n^2)。
高深一点的是KMP。
KMP在数据结构课上学过,由于老师只是负责教学,不负责解答他不懂的问题,于是KMP就这么被我搁置一边了。ACM这么多年了,一直不懂这些基础的算法,实在有愧与心。
于是乎专程学习了一下KMP。
先说说KMP的主要思想。KMP用于模式串的匹配。
下面看看一个字符串:(1)AACAACAAB;
我们需要查找的串为:(2)AACAAB;
首先顺序匹配:
(1)AACAACAAB
(2)AACAA
到这里都顺利匹配上了,我们肉眼观察当(2)继续匹配时,'B'和'C'是不匹配的。那么怎样滑动呢?
对于串(2)我们可以发现'B'之前的字符'AA',与串(1)的'C'前面的两个字符是一样的,那么可以这么滑动....
(1)AACAACAAB
(2) AA
然后继续匹配发现完全匹配了......
好了那么怎么滑动呢?构建一个next数组,记录滑动下标。
可以用一句话来说明:
在J字符的左边有[0,I-1]与[J-I-1,J-1]相同的话,下次J失配时,就可以滑动到I。
因为是滑动到J才失配,也就是说,J之前的所有字符串都是和主串相匹配的。由此,只要在本串中找到前缀和主串相匹配的(一定是部分匹配)选择滑动就可以了。
下面是构建next的函数。T是模式串
void setNext(){ int j=0,k=-1; next[0]=-1; while( j<lenT ) { if( k==-1||t[j]==t[k] ) next[++j]=++k; else k=next[k]; }}当失配则回退,匹配则赋值继续前进。
下面是KMP的匹配模版
int kmp(){ int i=0,j=0; cnt=0; while( i<lenS&&j<lenT ) { if( j==-1 || s[i]==t[j] ) i++,j++; else j=next[j]; } if( j>lenT )return i-lenT; elsereturn -1;}匹配则继续,失配则滑动。
KMP主要用来解决的问题:
1.主串中模式串出现的位置
2.主串中出现模式串的次数
3.主串分割成多少个模式串
4.模式串中前缀的循环次数
以上为KMP....... 写得不好啊........
好了接下来Manacher;
这个算法主要是用于计算回文串。运用了回文串的性质。
假设我们有一个回文串以id为中心,p[id]为以id为中心的回文串的半径。
下面给一个回文串:
id p[id]
|<----|----->|
CABAAKAABAA
可以看出回文串为ABAAKAABA中心为K。
好了我们以mx=id+p[id],以id为中心的回文字符串的最右控制范围。
现在看K的右边那个字符串'B'。这个B实在mx之内的,所以还是受到了id的控制!
所以这个B的性质与B关于id的对称点左边的B有关。为啥?因为是回文嘛~两边对称。
通过肉眼,p['B']=1;所以右边这个B的左右两边也和左边的B相似。
但是仅限于当右边的B的右边界还在mx内时。
为啥?
看下面
AABAAKAABAC
这个字符串左边的p[B]=2;而右边的B显然没有这么广的控制范围,因为超过了mx的控制范围了。
所以右边p[B]的控制范围在与右边界的距离,和对称点的控制范围内取一个最小的就可以了。
但是对于原来的字符串,右边的B实际范围是还可以拓展的。所以继续拓展就好了。
拓展完后会发现新的回文串的最右边界超过了mx,此时记录更新就好了。
从左到右扫完后,取出最大的p[id]就好。再处理一下就好了。
为了避免判断奇偶性,在串中插入不常用字符'#','$'什么的.... 就这样。
#include<iostream>#include<cstdio>#include<string.h>using namespace std;int p[2222222];char str[1111111],str1[2222222];int len;void Init(){ str1[0]='$'; str1[1]='#'; len=2; for( int i=0;str[i]!=0;i++ ) { str1[len++]=str[i]; str1[len++]='#'; } str1[len]=0;}int main(){ int T=0; while( scanf("%s",&str)!=EOF ) { if( strlen(str)==3 && str[0]=='E' && str[1]=='N' && str[2]=='D' ) break; //memset( str1,0,sizeof(str1) ); memset( p,0,sizeof(p) ); Init(); int id,mx=0; for( int i=1;i<len;i++ ) { if( mx>i ) p[i]=min(p[(id<<1)-i],mx-i); else p[i]=1; while( str1[i-p[i]]==str1[i+p[i]] ) p[i]++; if( mx<i+p[i] );{ mx=i+p[i]; id=i; } } printf( "Case %d: ",++T ); int ans=0; for( int i=1;i<len;i++ ) ans=max(p[i],ans); printf( "%d\n",ans-1 ); } return 0;}
好吧下面继续讲==
所谓TireTree就是字典树,字母树。从根节点开始,每个节点代表一个字母,单词的第K个字母在树的第K层。
这只是一种数据结构。实现也不难。但却是后缀树与AC自动机的基础。
不多说,直接上模版。
#include<iostream>#include<string>#include<cstdio>#define MAX 10using namespace std;char s[11111][11];int allocp;struct TireNode{ int nCount; TireNode *next[MAX];};TireNode Memeroy[1111111];void InitTire( TireNode **root ){ *root=NULL;}TireNode *CreateTire(){ int i; TireNode *p=&Memeroy[allocp++]; p->nCount=1; for( int i=0;i<MAX;i++ ) p->next[i]=NULL; return p;}void InsertTire( TireNode **root,char *s ){ int i=0,k; TireNode *p; if( !(p=*root) ) p=*root=CreateTire(); while( s[i] ) { k=s[i++]-'0'; if( p->next[k] ) p->next[k]->nCount++; else p->next[k]=CreateTire(); p=p->next[k]; }}bool SearchTire( TireNode **root,char *s ){ int i=0,k; TireNode *p=*root; int cnt=0; while( s[i] ) { k=s[i++]-'0'; cnt=p->next[k]->nCount; p=p->next[k]; } if( cnt==1 ) return false; else return true; }int main(){ int T; scanf( "%d",&T ); while( T-- ) { allocp=0; TireNode *root; root=NULL; int len=0; scanf( "%d",&len ); for( int i=0;i<len;i++ ) { scanf( "%s",&s[i] ); InsertTire(&root,s[i]); } bool found=true; for( int i=0;i<len;i++ ) { if( SearchTire(&root,s[i]) ) { found=false; break;} } if( found==false ) printf( "NO\n" ); else printf( "YES\n" ); } return 0;}累了。。。。AC自动机明天在写吧。。。
可以这么理解AC自动机就是在一棵字典树树上进行KMP......
先模版之.......
#include<iostream>#include<cstdio>#include<string.h>#define MAX 26using namespace std;int root,tot;struct node{ int fail; int cnt; int next[MAX]; void init() { memset( next,0,sizeof(next) ); fail=-1;cnt=0; }}Tire[5555555];int queue[5555555];void init(){ root=tot=0; Tire[root].init();}void insert( int root,char *s ){ int p=root; int i=0,k; while( s[i] ) { k=s[i++]-'a'; if( !Tire[p].next[k] ) { Tire[++tot].init(); Tire[p].next[k]=tot; } p=Tire[p].next[k]; } Tire[p].cnt++;}void build_ac_automation(){ int head,tail; head=tail=0; queue[tail++]=root; while( head<tail ) { int cur=queue[head++]; for( int i=0;i<MAX;i++ ){ if( Tire[cur].next[i] ) { int son=Tire[cur].next[i]; int p=Tire[cur].fail; if( cur==root ) Tire[son].fail=root; else Tire[son].fail=Tire[p].next[i]; queue[tail++]=son; } else { int p=Tire[cur].fail; if( cur==root ) Tire[cur].next[i]=root; else Tire[cur].next[i]=Tire[p].next[i]; } } }}int query( char *s ){ int i=0,k,p=root; int ret=0; while( s[i] ) { k=s[i++]-'a'; while( !Tire[p].next[k]&&p!=root ) p=Tire[p].fail; p=Tire[p].next[k]; if(p==-1)p=0; int temp=p; while( temp!=root&&Tire[temp].cnt!=-1 ) { ret+=Tire[temp].cnt; Tire[temp].cnt=-1; //sTire[temp].cnt=0; temp=Tire[temp].fail; } } return ret;}char str[1111111];int main(){ int T; scanf( "%d",&T ); while( T-- ){ init(); int N; scanf( "%d",&N ); while( N-- ) { scanf( "%s",&str ); insert( root,str ); } build_ac_automation(); scanf( "%s",&str ); printf( "%d\n",query(str) ); //system("pause"); } return 0;}
- 初等字符串匹配专题小结[KMP][Manacher][Tire Tree][AC Automation]
- 字符串专题(trie,KMP,AC自动机,manacher)
- 字符串匹配(KMP、Tire、Map)
- 字符串专题 kmp-trie-ac
- 字符串小记(KMP, AC自动机, Manacher)
- 字符串模板 KMP AC自动机 Manacher 后缀数组
- 字符串专题小结:Manacher算法求最长回文串
- KMP, 扩展KMP, Manacher, AC自动机
- 字符串搜索匹配算法学习收集(tire树/自动机/kmp/Boyer-moore)
- KMP · 扩展KMP · Manacher 专题
- KMP · 扩展KMP · Manacher 专题
- KMP · 扩展KMP · Manacher 专题
- AC自动机专题小结
- AC自动机专题小结
- 字符串匹配:KMP & AC 自动机个人学习总结
- 动态规划和字符串匹配(KMP、AC自动机)
- 高效面试之字符串匹配(KMP,AC算法)
- AC自动机2——KMP字符串匹配算法
- SGU 117 Counting 数论
- 数组最大值
- Drawable Mutations--绘图变异
- hdoj 3521 An easy Problem(矩阵乘法)
- 几个有用的MyEclipse设置
- 初等字符串匹配专题小结[KMP][Manacher][Tire Tree][AC Automation]
- 什么是反向代理,如何利用反向代理提高网站性能
- 根据时间关机
- 【软件测试】使用JUnit框架实现Java单元测试
- MySQL乱码问题终极指南
- oracle并发控制
- 让你少奋斗10年的工作经验
- 函数简单应用
- pat 1002