KMP算法

本文介绍了KMP算法用于字符串匹配的原理,并详细解释了如何计算next数组。通过一个具体的例子展示了next数组的生成过程,同时提到了快速计算next数组的方法。KMP算法在处理字符串匹配时能有效避免不必要的回溯,提高效率。
摘要由CSDN通过智能技术生成

下面来总结一下用来匹配字符串的KMP算法,以及怎么求next数组。

当然我们可以用笨办法可以进行字符串匹配和求next数组,但是学习算法不就是追求极致的过程么?

通过参考https://blog.csdn.net/sun20209527/article/details/79933237,可以通透的理解KMP是个什么过程,但细节处理和他会有些出入。

举例来说,有一个字符串"BBC ABCDAB ABCDABCDABDE",我想知道,里面是否包含另一个字符串"ABCDABD"?

  

 

我们先说一下next数组代表的是什么?就是一个字符串的每一个子串的前缀后缀的共有元素长度。

以"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。

所以next[0] = 0 ; next[1] = 1 ; next[2] = 0; next[3] = 0 ; next[4] = 1; next[5] = 2 ; next[6] = 0 ;

下面就是KMP的具体过程:

1.

     B与A不匹配,所以搜索词后移一位。

2.

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

3.

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

4.

这里出现了空格和D不匹配的现象.

5.

注意:这个操作一次连续移动了4位,我们把已经匹配的ABCDAB字符串的前缀移动到后缀的位置上就可以了!

6.

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

7.

一直匹配。。。。一直到发现C和D不匹配。

8.

已经匹配的字符串ABCDAB还是把最大的 前缀放到后缀的位置上。然后继续匹配ABCDAB后面的字符。

9.

  "部分匹配"的实质是,有时候,字符串头部和尾部会有重复。比如,"ABCDAB"之中有两个"AB",那么我们可以利用next数组记录的信息搜移动之后,就可以来到第二个"AB"的位置。

 

 

#include<iostream>
#include<cstring>
using namespace std;
void getnext(string s,int next[])
{
	int n = s.size();	
	memset(next,0,sizeof(next));
	int k = 0;                      //k代表 最大前后缀长度 
	for(int i = 1,k = 0; i<n; i++)
	{
		while(k>0 && s[k] != s[i])
		{
			k = next[k-1];	
		} 
		
		if(s[i] == s[k])
		{
			k++;
		}
		next[i] = k;
	}

	
}
 //a 是主字符串   b是小字符串 
int kmp(string a,string b,int next[])
{
	
	int n = a.size() , i = 0;           //主字符串的长度,主字符串的下标 
	int m = b.size() , j = 0;			//小字符串的长度,已经匹配的个数 
	
	while(i < n) 
	{
		
		if(j==0)                       //如果小字符串中的第一个元素没有找到与大字符串中有匹配的,那就寻找===  
		{
			if(a[i] == b[j])
			{
				j++;                                      //已经匹配的个数 
			}
			else
			{
				i++;									 //主字符串的 下标 
			}
		}else{                        //已经有匹配的了 
			if(a[i+1] != b[j])    //如果不下一个字符不相等,那就移动小字符串,这个过程操作起来就是更改小字符串的下标。 
			{
				j = next[j-1];
			}	
		
			if(a[i+1] == b[j])  //如果相等那就比较下一个字符 
			{
				i++;
				j++;
				
			 }
	}
		
	    if(j == m)                    //如果小字符串全比较完了,那就是全匹配了,完成! 否则大字符串中不存在 小字符串! 
			return i;                //这里返回小字符串在大字符串中最后匹配成功的字符的下标 
	}
	return 0;	
}

int main()
{
	int next[1000];
	string a = "BBC ABCDAB ABCDABCDABDE";

	string b = "ABCDABD";
	
	getNext(next,b);
	

	cout<<"kmp:"<<kmp(a,b,next)<<endl;	
}

这里的求next数组我们可以用笨办法枚举的方法如下:

void getNext(int next[],string s)
{
	memset(next,0,sizeof(next));
	for(int i=0; i<s.size(); i++)
	{
		int ans = 0;
		
		string str = s.substr(0,i+1);
		for(int j=0; j<str.size()-1; j++)
		{
			string str1 = str.substr(0,j+1);			
			string str2 = str.substr(str.size()-j-1,str.size());
			if(str1==str2)
			{
				ans = j+1;
			}
		
		}
		
		next[i] = ans;
	}
} 

 

这样很容易懂,和容易写出来,但是效率很慢!所以在网上找到了一个特别精简的方法!

void getnext(string s,int next[])
{
	int n = s.size();
	memset(next,0,sizeof(next));
	int k = 0;                                  //k代表 最大前后缀长度 
	for(int i = 1,k = 0; i<n; i++)
	{
		while(k>0 && s[k] != s[i])             //精髓!!!!
		{
			k = next[k-1];	
		} 
		
		if(s[i] == s[k])
		{
			k++;
		}
		next[i] = k;
	}

	
}

 

k代表的是前一个字符串最长的前后缀长度 ; a[i]是新加上的字符

这里分几种情况:

1、如果s[0]......s[k-1]和s[i-k].......s[i-1]匹配,并且s[i] == s[k] ,那么next[i] = next[i-1]+1就可以了(简单的情况)

2、但是如果s[i] != s[k]呢?就是上面的标记精简的那段代码!

我们应该利用已经得到的next[0]···next[k-1]来求s[0]···s[k-1]这个子串中最大相同前后缀。因为!!在于s[k]已经和s[i]失配了,而且s[i-k] ··· s[i-1]又与s[0] ···s[k-1]相同,看来s[0]···s[k-1]这么长的子串是用不了了,那么我要找个同样也是s[0]打头、s[k-1]结尾的子串即s[0]···s[j-1](j==next[k-1]),看看它的下一项s[j]是否能和s[i]匹配。

用我们一个例子把:

ABADABA[B]

我们已经知道了 ABADABA的 next[6] = 3,也就是k=3,但是s[3]=D不等于s[7]=B,k= next[k-1] = next[2] =  1,再来比较s[1]和s[i],结果相等,那么next[7]=next[1]+1 = 2;

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值