在待匹配串中设置指针i,每匹配完一次模式串则往后移一位,该算法的时间复杂度是O(n*m),空间复杂度O(n+m)。
对于这种方法,我们注意到,很多需要匹配的操作实际上是不必要的,比如模式串abcde,待匹配串abcdeabcde,当匹配完b之后移动到匹配c,是完全没有必要的。
因此,为了避免这种情况,KMP算法应运而生。KMP算法对于一个字符串匹配的时间复杂度是O(n+m),空间复杂度是O(n+m)。
KMP算法是通过对模式串预处理,找出失配之后模式串的匹配指针移动的位置,来减少无意义的匹配。这个预处理产生的数组被称为next数组,也称fail数组(理解fail数组的机制就可以对AC自动机的fail指针有更为深刻的理解),以下统一称为fail数组。
设字符串的字符序号从1开始计数,i是字符串的前i个字符的前缀和后缀的最长相同字符(前后缀均不包括它本身)。
fail数组通过fail[i-1]的信息求解fail[i],利用模式串自己和自己匹配,求得fail数组。fail[0]表示空字符串的前缀和后缀的相同字符长度,显然没有意义,设为-1。设置指针j和k,j=-1,k=0。j为匹配字符位置指针,k为待匹配字符位置指针。然后,如果fail[i-1]=-1,就意味着k的位置为字符串的第一个字符位置,此时得到fail[i]=0;如果fail[i-1]>-1,即表明k的位置大于字符串的第一个字符位置,此时分两种情况:
一、如果两个指针对应的字符相等,则都可以向右移动一位,同时fail[i]=fail[i-1]+1;(若一直都匹配成功,fail[i-1]=j,然后++j)
二、如果两个指针对应的字符不相等,则j就要回溯,回溯到哪里呢,这时就要使用fail已经求出的信息了。假设在这之前均匹配成功,由fail数组的定义,字符串中0~fail[i]-1和i-fail[i]~i-1相等,且fail[i]和i所指的位置一定不相等,所以就要找到可能的更短的前缀与其匹配或者不匹配。此时,通过j=fail[j]回溯找到更短的匹配。为什么可以这么做呢?我们知道字符串中0~fail[i]-1和i-fail[i]~i-1相等,且此时fail[i]=j,当j回溯到fail[j]时,0~j-1子串仍与回溯前的子串的长度为j的后缀串相同,因此相当于是将待匹配串和模式串(其实都是模式串在自己和自己匹配)的已知的最大相同长度部分对齐,然后再进行匹配。
fail数组求解代码:
注:源码是命名为next数组,C++中next是一个已经被使用的名称,故不能使用next作为数组名。
void getnext(string &str)
{
memset(nextarray, 0, sizeof(nextarray));
int j = -1, k = 0;
nextarray[0] = -1;
while (k < str.size())
{
if (j == -1 || str[j] == str[k])
nextarray[++k] = ++j;
else
j = nextarray[j];
}
}
使用已经求得的fail数组对待匹配串求解,设置指针i为待匹配串中的字符位置,指针j为模式串中的字符位置,如果两个位置的字符相等,则指针均向后移一位,如果不相等,我们已经知道了j个字符相等,j=fail[j]就是相当于将模式串0~fail[j]子串和待匹配串以最大相同长度对齐,然后再继续匹配。
匹配代码:
bool kmp(string &a, string &b)
{
int i = 0, j = 0, ans = 0;
while (i < a.size())
{
if (j == -1 || a[i] == b[j])
++i, ++j;
else
j = nextarray[j];
if (j == b.size())
++ans, j = nextarray[j];
}
if (ans)
return true;
return false;
}
//返回ans即为出现次数
(待补充)
详见:https://blog.csdn.net/Aya_Uchida/article/details/88536050
题意:求不重叠子串个数。
题解:水题,直接套匹配模即可,但是j==b.size()里面的j=nextarray[j]改成j=0。
AC代码:
#include
#include
#include
using namespace std;
int nextarray[1005];
void getnext(string &str)
{
memset(nextarray, 0, sizeof(nextarray));
int j = -1, k = 0;
nextarray[0] = -1;
while (k < str.size())
{
if (j == -1 || str[j] == str[k])
nextarray[++k] = ++j;
else
j = nextarray[j];
}
}
int count(string &a, string &b)
{
int i = 0, j = 0, ans = 0;
while (i < a.size())
{
if (j == -1 || a[i] == b[j])
++i, ++j;
else
j = nextarray[j];
if (j == b.size())
++ans, j = 0;
}
return ans;
}
int main()
{
string a, b;
while (cin >> a && a != "#")
{
cin >> b;
getnext(b);
cout << count(a, b) << endl;
}
return 0;
}
题意:给你一个字符串,请问在该字符串末尾最少添加多少个字符,可以让这个字符串获得重复循环序列。
题解:fail数组的应用,对于一个字符串,设其长度是len,若fail[len]=0,则表明整个字符串不存在部分重复,直接输出原长,如果len%(len-fail[len])=0,即字符串本身就循环,len-fail[len]是最小周期,输出0,其他的,输出len-fail[len]-len%(len-fail[len])。即需要补充以达到字符串的最小完整周期的长度。
AC代码:
#include
#include
#include
using namespace std;
int nextarray[100005];
void getnext(string &str)
{
memset(nextarray, 0, sizeof(nextarray));
int j = -1, k = 0;
nextarray[0] = -1;
while (k < str.size())
{
if (j == -1 || str[j] == str[k])
nextarray[++k] = ++j;
else
j = nextarray[j];
}
}
void solve(string &str)
{
getnext(str);
int cir = str.size() - nextarray[str.size()];
if (nextarray[str.size()] == 0)
cout << str.size() << endl;
else if (str.size() % cir == 0)
cout << 0 << endl;
else
cout << cir - str.size() % cir << endl;
}
int main()
{
int n;
cin >> n;
string a;
while (n--)
{
cin >> a;
solve(a);
}
return 0;
}
题意:给出一个字符串,输出其所有循环节大于1的循环子串的串长度和循环节数量。
题解:求出fail数组之后遍历计算即可。
AC代码:
#include
#include
#include
using namespace std;
int nextarray[1000005];
void getnext(string &str)
{
memset(nextarray, 0, sizeof(nextarray));
int j = -1, k = 0;
nextarray[0] = -1;
while (k < str.size())
{
if (j == -1 || str[j] == str[k])
nextarray[++k] = ++j;
else
j = nextarray[j];
}
}
void solve(string &str, int kase)
{
cout << "Test case #" << kase << endl;
for (int i = 2; i <= str.size(); ++i)
{
if (nextarray[i] && i % (i - nextarray[i]) == 0)
cout << i << " " << i / (i - nextarray[i]) << endl;
}
cout << endl;
}
int main()
{
string a;
int n, kase = 0;
while (cin >> n && n)
{
cin >> a;
getnext(a);
solve(a, ++kase);
}
return 0;
}
详见:https://blog.csdn.net/Aya_Uchida/article/details/88559426
详见:https://blog.csdn.net/Aya_Uchida/article/details/88372228
详见:https://blog.csdn.net/Aya_Uchida/article/details/89187496