模式串匹配:KMP算法和AC自动机(一)

模式串匹配也称字符串匹配,是在计算机中用得非常多的一种操作。这篇博客主要记录两种常见的字符串匹配算法的学习历程,它们分别是KMP算法AC自动机。其中KMP是迄今为止用得最为广泛并且算法效率极高的一种单模式串匹配的算法,相比朴素字符串匹配算法O(n*m)的时间复杂度(其中n为主串的长度,m为模式串的长度),KMP的时间复杂度只有O(n+m),在主串长度远大于模式串的长度时,KMP的效率是相当优秀的,当然现实场景中大多数字符串匹配都是主串远大于模式串。KMP是通过对某一特定模式串的每一个字符处建立起失配指针,通过这些失配指针可以在模式串任意位置匹配失败的时候完成最优状态转移。

其实KMP的关键就是求出特定模式串的适配指针数组next[],其实适配指针数据就是对一个状态机的描述。KMP算法的一个最主要的特征就是指向主串的字符指针不会回退,而这个状态机描述了模式串在与主串进行比较的过程中,模式串指针在任意位置匹配和失配之后下一步跳转的位置。

先上代码:KMP的关键一步就是求出失配指针数组next[]

void cal_next(const char s[],int next_[]){
    int len=strlen(s);
    next_[0]=0;
    int k=0;
    for(int i=1;i0&&s[i]!=s[k])
            k=next_[k-1];
        if(s[i]==s[k])
            k++;
        next_[i]=k;
    }
}

其中函数参数中,s为用来匹配的模式串,next_表示失配指针数组,其实失配指针数组中存放的并不是指针,而是匹配失败之后在模式串中的跳转位置,所以一般用int数组来保存。在这个计算next[]数组的函数中最最最精髓的部分是一个while循环:

while(k>0&&s[i]!=s[k])
    k=next_[k-1];

next[]数组中保存的是失配跳转的位置标号,这个标号的另外一种理解方式就是:如果从模式串开头到当前位置的字符串为Si,那么跳转位置标号的数值大小其实就是字符串Si最大前后缀的长度。通过while循环递归求出最大前后缀的长度,对模式串的每一位都会进行这样的while循环计算。

模式串匹配:KMP算法和AC自动机(一)_第1张图片

 

模式串匹配:KMP算法和AC自动机(一)_第2张图片

计算出失配指针数组之后,就可以在字符串匹配的过程中使用失配指针来达到提高字符串匹配的效率。KMP算法的核心过程其实和计算失配指针的过程非常相似(从代码中就可以看出)。在KMP算法过程中,我们如果用指针i和指针k分别表示指向主串和模式串的位置,每一次字符与字符之间的比较就是指针i指向的字符和指针k指向的字符,前面也提到过:指向主串的指针i不会回退是KMP相比朴素字符串匹配效率更高的直接原因。KMP算法过程的代码如下(在已经计算出失配指针数组的基础上):

void KMP(const char p[],const char s[],int next[],vector &ans){
	ans.clear();
	int plen=strlen(p);
	int slen=strlen(s);
	memset(next,0,sizeof(p));
	cal_next(p);
	int k=0;
	for(int i=0;i0&&s[i]!=p[k])
			k=next[k-1];
		if(s[i]==p[k])
			k++;
		if(k==plen){
			ans.push_back(i-k+1);
			k=next[k];
		}
	}
}

其中函数参数p为模式串、s为主串、next为失配指针数组、ans为保存匹配成功的字符串下标号。之所以说KMP算法过程与计算失配指针数组的过程十分相似,是因为它们都是在一个for循环中嵌套一个while循环递归求下一个跳转位置,只不过区别在于for循环一个是在模式串p上迭代(求失配指针数组),一个是在主串上循环迭代(KMP字符串匹配过程),其中的while循环中进行字符与字符之间的比较的对象也不同。

实例程序:

#include 
#include 
#include 
#include 
using namespace std;

const int MAXSIZE=100010;
int next_[MAXSIZE];
vector ans;

void cal_next(const char s[],int next[]){
    int len=strlen(s);
    next[0]=0;
    int k=0;
    for(int i=1;i0&&s[i]!=s[k])
            k=next[k-1];
        if(s[i]==s[k])
            k++;
        next[i]=k;
    }
}

void KMP(const char p[],const char s[],int next[],vector &ans){
	ans.clear();
	int plen=strlen(p);
	int slen=strlen(s);
	memset(next,0,sizeof(p));
	cal_next(p,next);
	int k=0;
	for(int i=0;i0&&s[i]!=p[k])
			k=next[k-1];
		if(s[i]==p[k])
			k++;
		if(k==plen){
			ans.push_back(i-k+1);
			k=next[k];
		}
	}
}

int main(){
	char *s="bacbababadababacambabacaddababacasdsd";
	char *p="ababaca";
	KMP(p,s,next_,ans);
	for(int i=0;i

最后结果可以得到:在主串下标的10和26处字符串匹配成功。这篇博客主要介绍的是KMP算法(单模板匹配),下一篇博客模式串匹配:KMP算法和AC自动机(二)会介绍用于多模板匹配的字符串匹配算法:AC自动机算法。

你可能感兴趣的:(ACM&CCF,数据结构和算法,C/C++)