KMP算法 AC自动机
2014-06-25 00:00
204 查看
先介绍一个数据结构Trie,也叫前缀树,用来保存字符串集合。
Trie例题:
给出一个由S个不同单词组成的字典和一个长字符串。把这个字符串分解成若干个单词的连接(单词可以重复使用),有多少种方法?比如,有4个单词a,b,cd,ab,则abcd又两种分解方法:a+b+cd和ab+cd
分析:不难想到用动态规划。令d[i]表示从字符串i开始的字符串(即后缀S[i....L])的分解方法数,则d[i]=sum{d[i+len(x)]},其中单词x是S[i...L]的前缀。如果枚举x,在判断它是否是为S[i...L],时间无法承受。可以换个思路,先把所有的单词组成一个Trie,然后试着在Trie中查找S[i...L]的前缀。查找过程中没经过一个单词节点,就找到一个上述状态转移方程中的x,最多需要比较100次就能找到所有x。
KMP算法:
KMP算法解决的是字符串匹配的问题。假定匹配过程中正在比较文本串*位置的字符和模板串abbaaba的最后一个字符,发现二者不同。KMP算法认为,我们已经知道文本串*前六个字符就是abbaab。因此应该根据模板串本身的特性来判断出右移一位一定不匹配。因此我们用一个f[i]来表示当模板串的第i个字符失配的时候,我们应该调到第几个字符重新匹配。
AC自动机:在模式匹配问题中,如果模板有很多个,KMP算法就不太合适了。因为每次查找一个模板,都要遍历整个文本串。可不可以只遍历一次文本串呢?可以,方法就是把所有的模板建成一个大的状态转移图,而不是每个模板各建一个状态转移图。注意到KMP的状态转移图是线性的字符串加上失配边组成的,不难想到AC自动机是Trie加上失配边组成的。
代码中出现的一个陌生的数组last,下面解释一下。和Trie一样,我们认为所有val[j]>0的结点j都是单词结点,反之亦然。但是和Trie不同的是,同一个结点可能对应多个字符串的结尾。如,当找到she的时候,也以为着我们找到了he。当然,失配指针不一定指向单词结点,为了提高效率,这里增设了一个指针last[j],表示结点j沿着失配指针往回走时,遇到的下一个单词结点。
计算失配函数的方式和KMP很接近,知识把线性递推改成了按照BFS顺序递推。代码如下:
HDOJ2222 基本的AC自动机
HDOJ 2896 病毒入侵 我的AC代码
int ch[maxnode][sigma_size]; int val[maxnode]; int sz; //结点总数 void init(){ sz = 1; //初始化时只有一个结点 memset(ch[0],0,sizeof(ch[0])); } //插入字符串s,附加信息为v。注意v必须是非0,因为0代表本结点不是单词结点 void insert(char* s, int v){ int u = 0, n = strlen(s); for(int i = 0; i < n; i++){ int c = s[i]-'a'; if(!ch[u][c]){ //结点不存在 memset(ch[sz],0,sizeof(ch[sz])); val[sz] = 0; //中间结点的附加信息为零 ch[u][c] = sz++; } u = ch[u][c]; //往下走 } val[u] = v; //字符串的最后一个字符的附加信息为v
Trie例题:
给出一个由S个不同单词组成的字典和一个长字符串。把这个字符串分解成若干个单词的连接(单词可以重复使用),有多少种方法?比如,有4个单词a,b,cd,ab,则abcd又两种分解方法:a+b+cd和ab+cd
分析:不难想到用动态规划。令d[i]表示从字符串i开始的字符串(即后缀S[i....L])的分解方法数,则d[i]=sum{d[i+len(x)]},其中单词x是S[i...L]的前缀。如果枚举x,在判断它是否是为S[i...L],时间无法承受。可以换个思路,先把所有的单词组成一个Trie,然后试着在Trie中查找S[i...L]的前缀。查找过程中没经过一个单词节点,就找到一个上述状态转移方程中的x,最多需要比较100次就能找到所有x。
KMP算法:
KMP算法解决的是字符串匹配的问题。假定匹配过程中正在比较文本串*位置的字符和模板串abbaaba的最后一个字符,发现二者不同。KMP算法认为,我们已经知道文本串*前六个字符就是abbaab。因此应该根据模板串本身的特性来判断出右移一位一定不匹配。因此我们用一个f[i]来表示当模板串的第i个字符失配的时候,我们应该调到第几个字符重新匹配。
int find (char* t, char* p, int* f) //T为文本,p为模板串,f为失配函数 { int n = strlen(t), m = strlen(p); getFail(p,f); //构建失配函数 int j = 0; for(int i = 0; i < n; i++){ while(j && p[j]!=t[i]) j = f[j]; if(p[j]==t[i]) j++; if(j==m) printf("%d\n", i-m+1); } } void getFail(char* p int* f){ int m = strlen(p); f[0] = 0,f[1] = 0; for(int i = 1; i < m; i++){ int j = f[i]; while(j && p[j]!=p[i]) j = f[j]; f[i+1] = (p[j]==p[i] ? j+1 : 0); } }
AC自动机:在模式匹配问题中,如果模板有很多个,KMP算法就不太合适了。因为每次查找一个模板,都要遍历整个文本串。可不可以只遍历一次文本串呢?可以,方法就是把所有的模板建成一个大的状态转移图,而不是每个模板各建一个状态转移图。注意到KMP的状态转移图是线性的字符串加上失配边组成的,不难想到AC自动机是Trie加上失配边组成的。
int find (char* t){ int n = strlen(t); int j = 0; for(int i = 0; i < n; i++){ int c = t[i]-'a'; while(j && !ch[j][c]) j = f[j]; j = ch[j][c]; if(val[j]){ print(j); //发现了一个单词匹配,并沿着后缀链接找更多匹配的单词 } else if(last[j]) print(last[j]); } } void print (int j){ if(j){ printf("%d\n", val[j]); print(last[j]); } }
代码中出现的一个陌生的数组last,下面解释一下。和Trie一样,我们认为所有val[j]>0的结点j都是单词结点,反之亦然。但是和Trie不同的是,同一个结点可能对应多个字符串的结尾。如,当找到she的时候,也以为着我们找到了he。当然,失配指针不一定指向单词结点,为了提高效率,这里增设了一个指针last[j],表示结点j沿着失配指针往回走时,遇到的下一个单词结点。
计算失配函数的方式和KMP很接近,知识把线性递推改成了按照BFS顺序递推。代码如下:
int getFail(){ queue<int> q; f[0] = 0; for(int i = 0; i < sigma_size; i++){ int u = ch[0][i]; if(u){ f[u] = 0; last[u] = 0; q.push(u); } } while(!q.empty()){ int u = q.front(), q.pop(); for(int i = 0; i < sigma_size; i++){ int r = ch[u][i]; if(r){ q.push(r); int p = f[u]; while(p && !ch[p][i]) p = f[p]; f[r] = ch[p][i]; last[cr] = (val[f[r]] ? f[r] : last[f[r]]); } } } }
HDOJ2222 基本的AC自动机
#include <iostream> #include <string.h> #include <queue> using namespace std; #define MAXN 500005 int n,sz,result; int ch[MAXN][26],last[MAXN],f[MAXN],val[MAXN]; char t[1100000], c[50]; //题目中说t的长度不会超过1000000,所以一开始给t开了1000000大小,一直WA,纠结了一上午,最后我看别人的程序,试探性的改了下t的大小,竟然AC了。。。杭电的数据有的时候真是让人无语。。。。 void insert(char c[]){ int m = strlen(c); int j = 0; for(int i = 0; i < m; i++){ int index = c[i]-'a'; if (!ch[j][index]) { memset(ch[sz], 0, sizeof(ch[sz])); val[sz] = 0; ch[j][index] = sz++; } j = ch[j][index]; } val[j] += 1; } void GetFail(){ queue<int> q; for(int i = 0; i < 26; i++){ if (ch[0][i]) { int u = ch[0][i]; f[u] = 0; last[u] = 0; q.push(u); } } while (!q.empty()) { int u = q.front(); q.pop(); for(int i = 0; i < 26; i++){ if (ch[u][i]) { int k = ch[u][i]; int j = f[u]; while (j && !ch[j][i]) j = f[j]; f[k] = ch[j][i]; last[k] = (val[f[k]] ? f[k] : last[f[k]]); q.push(k); } } } } void init(){ memset(ch[0], 0, sizeof(ch[0])); memset(val, 0, sizeof(val)); sz = 1; result = 0; memset(f, 0, sizeof(f)); memset(last, 0, sizeof(last)); scanf("%d", &n); for(int i = 0; i < n; i++){ scanf("%s",c); insert(c); } GetFail(); scanf("%s", t); } void count(int j){ if(!j) return; if (val[j]) { result += val[j]; val[j] = 0; } count(last[j]); } void solve(){ int j = 0; int m = strlen(t); for(int i = 0; i < m; i++){ int index = t[i]-'a'; while (j && !ch[j][index]) j = f[j]; j = ch[j][index]; if(val[j]) count(j); else if(last[j]) count(last[j]); } } int main(int argc, const char * argv[]) { int t; while (scanf("%d", &t)!=EOF) { while (t > 0) { t--; init(); solve(); printf("%d\n", result); } } }
HDOJ 2896 病毒入侵 我的AC代码
#include <iostream> #include <string.h> #include <vector> #include <queue> #include <stdlib.h> using namespace std; #define MAXN 110000 #define sigma 133 int m,n,sz; vector<int> result; int ch[MAXN][sigma],f[MAXN],last[MAXN],v[MAXN],vis[MAXN]; char t[11000],c[210]; void insert(int num){ int j = 0; int k = strlen(c); for(int i = 0; i < k; i++){ int index = (int)c[i]; if (!ch[j][index]) { memset(ch[sz], 0, sizeof(ch[sz])); v[sz] = 0; ch[j][index] = sz++; } j = ch[j][index]; } v[j] = num; } void MakeFail(){ queue<int> q; for(int i = 0; i < sigma; i++){ if (ch[0][i]) { 4000 int u = ch[0][i]; f[u] = 0; last[u] = 0; q.push(u); } } while (!q.empty()) { int u = q.front(); q.pop(); for(int i = 0; i < sigma; i++){ if(ch[u][i]){ int j = ch[u][i]; int k = f[u]; while (k && !ch[k][i]) k = f[k]; f[j] = ch[k][i]; last[j] = (v[f[j]] ? f[j] : last[f[j]]); } } } } void init(){ memset(ch[0], 0, sizeof(ch[0])); memset(f, 0, sizeof(f)); memset(last, 0, sizeof(f)); memset(v, 0, sizeof(v)); memset(vis, 0, sizeof(vis)); result.clear(); sz = 1; for(int i = 0; i < n; i++){ scanf("%s",c); insert(i+1); } } void count (int num){ int j = num; while (j) { if (v[j] > 0) { if(vis[j]==0) result.push_back(v[j]); vis[j] = 1; j = last[j]; } } } void find (){ int j = 0; int l = strlen(t); for(int i = 0; i < l; i++){ int index = (int)t[i]; while(j && !ch[j][index]) j = f[j]; j = ch[j][index]; if (v[j] > 0) { count(j); }else if(last[j]) { count(last[j]); } if (result.size()==3) { break; } } } int main(int argc, const char * argv[]) { while (scanf("%d",&n)!=EOF) { int total = 0; init(); MakeFail(); scanf("%d", &m); for(int i = 0; i < m; i++){ result.clear(); memset(vis, 0, sizeof(vis)); scanf("%s", t); find(); sort(result.begin(),result.end(), less<int>()); if (result.size() > 0) { printf("web %d:",i+1); for(int j = 0; j < result.size(); j++){ printf(" %d", result[j]); } printf("\n"); total++; } } printf("total: %d\n",total); } }
相关文章推荐
- KMP算法(AC自动机前奏)(转)
- 从KMP算法,trie树再到AC自动机
- hipo 3 kmp算法
- 关于KMP算法的理解——文本匹配算法
- 匹配字符串的KMP算法
- hiho第三周——字符串匹配KMP算法
- AC自动机
- KMP算法详解
- BZOJ 3899 仙人掌树的同构 仙人掌同构+KMP算法
- KMP算法
- KMP算法解决字符串匹配问题
- 暑假- ac自动机-(A - Keywords Search)
- [HDU 2896] 病毒侵袭 AC自动机
- KMP算法 —— next 数组的应用 --- 前缀中最小循环节,最大重复次数
- KMP算法
- AC自动机(1)
- POJ 1625 Censored! (AC自动机 + 高精度 + DP)
- KMP算法--next数组
- HDU--2222--Keywords Search--AC自动机
- hdu3065 AC自动机