您的位置:首页 > 其它

KMP算法 AC自动机

2014-06-25 00:00 204 查看
先介绍一个数据结构Trie,也叫前缀树,用来保存字符串集合。

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自动机