C++实现KMP

1、此文总结以上两篇博文,然后给出自己的理解

c/c++程序之_KMP字符串模式匹配详解(非常不错的详解)

【经典算法】——KMP,深入讲解next数组的求解

2.第一种next数组的求解思路

  通过上文完全可以对kmp算法的原理有个清晰的了解,那么下一步就是编程实现了,其中最重要的就是如何根据待匹配的模版字符串求出对应每一位的最大相同前后缀的长度。我先给出我的代码:

vector<int> getNext(string s) {
	vector<int> ret(s.length(), 0);
	if (ret.empty()) return ret;
	for (size_t k1(1), kt(0); k1 < ret.size(); ++k1) {
		while (kt && s[kt] != s[k1]) kt = ret[kt - 1];
		if (s[kt] == s[k1]) ++kt;
		ret[k1] = kt;
	}
	return ret;
}

vector<int> kmp(string s1, string s2) {
	vector<int> ret;
	int sl1(s1.length()), sl2(s2.length());
	if (!(sl1 && sl2)) return ret;
	auto nv2(getNext(s2));
	for (int k1(0), k2(0); k1 < sl1;) {
		if (s1[k1] == s2[k2]) ++k1, ++k2;
		else if (k2) k2 = nv2[k2 - 1];
		else ++k1;
		if (k2 == sl2) ret.push_back(k1 - k2), k2 = nv2[k2 - 1];
	}
	return ret;
}



解释getNext:

(1)初始化向量ret; 

(2)已知前一步计算时最大相同的前后缀长度为kt(kt>0), 即s[0]···s[kt-1], 从第二字符开始,比较s[k1]与s[kt];

如果s[k1]等于s[kt],那么很简单跳出while循环; 且匹配长度比之前的多1 ; 

如果不等, 那么我们应该利用已经得到的ret[0]···rett[kt-1]来求s[0]···s[kt-1]这个子串中最大相同前后缀。 由于s[kt] 已经和s[k1]失配了,而且s[k1-k] ··· s[k1-1]又与s[0] ···s[kt-1]相同,所以s[0]···s[kt-1]这么长的子串是用不了了,并且s[0]···s[k1]的最大相同后缀一定短于kt,也即要找个同样也是s[0]打头s[kt'-1]结尾的子串即s[0]···P[kt'-1](jkt'=next[kt-1]),看看它的下一项s[kt']是否能和s[k1]匹配。


解释KMP调用getNext:

调用根据next向量的值,调整比较的字符,并将匹配结果以向量的形式输出。

(1)当s[k1]==s[k2] 显然k1, k2均递增;

(2)否则(即s[k1]!=s[k2])此时需调整k2; 若k2为0, 表示只有为比较的第一个字符就不符, 因此只增加k1; 若k2>0, 只调整k2

(3) 当k2比完最后最后一个字符时, 表示匹配, 添加到结果向量, 并进行下一次查找




3.第二种next数组的求解思路

还可以这样构造next向量和kmp函数,同样我先给出我的代码:


vector<int> getNext2(string s) {
	vector<int> ret(s.length(), 0);
	if (s.length() < 1) return ret;
	ret[0] = -1;
	for (int k1(1), kt(-1); k1 < s.length(); ++k1) {
		while (-1 < kt && s[kt] != s[k1 - 1]) kt = ret[kt];
		++kt;
		ret[k1] = s[kt] == s[k1] ? ret[kt] : kt;
	}
	/*
	for (int k1(0), kt(-1); k1 < s.length() - 1;) {
		if (kt < 0 || s[k1] == s[kt]) {
		++kt; ++k1;
		ret[k1] = s[kt] == s[k1] ? ret[kt] : kt;
	}
	else
	kt = ret[kt];
	}
	*/
	return ret;
}

vector<int> kmp2(string s1, string s2) {
	vector<int> ret;
	int sl1(s1.length()), sl2(s2.length());
	if (!(sl1 && sl2)) return ret;
	auto nv2(getNext2(s2));
	for (int k1(0), k2(0); k1 < sl1;) {
		if (-1 < k2 && s1[k1] == s2[k2]) ++k1, ++k2;
		else if (k2 < 0) k2 = 0, ++k1;
		else k2 = nv2[k2];
		if (k2 == sl2) ret.push_back(k1 - k2), k2 = nv2[k2 - 1];
	}
	return ret;
}


解释getNext2:

基本原理相同,不同的只是细节。在getNext里面,ret[k1] 指的是若k1+1位置的字符不同时,下次比较的字符位置,也即

s[0]···s[k1]的最大相同后缀的长度。而在getNext2里面,ret[k1]指若k1位置的字符不同时,下次比较的字符位置,这种处理方式要考虑的因素稍微复杂一些(要考虑s[ret[k1]] 与 s[k1]是否相同的情况)。因此这样:

ret[k1] = p : k1的前面p个字符与开头的p个字符相等,且k1处的字符和p处的字符不同。此满足该条件的最大的p为ret[k1]的值

ret[k1] = -1 : 对于任意p>=0,当k1的前面p个字符与开头的p个字符相等时,k1处的字符和p处的字符也相同。

因此ret[0] = -1也属于此类;或者s1[0] == s1[k1] ,此时p取0即可。

(1)初始化向量ret; 

(2)已知前一步计算时该字符(即s[k1-1])前的最大相同的前后缀长度为kt(kt>=0), 即s[0]···s[kt-1], 从第二字符开始,比较s[k1-1]与s[kt];

如果s[k1-1]等于s[kt],那么很简单跳出while循环; 且kt+1为k1前的最长重叠字符数 ; 
如果不等, 那么我们应该利用已经得到的ret[0]···ret[kt-1]来求。由于kt>=0,表示s[0]···s[kt-1] 与s[k1-1-kt]···s[k1-1-1]相同;所以,k1前的一定比kt短。再根据ret[kt]的意义,若令kt' = ret[kt],则表示
s[0]···s[kt'-1] 与s[kt-kt']···s[kt-1]相同, 于是s[0]···s[kt'-1] 与s[k1-1-kt']···s[k1-1-1]相同,因此只要满足s[kt']==s[k1-1]的最大kt',再+1就是k1前的最长重叠字符数。

若上一步找到的k1前的最长重叠字符数为kt,若s[kt]!=s[k1]时,显然ret[k1] = kt

若s[kt]==s[k1] ,根据ret[kt]的定义,ret[k1] = ret[kt]


解释KMP2调用getNext2:

调用根据next向量的值,调整比较的字符,并将匹配结果以向量的形式输出。

(1)当s[k1]==s[k2] 显然k1, k2均递增;
(2)否则(即s[k1]!=s[k2])此时需调整k2; 若k2为
-1, 表示完全不符, 因此增加k1, k2=0; 若k2>=0, 只调整k2
(3) 当k2比完最后最后一个字符时, 表示匹配, 添加到结果向量, 并进行下一次查找



posted @ 2017-05-12 19:51  lmjy  阅读(915)  评论(0编辑  收藏  举报