1. kmp算法要解决什么问题
有两个字符串str1和str2,现在要求查找str1中是否包含和str2相同的子串,如果存在,返回str1中子串的起始索引,如果没有,返回-1。
2. 暴力的解法
比如str1为 abcabcqwerty
str2为 abcq
暴力解法为:
首先,将str1和str2起始位置"对齐"
a b c a b c q w e r t y
a b c q
然后逐个字符比较。发现在str1[3]和str2[3]位置处发生了不匹配。
于是将str2的起始位置向后错开一位
a b c a b c q w e r t y
a b c q
然后接着逐个字符比较。
3. 为什么暴力解法不是最优解
还是举个栗子:
str1 : a b c a b c a b c k
str2 : a b c a b c k
很显然,第一次发生不匹配的时刻发生在这种情况
a b c a b c a b c k
a b c a b c k
a不等于k
那么按照暴力解法,接下来的
a b c a b c a b c k
a b c a b c k
和
a b c a b c a b c k
a b c a b c k
都要进行判断,但是我们很清楚,这两种情况下根本不可能完成匹配,所以优化的思路就在于如何在匹配过程中抛弃掉这些不可能完成匹配的过程。
4. 最大前缀和最大后缀的概念
继续使用上面的例子。
str1 : a b c a b c a b c k
str2 : a b c a b c k
说明什么是最大前缀和最大后缀
我们将求str2[6]的最大前缀和最大后缀
str2[6]之前的字符串为a b c a b c 我称之为 strtemp
在这种情况下长度为1的前缀是 a
长度为1的后缀是 c
长度为2的前缀是 ab
长度为2的后缀是 bc
长度为3的前缀是 abc
长度为3的后缀是 abc
长度为4的前缀是 abca
长度为4的后缀是 cabc
长度为5的前缀是 abcab
长度为5的后缀是 bcabc
在此人为规定,前缀不看最后一个字符,后缀不看第一个字符。(因为如果考虑这种情况,长度为6的时候前后缀为本身,一定是相等的,没有意义,所以不考虑)
这时我们发现当长度为3 的时候,前后缀相等,所以我们说str2[6] 的最大相等前后缀长度为3.
5. 如何使用最大相等前后缀长度来加速匹配过程
继续使用上面的例子。
str1 : a b c a b c a b c k
str2 : a b c a b c k
发现在str2[6]位置发生了不匹配现象。
已知str2[6]的最大相等前后缀长度为3.
也就是说,我们已经知道在str2[6]之前的字符和str1对应的字符应该是一样的,只是到6这个位置才发生了不同。
所以在这里我直接将str2向后移动3个位置(也就是移动了最大相等前后缀长度个位置)
得到的结果是
a b c a b c a b c k
a b c a b c k
这样操作就省略掉了暴力解法中不可能的匹配过程。
可以简单的反证一下。
如果之前那么按照暴力解法
a b c a b c a b c k
a b c a b c k
和
a b c a b c a b c k
a b c a b c k
这两个步骤如果有可能匹配成功的话
说明长度为5的最大前后缀或者长度为4的最大前后缀应该会相等。
也就是说,加速的思路就是寻找最大相等的前后缀,将前缀移动到后缀的位置,这样就省略了很多不可能的匹配过程,并且接下来,我可以直接匹配str2[3]和str1对应位置的匹配情况了。
6. 最大相等前后缀长度——next数组
在第4部分提出的最大相等前后缀长度,是一种易于理解但是操作繁琐的方式,在实际的加速过程中,并不是这样求的。
在实际的kmp算法中,我们将这个最大相等前后缀长度,命名为next数组。
举个例子
str2 为:a b a c a b a b a
next[i]对应str2[i]
首先说明在next数组中,next[0]=-1,next[1] = 0,这是人为规定,至于为什么一会再说。
接下来假设我们已知next数组中,next数组的0~6为[-1,0,0,1,0,1,2]。
那么如何根据已知求next[7]呢?
答案是根据next[6]求next[7]。
因为next[6] = 2,说明在str2[5] == str2[1],str2[4] == str2[0].
那么接下来,发现str2[6] == str2[2],所以next[7]的值将是next[6]+1;
但是如果str2[6] != str2[3]怎么办呢?
比如
可以看到如图这样的情况,在求a位置的next的时候,发现 c位置和b位置不相等(假设两个黄圈是b位置的最大相等的前后缀,绿圈是c位置的最大相等前后缀)
很显然,对应颜色的圈里面的内容应该相等。
实际上,后面的黄圈中,应该也有对应的两个绿圈存在(并且完全相等)
那么如果c!=b,且c`和b相等,根据对应关系,可以求得a位置的next就等于c位置next+1.
如果c`仍然不等于b那么就可以迭代下去,寻找c``,c```等,只要找到一个和b相等的就成功的确定了这个位置的next值,如果迭代到str2[0]的位置,仍然没有(也就是next值搜到-1了)那么说明next 等于 0。
好了,啰嗦了一大堆,下面整理一下求法。
1.next[0] = -1,next[1] = 0,人为规定。
2.已知next[i],求next[i+1];
3.将str2[i]和str2[next[i]]比较。
具体如下
用c表示str2[next[i]]
nc表示next[i]
while(nc != -1 && str2[i-1] != c)
{
nc = next[nc];
if(nc == -1) break;
c = str2[nc];
}
next[i+1] = nc+1;
这样就是求解next数组的基本思路。
7. 代码
/*
vs2012 c++语言
代码有些啰嗦,但是严格按照上述分析过程完成
完美代码实现在互联网上很多,就不写了。
测试用例:
string str1 = "12345abacababa2134567";
string str2 = "abacababa";
结果:5
结果正确。
*/
vector<int> getnext(string& str2)
{
vector<int> next;
next.push_back(-1);
next.push_back(0);
int length = str2.length();
for(int i = 1;i<length-1;++i)
{
char c = str2[next[i]];
int nc = next[i];
while(nc != -1 && str2[i] != c)
{
nc = next[nc];
if(nc == -1) break;
c = str2[nc];
}
next.push_back(nc+1);
}
return next;
}
int kmp(string& s1,string& s2)
{
int it1 = 0,it2 = 0;
int ret = -1;
vector<int> next = getnext(s2);
while(it1<s1.length() && it2 < s2.length())
{
if(s1[it1] == s2[it2])
{
it1++;
it2++;
continue;
}
else
{
if(next[it2] == -1)
it1++;
else
it2 = next[it2];
}
}
if(it2 == s2.length())
return it1-it2;
else
return -1;
}
int main()
{
string str1 = "12345abacababa2134567";
string str2 = "abacababa";
cout<<kmp(str1,str2);
system("pause");
return 0;
}
时间: 2024-10-21 04:53:30