字符串子串识别——KMP算法与AC自动机

字符串子串识别——KMP算法与AC自动机

  • KMP算法
    • 算法简介
    • 预处理子串
    • KMP算法主体
    • 例题
  • AC自动机
    • 算法简介
    • 建立失败跳转数组(其实就是在Trie中添加几条边)
    • 匹配
    • 例题
    • 实际应用

KMP算法

算法简介

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特–莫里斯–普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)

预处理子串

预处理子串,使匹配失败时可以快速进入下一次匹配,避免匹配失败就要重新匹配。

	int j=0;P[1]=0;
	for(int i=1;i<m;i++){
		while(j>0&&B[j+1]!=B[i+1]) j=P[j];
		if(B[j+1]==B[i+1]) j++;
		P[i+1]=j;
	}

KMP算法主体

	j=0;
	for(int i=0;i<n;i++){
		while(j>0&&B[j+1]!=A[i+1]) j=P[j];
		if(B[j+1]==A[i+1]) j++;
		if(j==m){
			printf("%d\n",i+1-m+1);		//输出匹配子串的位置(开始位置)
			j=P[j];
		}
	}

例题

洛谷P3375 【模板】KMP字符串匹配

这道题卡时间非常紧,函数调用都要超时,故预处理和KMP主体都应写在主函数中。

AC自动机

算法简介

一个常见的例子就是给出n个单词,再给出一段包含m个字符的文章,让你找出有多少个单词在文章里出现过。
要搞懂AC自动机,先得有模式树(字典树)Trie和KMP模式匹配算法的基础知识。AC自动机算法分为3步:构造一棵Trie树,构造失败指针和模式匹配过程。
如果你对KMP算法了解的话,应该知道KMP算法中的next函数(shift函数或者fail函数)是干什么用的。KMP中我们用两个指针i和j分别表示,A[i-j+ 1…i]与B[1…j]完全相等。也就是说,i是不断增加的,随着i的增加j相应地变化,且j满足以A[i]结尾的长度为j的字符串正好匹配B串的前 j个字符,当A[i+1]≠B[j+1],KMP的策略是调整j的位置(减小j值)使得A[i-j+1…i]与B[1…j]保持匹配且新的B[j+1]恰好与A[i+1]匹配,而next函数恰恰记录了这个j应该调整到的位置。同样AC自动机的失败指针具有同样的功能,也就是说当我们的模式串在Trie上进行匹配时,如果与当前节点的关键字不能继续匹配,就应该去当前节点的失败指针所指向的节点继续进行匹配。

建立失败跳转数组(其实就是在Trie中添加几条边)

一次BFS就可以了,相对于在Trie上跑KMP的预处理。

	for(i=0;i<26;i++) ch[0][i]=1;
	que[1]=1;nxt[1]=0;
	for(head=1,tail=1;head<=tail;head++){
		u=que[head];
		for(i=0;i<26;i++){
			if(!ch[u][i]) ch[u][i]=ch[nxt[u]][i];
			else{
				que[++tail]=ch[u][i];
				v=nxt[u];
				nxt[ch[u][i]]=ch[v][i];
			}
		}
	}

匹配

与Trie几乎无异。

	u=1,ans=0,len=strlen(tmp);
	for(i=0;i<len;i++){
		c=tmp[i]-'a';
		for(k=ch[u][c];k>1&&bo[k]!=-1;k=nxt[k]){	//避免有子串是另一个子串的子串而被覆盖
			ans+=bo[k];
			bo[k]=-1;	//只统计一次
		}
		u=ch[u][c];
	}

例题

洛谷P3375 【模板】AC自动机(简单版)

实际应用

搜索是否字符串中有不和谐的词语。。。

你可能感兴趣的:(算法及数据结构)