[字符串] kmp与trie与ac自动机的快乐一家人

Posted zero_orez6

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[字符串] kmp与trie与ac自动机的快乐一家人相关的知识,希望对你有一定的参考价值。

kmp

kmp算法,主要用于进行字符串之间的模式匹配,也就是判断字符串b是否为a的子串,并支持在线性的时间内找出b在a中的出现次数。

具体实现

讲完主要功能,当然要开始讲如何去实现了。

通常的,判断一个字符串是否为另一个字符串的子串,就是逐字逐字地去判断,若有不相同的字符,就将b字符串整体向后平移一位,再次从头开始判断。

下面介绍kmp算法的主要步骤:

  1. 先对母串a进行处理,求出一个数组 n e x t [ n ] next[n] next[n],其中 n e x t [ i ] next[i] next[i]表示“字符串a 1~i位中后缀能够与前缀匹配的最长长度”(字符串默认从1开始)
  2. 然后对字符串a,b进行匹配,求出一个数组f,表示“b中以i结尾的子串”与“a的前缀”所能够匹配的最大长度。

很多同学就要问了,你这求前缀与后缀的最长匹配,不也是 O ( n 2 ) O(n^2) O(n2)的吗?博主骗人!!!

下面,我们就主要讨论如何在线性时间内求出next数组。

求出next

朴素的算法当然就是像判断子串那样,一个一个平移判断, O ( n 2 ) O(n^2) O(n2),就不多讲了。

先对母串a进行自我匹配

  1. 先将匹配的下标j,赋初值为0,也就是字符串前一位

  2. 再一边枚举a 1 _1 1数组,一边将j+1位与现在枚举的位数进行匹配,分为以下两种情况:

    若两者相同,则令j++.

    否则令j=next[j],再次进行判断

3.最后令next[i]=j就行了。

下面解释原理:

  • 为什么令j=next[j]???

    因为next数组中存储的是前缀与后缀能够匹配的最长长度,也就是说,假如 n e x t [ i ] = k next[i]=k next[i]=k,那么字符串a中 a [ 1 a[1 a[1~ k ] k] k] == a [ i − k a[i-k a[ik~ i ] i] i],那么我们将 j j j移到 n e x t [ j ] next[j] next[j],与前next[next[j]]位进行匹配,在前缀匹配上是等价的,只有后面的字符不相同,对后面的字符再次进行判断。

  • 为什么相同时令j++???

    因为前缀和后缀又多匹配了一位呀,就是新枚举到的第i位。

  • 为什么next[1]=0???

    因为这里说的前缀和后缀的匹配是不平凡匹配,也就是不包括这个串本身,所以第一个字符只有其本身,当然是0了。

附上code

next[0]=-1;
for(int i=1;i<m;i++)
	
		int now=next[i-1];
		while((now!=-1)&&(ss[now+1]!=ss[i])) now=next[now];
		next[i]=now+1;
	

复杂度

在每次while循环中,j的值不断减小,对于 j = n e x t [ j ] j=next[j] j=next[j]这一语句的执行次数不会超过for循环开始时的值与while结束时的值的差,在for循环中j的值至多增加1。
综上所述:j的总变化次数不超过 2 ( n + m ) 2(n+m) 2(n+m),忽略掉常数,求出next数组的复杂度约为 O ( n + m ) O(n+m) O(n+m)

trie

trie,又被称为字典树,是一种支持字符串快速检索的多叉树结构。

这种树结构的每一个节点都有一个字符指针,当询问或插入一个字符串时,按这个字符串的每一个字符依次向下检索trie树。

初始化

一棵trie在开始时只包含一个根节点,字符指针指向此根节点。

插入

插入一个字符串时,依次遍历该字符串的每一个字符,若在trie树中存在该字符,则使字符指针指向该节点,并沿继续向下检索,否则新建一个节点q,使字符指针指向该节点。

检索

检索操作与插入大致相同,只是在字符指针指向空时,返回0即可。

为便于大家理解,附上草图:

如图是插入cat,car和dog的一棵trie树

附上code:

void add(char* s)//插入操作

	int len=strlen(s),p=1;
	for(int k=0;k<len;k++)
	
		int ch=s[k]-'a';
		if(trie[p][ch]==0) trie[p][ch]=++tot;
		p=trie[p][ch];
	
	end[p]=1;
 
bool ask(char* s)//检索操作

	int len=strlen(s),p=1;
	for(int k=0;k<len;k++)
	
		p=trie[p][s[k]-'a'];
		if(p==0) return 0;
	
	return end[p];
 

值得注意的几点

  • 对于插入字符串的每一个字符,他们在trie树中其实是若干条边,也就是所说的指针
  • 树的节点上记录一些关于字符串的额外信息,例如上图中存储的是单词末尾的标记,我们还可以根据题目要求改变存储信息。
  • 在将char类型与int 类型相互转换时注意题目要求是大写字母,小写字母或是两者都有。

ac自动机

观察题目可得,ac自动机是上面两者结合的产物

以上是关于[字符串] kmp与trie与ac自动机的快乐一家人的主要内容,如果未能解决你的问题,请参考以下文章

AC自动机初步

总结与归纳之字符串

KMP,Trie,AC自动机题目集

AC自动机

数据结构与算法简记--多模式字符串匹配AC自动机

高效处理字符串!——AC自动机