在一个字符串中查找一个子字符串有很多方法,最简单容易想到的算法便穷举,但这样的情况下算法复杂度为O(m * n)。
而KMP是一种最常见的改进算法,它可以在匹配过程中失配的情况下,有效地多往后面跳几个字符,加快匹配速度。
KMP算法中有个数组,叫做前缀数组,也有的叫next数组,每一个子串有一个固定的next数组,它记录着字符串匹配过程中失配情况下可以向前多跳几个字符,当然它描述的也是子串的对称程度,程度越高,值越大,当然之前可能出现再匹配的机会就越大。
先来个图表来表示一下next数组的求解方式:
可是如何去求next数组每个数组元素的值呢?
-1. 逐个查找对称串
比如i = 0时,串对应的字符是a,不存在对称,所以next[0] = 0;
同样的,i = 1, 2, 3时 ag, agc, agct均不存在对称,所以next[1…3] = 0;
而i = 4时,agcta串的前缀a和后缀a相等,长度为1,所以next[4] = 1;
再比如i = 6时,agctagc的前缀agc和后缀agc相等,长度为3,所以next[6] = 3;
而i = 7时,agctagca只有前缀a和后缀a相等,长度为1,所以next[7] = 1;
之后,便可以次类推。
既然已经可以人工的推出这个next数组的值了,那么编程应该也可实现了:
编程思想如下:
1) 当前面字符的前一个字符的next值为0时,只要将当前字符与子串第一个字符进行比较。前面都是0,说明都不对称了,到当前位置时只是多加了一个字符,要对称的话最多是当前的和第一个对称。此时,如果当前字符和第一个相等,那个该位置的next值为1,否则为0;
2) 依此,可以总结一个规律,不仅当前一个字符的next值为0时,如果前面一个字符的next值是1,那么我们就把当前字符与子串第二个字符进行比较,因为前面的是1,说明前面的字符已经和第一个相等了,如果这个又与第二个相等了,说明next就是2了;
3) 同2)中所说如果一直相等,就一直累加。
说到这里,上面情况理解起来应该没什么问题,现在的问题是如果遇到下一个不等,该怎么进行处理?
比如i = 14时,该位置的前一个字符的next值为7,但该位置的next值按照最大公共前后缀(agct)的规则来看,长度为4,其next的值为4。
在这里相当于重新寻找i = 14,更小的对称性,那么:
1)如果要存在对称性,那么对称程度肯定比前一个字符 的对称程度小,所以要找个更小的对称;
2)要找更小的对称,必然在对称内部还存在子对称,而且当前字符还必须紧接着在子对称串之后;
所以,agctagc 和 agctagc对称,agc、agc、agc、agc、对称。
算法实现与测试:
#include<iostream>
#include <string>
#include <map>
#include <vector>
#include <cmath>
#include <bitset>
#include <stack>
using namespace std;
//求next数组
void create_next(const string &seq,int *next)
{
int k;//最大前后缀长度//
int len = seq.size();
//字符串的第一个字符的最大前后缀长度为0
next[0] = 0;
for (int i = 1, k = 0; i < len; ++ i)
{
//求出seq[0]···seq[q]的最大的相同的前后缀长度k
while(k > 0 && seq[i] != seq[k])
k = next[k-1];
//如果相等,那么最大相同前后缀长度加1
if (seq[i] == seq[k])
{
k++;
}
next[i] = k;
}
}
//KMP算法实现
int kmp(const string &str,const string &sub,int *next)
{
//当前已经匹配的串长度//
int q;
int n = str.size();
int m = sub.size();
create_next(sub,next);
int i;
for (i = 0, q = 0; i < n; ++ i)
{
while(q > 0 && sub[q] != str[i])
q = next[q-1];
if (sub[q] == str[i])
{
q++;
}
if (q == m)
{
break;
}
}
return i - m + 1;
}
int main()
{
string str, sub;
cin >> str >> sub;
int *next = new int[sub.size() + 1];
cout << "start position : " << kmp(str, sub, next) << endl;
system("pause");
return 0;
}
测试结果: