字符串匹配问题描述:
定义:如果给定字符串T以及字符串S,|T|>=|S|,存在正整数i,使得T[i, i+|S|] = S,则称S为T的子串。
给定一个模式字符串pat,以及一个字符串str,问str是否为pat的字串,如果是,给出子串在pat中开始的位置。
一个很直观的暴力解决方法是:
定义两个指针i,j。
0)初始时,i指向pat[0],j指向str[0]。
1)检查str是否为pat以i为起始位置的字串,如果是,则返回i。
2)i = i+1,j = 0。如果i > i -len(str),则返回-1,表示没有找到字串,否则回到1)。
这个方法虽然直观,但是速度会很慢。原因是没有利用已经检索过的指针j之前的字符串信息。D.E.Knuth,J.H.Morris和V.R.Pratt同时(这也能同时?)发明了KMP算法用来解决字符串匹配问题,人们用他们三个人的名字的首字母命名了这个算法。
KMP算法思路:
1)构建一个next数组。数组值为整数,长度与str相同。next的含义是,字符串str[0,..j-1]中,前缀等于后缀的最长长度。
2)每当匹配失败pat[i] != str[j]的时候,令j = next[j],重新匹配。
这里涉及到的关键就是next数组的构建(getnext函数)。下面进行简单说明:
1)规定next[0] = -1,方便进行边界控制
2)int k,j,k和j的含义是,当前next[j]=k。下一步要计算next[j+1]
3)当str[j] == str[k]的时候。由k,j的含义可以知道,str[0,..,k-1] = str[j-k,j-1],则str[0,k] = str[j-k,j],也就是说,next[j+1] = k+1。即next[j+1] = next[j] + 1。
4)当str[j] != str[k]的时候。我们的目标是要找到str[0, j]的对应的前缀等于后缀的最长长度。
已经知道,str[0, k-1] = str[j-k, j-1],str[k] != str[j],也就是说,最长前缀等于后缀,对于str[0,k]和str[j-k,j]已经没有可能了。这样,我们需要调整k,减少这个最长前缀。下一个可能,就是str[0, next[k]]。(这里可以手工画图,好好推导)
vector<int> getnext(const string& str){
vector<int> next(str.size(), 0);
next[0] = -1;
int k = -1, j = 0;
while(j < str.size()){
if(k == -1 || str[j] == str[k]) {
next[++j] = ++k;
} else {
k = next[k];
}
}
return next;
}
int kmp(const string& pat, const string& str){
vector<int> next = getnext(str);
int i = 0, j = 0, l = str.size();
while(j < l){
if(j == -1 || pat[i] == str[j]) {
i++;
j++;
} else {
j = next[j];
}
}
return (j == l) ? i - l : -1;
}