zoukankan      html  css  js  c++  java
  • kmp匹配

    字符串匹配算法(暴力匹配与kmp算法)

    1.暴力匹配算法

    顾名思义,暴力匹配算法就是用for循环暴力匹配,将两个字符串逐一匹配,一直匹配到两个字符串相等或者直至字符串匹配结束,其时间复杂度为O(mn),其基本模板为:

    int ViolentMatch(string s1,string s2)
    {
    	int len1 = s1.size();
    	int len2 = s2.size();
    	int i = 0,j = 0,temp;
    	while (i < len1 && j < len2)
    	{
    		temp=i;
    		if (s[i] == p[j])	//匹配成功
    		{  
    			i++;
    			j++;
    		}
    		else
    		{
    			i=temp+1
    			j = 0;
    		}
    	}
    	if (j == len2)		  //如果匹配成功
    		return i - j;
    	else
    		return -1;
    }
    

    其完整代码如下:

    #include<iostream>
    #include<string>
    
    using namespace std;
    int Violetmatch(string s1, string s2)
    {
    	int len1, len2, temp;
    	len1 = s1.size();
    	len2 = s2.size();
    	int i = 0, j = 0;
    	while (i < len1&&j < len2)
    	{
    		temp = i;
    		if (s1[i] == s2[j])
    		{
    			i++;
    			j++;
    		}
    		else
    		{
    			i = temp + 1;
    			j = 0;
    		}
    	}
    	if (j == len2)
    		return i - j;
    	else
    		return -1;
    }
    
    int main()
    {
    	string s1, s2;
    	cin >> s1 >> s2;
    	int ans;
    	ans=Violetmatch(s1, s2);
    	if (-1 == ans)
    		cout << "not find" << endl;
    	else
    		cout << "Match successfully in the " << ans+1 <<"-th of s1"<< endl;
    	return 0;
    }
    

    2.kmp算法

    先上完整代码:

    #include<iostream>
    
    using namespace std;
    const int N = 100005;
    char s[N], p[N];                 //s为主串,p为模式子串
    int nnest[N];                   //nest数组
    void getnnest(char *p);
    int kmp(char *s, char *p);
    
    int main()
    {
    	cin >> s >> p;
    	cout << kmp(s, p) << endl;
    	return 0;
    }
    
    void getnnest(char *p)
    {
    	int j = 0, k = -1;
    	nnest[0] = -1;
    	int len = strlen(p);
    	while (j < len)
    	{
    		if (-1 == k || p[j] == p[k])
    		{
    			j++;
    			k++;
    			nnest[j] = k;
    		}
    		else
    			k = nnest[k];
    	}
    }
    
    int kmp(char *s, char *p)
    {
    	getnnest(p);
    	int lens = strlen(s);
    	int lenp = strlen(p);
    	int k = 0;
    	for (int i = 0; i < lens; i++)
    	{
    		while (k&&s[i] != p[k])
    			k = nnest[k];
    		if (s[i] == p[k])
    			k++;
    		if (k == lenp)
    			return i - lenp + 1;
    	}
    	return -1;
    }
    

    注意不要使用string类型,因为string类型的字符串比char数组耗时,比赛时容易超时!

    参考来源:CSDN的一位大佬的博客:https://blog.csdn.net/v_july_v/article/details/7041827

    这里是匹配过程的详解

    kmp用来处理长字符串或则数据量非常大的字符串时非常实用,因其时间复杂度只有O(m+n),其算法流程如下:

    假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置

    如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++,继续匹配下一个字符;

    如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]。此举意味着失配时,模式串P相对于文本串S向右移动了j - next [j] 位。

    换言之,当匹配失败时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的next值,即移动的实际位数为:j - next[j],且此值大于等于1。

    故问题转化为如何求next数组,next数组可以通过两个方式求得:

    1.若要求next[j]的值,我们只需知道next数组的前面的值,再通过运算求得;

    2.我们还可以根据前面j-1个字符串的最长前缀后缀,再讲所求的的各个最长前缀后缀的值向后移一位,再讲next[0]=-1赋初值,即可快速求得各个next数组的值。

    寻找最长前缀后缀即相应next数组的值

    如果给定的模式串是:“ABCDABD”,从左至右遍历整个模式串,其各个子串的前缀后缀分别如下表格所示:

    所以我们可以将最长前缀后缀的各个值向后移一位,再讲初值赋为-1,即可得到next数组,例如若对于给定的模式串:ABCDABD,它的最大前缀后缀长度表及next数组分别如下:

    接下来我们就可以根据next数组求出模式字符串向右移动的位置,即

    模式串向右移动的位数 = 失配字符所在的位置 - 失配字符对应的next值

    或者说根据最大前缀后缀的长度值来说

    模式串向右移动的位数 = 已经匹配的字符数 - 失配字符的上一位字符的最大前缀后缀长度值

    通过代码递推计算next数组

    1.如果对于值k,已有p0 p1 ... pk-1 = pj-k pj-k+1, ..., pj-1,相当于next[j]=k;其原因是这里相当于p[j]之前的模式串子串中,有长度为k的相同前缀和后缀;

    2.下面的问题是:已知next[0, ..., j],如何求出next[j + 1]呢?这里有这么几个规律:

    若p[k] == p[j],则next[j+1]=next[j]+1=k+1;

    若p[k]≠p[j],如果此时p[next[k]]==p[j],则next[j+1]=next[k]+1,否则继续递归前缀索引k=next[k],而后重复此过程。 相当于在字符p[j+1]之前不存在长度为k+1的前缀"p0 p1, …, pk-1 pk"跟后缀“pj-k pj-k+1, …, pj-1 pj"相等,那么是否可能存在另一个值t+1 < k+1,使得长度更小的前缀 “p0 p1, …, pt-1 pt” 等于长度更小的后缀 “pj-t pj-t+1, …, pj-1 pj” 呢?如果存在,那么这个t+1 便是next[ j+1]的值,此相当于利用已经求得的next 数组(next [0, ..., k, ..., j])进行P串前缀跟P串后缀的匹配。

    如下图所示,假定给定模式串ABCDABCE,且已知next [j]=k(相当于“p0 pk-1” = “pj-k pj-1” = AB,可以看出k为2),现要求next[j+1]等于多少?因为pk =pj=C,所以next[j+1] = next[j]+1=k+1(可以看出next[j+1]=3)。代表字符E前的模式串中,有长度k+1 的相同前缀后缀。

    但如果pk != pj 呢?说明“p0 pk-1 pk”  ≠ “pj-k pj-1 pj”。换言之,当pk != pj后,字符E前有多大长度的相同前缀后缀呢?很明显,因为C不同于D,所以ABC跟ABD不相同,即字符E前的模式串没有长度为k+1的相同前缀后缀,也就不能再简单的令:next[j+1]=next[j]+1 。所以,咱们只能去寻找长度更短一点的相同前缀后缀

    结合上图来讲,若能在前缀“ p0 pk-1 pk ” 中不断的递归前缀索引k=next [k],找到一个字符pk’ 也为D,代表pk’=pj,且满足p0 pk'-1 pk' = pj-k' pj-1 pj,则最大相同的前缀后缀长度为k'+1,从而next[j+1]=k’+1=next[k']+1。否则前缀中没有D,则代表没有相同的前缀后缀,next[j+1]=0。

    但是这样还有一个问题,比如字符串abab,p[3]=b,s[3]=c失配p[next[3]]=p[1]=b再跟s[3]匹配时,必然失配,所以我们需要改进一下算法避免这种情况发生,即不允许p[j]=p[next[j]];

    综上,我们可以通过递推求得next数组,其代码如下所示:

    void GetNext(string s1,int next[])
    {
    	int len=s1.size();
    	next[0]=-1;
    	int k=-1;
    	int j=0;
    	while(j<len)
    	{
    		//p[k]表示前缀,p[j]表示后缀
    		if (k == -1 || p[j] == p[k]) 
    		{
    			++k;
    			++j;
    			if(p[j]!=p[k])
    				next[j] = k;		//为了防止重复
    			else
    				next[j]=next[k];         
    		}
    		else 
    		{
    			k = next[k];
    		}
    	}
    }
    
  • 相关阅读:
    how to uninstall devkit
    asp.net中bin目录下的 dll.refresh文件
    查找2个分支的共同父节点
    Three ways to do WCF instance management
    WCF Concurrency (Single, Multiple, and Reentrant) and Throttling
    检查string是否为double
    How to hide TabPage from TabControl
    获取当前系统中的时区
    git svn cygwin_exception
    lodoop打印控制具体解释
  • 原文地址:https://www.cnblogs.com/StungYep/p/12252096.html
Copyright © 2011-2022 走看看