博主链接

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

一.KMP算法求解什么类型问题

字符串匹配。给你两个字符串,寻找其中一个字符串是否包含另一个字符串,如果包含,返回包含的起始位置。 或者给你两个个字符串,查找一个字符串在另外一个字符串中出现了几次
如下面两个字符串:

char *str = "BBC ABCDAB ABCDABCDABDE";
char *ptr = "ABCDABD";

 1.

  首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一个字符与搜索词"ABCDABD"的第一个字符,进行比较。因为B与A不匹配,所以搜索词后移一位。

  2.

  因为B与A不匹配,搜索词再往后移。

  3.

  就这样,直到字符串有一个字符,与搜索词的第一个字符相同为止。

  4.

  接着比较字符串和搜索词的下一个字符,还是相同。

  5.

  直到字符串有一个字符,与搜索词对应的字符不相同为止。

  6.

  这时,最自然的反应是,将搜索词整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把"搜索位置"移到已经比较过的位置,重比一遍。

  7.

  一个基本事实是,当空格与D不匹配时,你其实知道前面六个字符是"ABCDAB"。KMP算法的想法是,设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,继续把它向后移,这样就提高了效率。

  8.

  怎么做到这一点呢?可以针对搜索词,算出一张《部分匹配表》(Partial Match Table)。这张表是如何产生的,后面再介绍,这里只要会用就可以了。

  9.

  已知空格与D不匹配时,前面六个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符B对应的"部分匹配值"为2,因此按照下面的公式算出向后移动的位数:

  移动位数 = 已匹配的字符数 - 对应的部分匹配值

  因为 6 - 2 等于4,所以将搜索词向后移动4位。

  10.

  因为空格与C不匹配,搜索词还要继续往后移。这时,已匹配的字符数为2("AB"),对应的"部分匹配值"为0。所以,移动位数 = 2 - 0,结果为 2,于是将搜索词向后移2位。

  11.

  因为空格与A不匹配,继续后移一位。

  12.

  逐位比较,直到发现C与D不匹配。于是,移动位数 = 6 - 2,继续将搜索词向后移动4位。

  13.

  逐位比较,直到搜索词的最后一位,发现完全匹配,于是搜索完成。如果还要继续搜索(即找出全部匹配),移动位数 = 7 - 0,再将搜索词向后移动7位,这里就不再重复了。

  14.

  下面介绍《部分匹配表》是如何产生的。

  首先,要了解两个概念:"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。

  15.

  "部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例,

  - "A"的前缀和后缀都为空集,共有元素的长度为0;

  - "AB"的前缀为[A],后缀为[B],共有元素的长度为0;

  - "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;

  - "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;

  - "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;

  - "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;

  - "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。

  16.

  "部分匹配"的实质是,有时候,字符串头部和尾部会有重复。比如,"ABCDAB"之中有两个"AB",那么它的"部分匹配值"就是2("AB"的长度)。搜索词移动的时候,第一个"AB"向后移动4位(字符串长度-部分匹配值),就可以来到第二个"AB"的位置。

/*

Problem:HDU1005,POJ3461,HDU3764

Content:KMP详解

Author : Anoyer

*/


#include<bits/stdc++.h>
using namespace std;
const int maxn=10001;
int next[maxn];
char s[maxn];	//主串 
char p[maxn];	//模式串 
int cnt=0;
void prefix_next(int n){	//匹配模式串next数组 
	next[0]=0;	//next[0]初始化为0,0表示不存在相同的最大前缀和最大后缀 
	int len=0;
	int i=1;
	while(i<n){
		if(p[i]==p[len]){	//如果相等则 i和len同时后移一位 
			len++;
			next[i]=len;	//next[i]变换 
		}
		else {		//如果发生失配 则len回到next[len-1]位置 ,(考虑len是否大于零) 
			if(len>0){
				len=next[len-1];
			}
			else{
				next[i++]=len;
			}
		}
	}
	return;
}
void move_next(int n){
	for(int i=n-1;i>0;i--){
		next[i]=next[i-1];
	}
	next[0]=-1;
	return;
}
void kmp_search(){			//kmp匹配和next数组步骤类似 
	int n=strlen(p);
	int m=strlen(s);
	prefix_next(n);
	move_next(n);
	int i=0;
	int j=0;
	while(i<m){					
		if(s[i]==p[j]&&j==n-1){
			printf("No.%d-->%d\n",++cnt,i-j);
			j=next[j];
		}
		if(s[i]==p[j]){
			i++;
			j++;
		}
		else{
			j=next[j];
			if(j==-1){
				i++;j++;
			}
		}
	}
	if(cnt==0)cout<<"NO FOUD"<<endl;
	return;
}
int main(){
	cin>>s;
	cin>>p;
	kmp_search();
}

如果对代码不能完全理解,请观看视频详细讲解

优化代码,效率更高

/*

Problem:HDU1005,POJ3461,HDU3764

Content:KMP详解

Author : Anoyer

*/


#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
char s[1000005],t[200000];
int slen,tlen;
int nex[200000];//nex数组大小和短串一致
int ans,a,b,c,d,n,m;
inline void get_nex()
{
        int j=-1;//j初始化为-1
        for (int i=0;i<tlen;i++){
            while  (t[i]!=t[j+1] && j!=-1)//如果下一个不同,那么j就变成next[j],注意next[j]是小于j的,无论j取任何值
			j=nex[j];//往前回溯
            if (t[i]==t[j+1] && i!=0) j++;//如果相同,j++
            nex[i]=j;//这个是把算的j的值(就是相同的最大前缀和最大后缀长)赋给next[i]
    	}
}
inline void kmp()
{
        int j=-1;
        for (int i=0;i<slen;i++){
            while  (s[i]!=t[j+1] && j!=-1) 
				j=nex[j];
            if (s[i]==t[j+1]) 
				j++;
            if (j==tlen-1)  
				ans++,j=nex[j];
        }
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++){
        ans=0;
        scanf("%s %s",t,s);
        slen=strlen(s);
        tlen=strlen(t);//这两个长度应该设为全局变量最开始时求出,不能用一次求一次
        get_nex();
        kmp();
        printf("%d\n",ans);
    }
}