串的模式匹配

 题目大意:给定两个字符串,a(主串) b(模式串),若在a中找到与b完全相同的一个子串,返回a中匹配到的第一个字符的位置:
eg:ABABABC  返回1
   BABA

解决方法: 1 bf暴力解决 

                    2 kmp 算法 

1 bf暴力解决 

//暴力解决字符串模式匹配
#include<bits/stdc++.h>

using namespace std;
int main()
{
	int i=0,j=0;
	string s1,s2;
	cin>>s1>>s2;
	while(i<=s1.length())
	{
		if(j==s2.length())
		{
			cout<<i-j<<endl;
				break;
		}
		if(s1[i]==s2[j])
		i++,j++;
		else {
			i=i-j+1;
			j=0;
		}
	}
	if(j!=s2.length())
	cout<<"not found!\n";
	return 0;
}

 kmp算法 :kmp算法的好处在于主串的i指针一直在往前进行,没有倒回去的过程;

   下面举例讲解:求解+求nextval 数组


   eg:下标从0开始

         a: abcabcabd                                                                         a: abcabcabd 

         b: abcabd             首先进行匹配 b[0],b[1]……b[4]成功匹配   b: abcab          b[5]没有成功匹配   这里怎么办呢?

   引出最长公共前后缀(用pre表示),比如,pre(aba)=1 为a;pre(ababab)=4 为abab;

最长公共前后缀表示前面从 b[0]开始的一个子串 与 以b[end]j结尾的子串 最长的字符数

pre(abcab)=2 那么就将j指针指向pre[j-1] (pre[j]表示从下标为0开始以j结尾的子串的最长公共前后缀),i指针不变,也就是  将j=5->j=2 即 a: abcabcabd 然后进行匹配a: abcabcabd

                                                                             b: abcabd                             b: abcabd


Q;那你是否会有疑惑,为什么b串前面的ab不用和前面进行匹配了呢,你一定确保相同吗?

answer:是的,一定相同,为什么呢?

当到 j指针停下的位置(即上例中的d的位置)时,0~ j-1都与 主串中的相同,pre(abcab)=2表示前面有两个字符和最后面两个字符一样,后面的两个字符与主串匹配,那么前面pre个字符也与主串匹配,那就可以放心的移动了

Q:为什么要最长公共前后缀呢?不是最长的可以吗?

answer:不可以。不是最长则可能导致原先能匹配的那部分不能匹配了

     a:      abaabac……

     b:      abaabad     eg:pre(abaaba)=3 如果把它当成1来算会导致错误匹配

原先应该是这样的 a:      abaabac……  但当成1之后是这样的 a:      abaabac……

                                    b:      abaabad                                                b:      abaabad 

         这一步求解代码如下:

讲解:分为两种情况 1.依次比较成功匹配 2.未成功匹配则移动子串j=pre[j-1](注意对j=0特殊讨论)

int solve()
{
	while(i<a.length())
 	{
		if(a[i]==b[j])
 		i++,j++;
 		else if(j>0)
 			j=nextval[j-1];
 		else i++;	 
	 }
	 
	 if(j==b.length())
	  return i-j;
	  return -1;
}

 求naxtval数组(递归):

直接拿例子来说 ABACABAB

第一种情况:

          求此处B的pre,看前面的A,到A的pre为1,也就是前面有一个字符与到A的最后面的1个字符相同,求此处B的pre时可以充分利用起前面的pre,若b[j]与下标为pre[j-1]相同的话即(b[j]==b[pre[j-1]]),则b[j]=前一个的pre+1

第二种情况:               

              求此处B的pre,看前面的A,到A的pre为3,也就是前面有一个字符与到A的最后面的3个字符相同,求此处B的pre时可以充分利用起前面的pre,但发现此处下标为pre[j-i]的C不与此处B相等,这时说明没有那么长的公共子前缀了,即此处B前面的ABA不能全是公共子前缀,那我们可以知道此处B的公共子前缀比3小​​​​ABACABAB,也就是,到B的公共子前缀应该是从标红的ABA中找到的与B连续的一部分,比如:下划线的AB,求这连续的一部分不是就和求标红的ABA的公共子前缀一样嘛,标红的和标黄的也一样,而标黄的公共子前缀我们已经求出来了,也就是让pre=nextval[pre-1]再进行判断

 加上代码更好理解:

void deal_next()//递归 其实不是很好理解
{
	//分为两种情况 1 同则+1 
	//2异则查看更小的公共前缀
	int i=1,pre=0;
	while(i<b.length())
	{
		if(b[i]==b[pre])
		{
		  pre++;//pre每次保留上一次的值
		  nextval[i]=pre;
		  i++;		  	
		}
		else if(pre>0)
	    {//考虑 前面的字母的 公共前后缀的最长公共前后缀
		 pre=nextval[pre-1];//!!!!!!!!!
		}
		else 
		{
			nextval[i]=pre;
			i++;
		}
	}	
}

 全部代码如下:

//kmp算法 字符串匹配问题
//O(n)

#include<bits/stdc++.h>
using namespace std;
//先考虑一般情况 在考虑特殊情况(下标为0)
const int N=1e6;
string a,b;
int i=0,j=0;
int nextval[N];
void deal_next()//递归 其实不是很好理解
{
	//分为两种情况 1 同则+1 
	//2异则查看更小的公共前缀
	int i=1,pre=0;
	while(i<b.length())
	{
		if(b[i]==b[pre])
		{
		  pre++;//pre每次保留上一次的值
		  nextval[i]=pre;
		  i++;		  	
		}
		else if(pre>0)
	    {//考虑 前面的字母的 公共前后缀的最长公共前后缀
		 pre=nextval[pre-1];//!!!!!!!!!
		}
		else 
		{
			nextval[i]=pre;
			i++;
		}
	}	
}
int solve()
{
	while(i<a.length())
 	{
		if(a[i]==b[j])
 		i++,j++;
 		else if(j>0)
 			j=nextval[j-1];
 		else i++;	 
	 }
	 
	 if(j==b.length())
	  return i-j;
	  return -1;
}
 int main()
 {
 	cin>>a>>b;//b是子串
 	deal_next();
   if(solve()<0) 
   cout<<"not found!\n";
   else cout<<solve()<<endl;
 	return 0;
 }

 请大佬指教!

最浅显易懂的 KMP 算法讲解  <<吸收于此视频!

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值