KMP算法 AC自动机

先介绍一个数据结构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]) {
            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);
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值