字符串匹配算法之KMP

【kmp算法思想】

说起字符串查找,大家肯定能理解朴素的查法,就是以 S 每个字符为开头与 W 比较。O(m*n)

这种方法是基于回溯,但是这种回溯每次出现不匹配就要重头比较,没能很好的利用不匹配点之前已经进行的匹配比较,有很多回溯点可以直接判断是不可能匹配成功的,因此KMP变基于对子串的提前分析,在子串的位置j出现不匹配时,直接获得子串后移长度,减少了不必要的回溯,其效率可达到性O(m+n)。看下图,现在字符串查找过程中出现了不匹配:

按朴素算法,这里 W 应该右移一位然后重新匹配。但是,目前为止,蓝色色部分在两个字符串中都是已知的。我们就不用一个个比较了,直接滑动,跳过不匹配的就好:

《字符串匹配算法之KMP》

以上就是 KMP 算法的思想,就是针对每个不匹配点,在子串开始位置到不匹配点位置,找到最长的滑动区间。

【数学表达】

针对字符串P,定义next特征函数如下:

《字符串匹配算法之KMP》

其中k即为最长的相等前缀长度,所以当S[i]!=P[j]时,我们将子串P向后滑动j-k位,此时源串指针不动,子串指针指到p[next[j]]即p[k+1],接下来只需要继续比较S[i]和p[next[j]],因为根据定义,前面的字符肯定相同。

【next数组的求解】

next数组的求解可以使用递推的方式。

设已知next[j]=k+1,

则有p[0]p[1]…p[k]=p[j-1-k]p[j-k]…p[j-1],现在要求next[j+1],则:

(1)若p[0]p[1]…p[k]p[k+1]=p[j-1-k]p[j-k]…p[j-1]p[j] 即p[k+1]=p[j] 即 p[next[k]]=p[j],则next[j+1]=next[j]+1

(2)若p[next[j]]!=p[j],则next[j]肯定小于next[j]。根据KMP最长相等前缀、后缀的思路,我们对p[0]~p[k]再次找最长前缀,根据下图所示:

《字符串匹配算法之KMP》

因为part1=part2=part3=part4,k=next[j],k=next[k], 

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

否则对part1继续上述的处理,直到k=-1.

【代码实现】

#include <string>
#include <vector>
#include <iostream>
using namespace std;
class KMP_Algorithm
{
public:
	KMP_Algorithm(){};
	virtual ~KMP_Algorithm(){};
	static int findSubstr(string& source, string& subStr);
	static void initNext(string& str, vector<int>& Next);
	static void KMP_Algorithm_test();
};

方法实现:

#include "KMP_Algorithm.h"


int KMP_Algorithm::findSubstr(string& source, string& subStr)
{
	if (0 == source.length() || 0 == subStr.length()) return -1;
	if (source.length()<subStr.length())
	{
		cerr << "[KMP_Algorithm] source string is shorter than subStr!" << endl;
		return -1;
	}
	vector<int> Next(subStr.length(), 0);
	KMP_Algorithm::initNext(subStr, Next);
	//Next[0]=-1; Next[j]=k+1, subStr[Next[j]] will compare with the last position£¬0<=k<j-1, p(0,k)=p(j-1-k,j-1);Next[j]=0,other
	int i = 0, pos = 0;
	int SLen = source.length();
	int len = subStr.length();
	while (i<SLen && pos<len)
	{
		if (pos == -1||source[i] == subStr[pos])
		{
			i++;
			pos++;
		}
		else
		{
			pos = Next[pos];
		}
	}

	if(pos == subStr.length())
	{
		return i - subStr.length();
	}
	else return -1;
}

void KMP_Algorithm::initNext(string& str, vector<int>& Next)
{
	if (str.length() == 0) return;
	Next[0] = -1;
	int j = 0, k = -1, Len = str.length();
	while (j<Len)
	{
		if (k == -1 || str[j] == str[k])
		{
			j++;
			k++;
			if (j<Len)
				Next[j] = k;
		}
		else k = Next[k];
	}
	return;
}

void KMP_Algorithm::KMP_Algorithm_test()
{
	string s1;
	string s2;
	while (1)
	{
		cout << "[Enter source string]:";
		getline(cin, s1);
		cout << "[Enter sub string ]:";
		getline(cin, s2);
		cout << "[pos]" << KMP_Algorithm::findSubstr(s1, s2) << endl;;
	}

}

【测试】

#include "KMP_Algorithm.h"
void main()
{
	KMP_Algorithm::KMP_Algorithm_test();
	int a;
	cin >> a;

}

《字符串匹配算法之KMP》

点赞