困难的KMP

1.KPM算法

该算法主要用于快速求解在主串中子串的数量,和位置,如果用暴力的话,时间复杂度是O(m*n),如果m,n的大小为int的最大值呢,那计数花费的时长就挺多的了,所以有了KMP算法,他的时间复杂度为O(m+n),时间花费大大降低。

接下来讲讲算法的实现:该算法需要用到前缀的思想,这样才能达到线性的时间,前缀主要用于存储子串的最大相同字符。这需要建立一个next数组,也可以叫KMP数组,用来表示可以跳过多少个字符。

例如:cabcabca

next为0,0,0,1,2,3,4,5  重叠的也算,第8个的“cabca”。

例如:子串:aabcaabf

下标从1开始,next数组的大小为0  1  0  0  1  2  3  0    

这是为什么呢,这需要把它看成7(子串长度)个字符串来看,第i个字符串有该字符串的前i个字符。第一个只有‘a’,这个'a'既是前缀又是后缀,所以对应数组大小为0。第二个字符串有‘aa',前面一个'a',后面一个’a‘,那next[2]=2了,接下来来看第六给字符串’aabcaab‘,他们的next[6]则是3,因为前后都有’aab'。那可能有人有疑问了,那‘acbcb’,这样相同的在中间的怎么办?注意是前缀,所以只能要前面的,所以他们是0,0,0,1,1

大致就是这样

子串数组处理:

    j=0;
    for (int i=2;i<=lb;i++)
	   {     
	   while(j&&b[i]!=b[j+1])
       //此处判断j是否为0的原因在于,如果回跳到第一个字符就不 用再回跳了
       j=kmp[j];    
        //通过自己匹配自己来得出每一个点的kmp值 
       if(b[j+1]==b[i])j++;    
       kmp[i]=j;
        //i+1失配后应该如何跳 
       

开始匹配:

    int j;
    j=0;//j可以看做表示当前已经匹配完的模式串的最后一位的位置 
    //如果楼上看不懂,你也可以理解为j表示模式串匹配到第几位了 
    for(int i=1;i<=la;i++)
	   {
          while(j&&b[j+1]!=a[i])j=kmp[j];
		  //如果失配 ,那么就不断向回跳,直到可以继续匹配 
          if (b[j+1]==a[i]) j++;
          //如果匹配成功,那么对应的模式串位置++ 
          if (j==lb) 
		  {
		  cout<<i-lb+1<<endl;
		  j=kmp[j];
		  //继续匹配 
		  }
       }

2.实战演练

1.题目:【模板】KMP

输入:

ABABABC
ABA

输出:

1
3
0 0 1 

1.处理kmp 数组

j=0;
    for (int i=2;i<=lb;i++)
	   {     
	   while(j&&b[i]!=b[j+1])
       //此处判断j是否为0的原因在于,如果回跳到第一个字符就不 用再回跳了
       j=kmp[j];    
        //通过自己匹配自己来得出每一个点的kmp值 
       if(b[j+1]==b[i])j++;    
       kmp[i]=j;
        //i+1失配后应该如何跳 
       }

2.和文本串比对

    int j;
    j=0;//j可以看做表示当前已经匹配完的模式串的最后一位的位置 
    //如果楼上看不懂,你也可以理解为j表示模式串匹配到第几位了 
    for(int i=1;i<=la;i++)
	   {
          while(j&&b[j+1]!=a[i])j=kmp[j];
		  //如果失配 ,那么就不断向回跳,直到可以继续匹配 
          if (b[j+1]==a[i]) j++;
          //如果匹配成功,那么对应的模式串位置++ 
          if (j==lb) 
		  {
		  cout<<i-lb+1<<endl;
		  j=kmp[j];
		  //继续匹配 
		  }
       }

完整代码:

#include<bits/stdc++.h>
#define MAXN 1000010
using namespace std;
int kmp[MAXN];
int la,lb,j;
char a[MAXN],b[MAXN];
int main() {
	cin>>a+1;
	cin>>b+1;
	la=strlen(a+1);
	lb=strlen(b+1);
	for (int i=2; i<=lb; i++) {
		while(j&&b[i]!=b[j+1])
			j=kmp[j];
		if(b[j+1]==b[i])j++;
		kmp[i]=j;
	}
	j=0;
	for(int i=1; i<=la; i++) {
		while(j>0&&b[j+1]!=a[i])
			j=kmp[j];
		if (b[j+1]==a[i])
			j++;
		if (j==lb) {
			cout<<i-lb+1<<endl;
			j=kmp[j];
		}
	}

	for (int i=1; i<=lb; i++)
		cout<<kmp[i]<<" ";
	return 0;
}

2.题目:[BOI2009] Radio Transmission 无线传输

输入:

8
cabcabca

输出:

3

为了防止特殊情况,以“cabcdcabcdc”长度为11的串例子,next对应为0,0,0,1,0,1,2,3,4,5,6有没有方向,在某些时候,他是一直增加,并且没有负数,很明显,该部分是子串分裂组成的,那个next数组中数值为1到6的连续增加的下标就是输入字符串的最大分裂长度,那用总长度减一下,就是最短子长度了。

#include<bits/stdc++.h>
#define MAXN 1000010
using namespace std;
int kmp[MAXN];
int la,lb,j;
char b[MAXN];
int main() {
int n,k;
cin>>n;
	cin>>b+1;
	for (int i=2; i<=n; i++) {
		while(j&&b[i]!=b[j+1])
			//此处判断j是否为0的原因在于,如果回跳到第一个字符就不 用再回跳了
			j=kmp[j];
		//通过自己匹配自己来得出每一个点的kmp值
		if(b[j+1]==b[i])j++;
		kmp[i]=j;
	}
		cout<<n-kmp[n];
	return 0;
}

3.题目:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值