当前位置:   article > 正文

算法基础课第二章KMP_复现算法:假设字符串s采用string对象存储。利用kmp算法设计一个算法,在串s中查找

复现算法:假设字符串s采用string对象存储。利用kmp算法设计一个算法,在串s中查找

KMP算法用于进行字符串匹配。
暴力的方法是每次失配就从模式串的头部进行匹配,但是因为有前后缀匹配的情况,不必每次回到头部,通过一个next数组, 可以在失配时让主串i去匹配next[j],这样能很快的进行匹配。

如何使用next数组

void KMP(string s, string p)
{
	int j, i = 0;
	while(i < p.size())
	{
		if(j == -1 || s[j] == p[i])//j == -1 说明第一个字符也不匹配,这样j归0, i增加
			i ++, j ++;
		else j = ne[j] //失配则回溯j进行匹配。
		if(j == s.size())//匹配成功
		{
		 	ans.push_back(i - j);
            j = ne[j];//回溯j继续匹配。
		}
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

如何求解next数组

参考1
参考2
next数组中储存的是这个字符串前缀和后缀中相同字符串的最长长度
在这里插入图片描述
如图,next数组中存下了这个字符之前的字符串前后缀的最大公共长度,而j进行回溯也按这个数组。
在这里插入图片描述

有这样一个规律,一个串的最小循环节长度:len - next[len]。可以从回溯的角度理解,当匹配完成以后,j会回溯到最后一个循环节进行再次匹配,len减去next[len]就是一个循环节的长度。
一定要注意前提是 len % (len - next[len]) == 0,否则不存在循环周期。

题目

831. KMP字符串

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int n, m;
string s, p;
int ne[100010];
vector<int> ans;
void getNext(string s)
{
    ne[0] = -1;
    int i = 0, j = -1;//j在前 i在后
    while(i < s.size())
    {
        if(j == -1 || s[i] == s[j])
        {
            ne[++ i] = ++ j;// 如果s[i] == s[j] 则 next[i + 1] = next[i] + 1
        }
        else j = ne[j];//如果不相等,那么回溯j继续匹配
    }
}
void KMP(string s, string p)
{
    int i = 0, j = 0;
    getNext(s);
    while(i < p.size())
    {
       // cout << i <<" "<< j << endl;
        if(j == -1 || p[i] == s[j])//第一个就失配,i增加, j归0, 匹配也同时增加  
            i ++, j ++;
        else j = ne[j];//回溯j
        if(j == s.size())//匹配完成
        {
            ans.push_back(i - j);
            j = ne[j];
        }
    }
}
int main()
{
    cin >> n >> s >> m >> p;
    KMP(s,p);
    for(auto x : ans)
    {
        cout << x << " ";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

KMP算法实现就是字符查找问题,假设现在有这样一个问题,有一个文本串S和一个模式串P,要查找P在S中的位置,即从文本串S中找出模式串P第一次出现的位置。

如何比较字符串

  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];
		  //继续匹配 
		  }
       }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

如何求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失配后应该如何跳 
       }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

其他应用

求字符串周期

思路:利用kmp数组的含义来解。
kmp数组中储存的是这个字符串前缀和后缀中相同字符串的最长长度
1.一个串的最小循环节长度:len - kmp[len]。
2.若len%(len-kmp[len]) == 0, 则这个字符串的最小周期为len-kmp[len]。一定要注意前提是 len % (len - next[len]) == 0,否则不存在循环周期。


char a[100];
int kmp[100];
main(void)
{
	int n;
	cin>>n;
	_0for(p,n)
	{
		cin>>a+1;
		int j=0;
		int flag=0;
		int l=strlen(a+1);
		memset(kmp,0,sizeof(kmp));
		for(int i=2;i<=l;i++)
		{
			while(j>0&&a[j+1]!=a[i])
			j=kmp[j];
			if(a[j+1]==a[i])
			{
				j++;
			}
			kmp[i]=j;
		}
		if(l%(l-kmp[l])||kmp[l]==0)
		cout<<l<<endl;
		else
		{
			cout<<l-kmp[l]<<endl;
		}
		if(p!=n-1)cout<<"\n";
	}
}




  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

字符串求前后缀

题意
The little cat is so famous, that many couples tramp over hill and dale to Byteland, and asked the little cat to give names to their newly-born babies. They seek the name, and at the same time seek the fame. In order to escape from such boring job, the innovative little cat works out an easy but fantastic algorithm:

Step1. Connect the father’s name and the mother’s name, to a new string S.
Step2. Find a proper prefix-suffix string of S (which is not only the prefix, but also the suffix of S).

Example: Father=‘ala’, Mother=‘la’, we have S = ‘ala’+‘la’ = ‘alala’. Potential prefix-suffix strings of S are {‘a’, ‘ala’, ‘alala’}. Given the string S, could you help the little cat to write a program to calculate the length of possible prefix-suffix strings of S? (He might thank you by giving your baby a name:)

Input
The input contains a number of test cases. Each test case occupies a single line that contains the string S described above.

Restrictions: Only lowercase letters may appear in the input. 1 <= Length of S <= 400000.

Output
For each test case, output a single line with integer numbers in increasing order, denoting the possible length of the new baby’s name.

Sample Input
ababcababababcabab
aaaaa

Sample Output
2 4 9 18
1 2 3 4 5
要求出前后缀相同可能的长度
思路:最长当然是l,求得样例1的kmp
0 0 1 2 0 1 2 3 4 3 4 3 4 5 6 7 8 9
j=L
step 1 :
ababcababababcabab
ababcababababcabab
前后移位j-kmp[j],依然匹配而且长度为kmp[j],j=kmp[j]=9(有点周期的感觉)
step2:
ababcabab
ababcabab
step3
前后移位j-kmp[j],依然匹配而且长度为kmp[j],j=kmp[j]=4
abab
abab
step4
前后移位j-kmp[j],依然匹配而且长度为kmp[j],j=kmp[j]=2
ab
ab
最后kmp[j]=0;
这样就求得了所有前后缀相等可能的长度。

int kmp[400005];
char s[400005];
int ans[400005];
main(void)
{	
	while(cin>>s+1)
	{
		int j=0;
		int l=strlen(s+1);
		for(int i=2;i<=l;i++)
		{
			while(j>0&&s[j+1]!=s[i])j=kmp[j];
			if(s[j+1]==s[i])j++;
			kmp[i]=j;
		}
		int cnt=0;
		j=l;
		while(kmp[j]!=0)
		{
			ans[++cnt]=kmp[j];
			j=kmp[j];
		}
		for(int i=cnt;i>=1;i--)
		printf("%d ",ans[i]);
		printf("%d\n",l);
	}
	
}




  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号