如有错误,敬请指出,谢谢~
1.概念介绍
前缀
后缀
公共前后缀
最长公共前后缀
2.next数组
详细解说
3.模式匹配
图演示
4.代码
1.概念介绍
给出一段主字符串S,一段模式串M,从S中找出与M匹配的字符串。
前缀、后缀:除字符串本身
前缀:从第一个字符开始,依次向后所组成的所有子符串(除掉最后一个字符)
后缀:以最后一个字符为结尾,依次向前组成的所有子字符串(除掉第一个字符)
公共前后缀:前缀和后缀一致
最长公共前后缀:公共前后缀中长度最长的字符串。
举例:对给定的字符串 : a b a a b b a
前缀有哪些: a 、ab 、aba、 abaa 、abaab 、abaaba
后缀有哪些: a 、ba 、bba 、 abaa 、aabba 、 baabba
2.next数组
next数组是针对模式串而言的
next数组含义: j+1位失配,j应该回到的位置。
模式串M:a b a a b b a b a a b
I字符前的字符的 最大公共前后缀长度
I = 0 首字符前面没有字符我们规定为 next[0] = -1 next数组
I = 1 a 0 next[1] = 0
I = 2 ab 0 next[2] = 0
I = 3 aba 1 next[3] = 1
I = 4 abaa 1 next[4] = 1
I = 5 abaab 1 next[5] = 1
I = 6 abaaba 2 next[6] = 2
I = 7 abaabab 3 next[7] = 3
I = 8 abaababa 2 next[8] = 2
I = 9 abaababaa 3 next[9] = 3
I = 10 abaababaab 4 next[10] = 4
aba最大公共前后缀长度为1,最大公共前后缀是a
abaa最大公共前后缀怎么找?
比上个子串aba新添加了一个字符a,将新添加的字符a与之前最大公共前后缀中的前缀a的下一个字符b比较,不一样,最大公共前后缀依然是a,长度还是1
abaab最大公共前后缀怎么找?
比上个字符串abaa新添加了一个字符b,将新添加的字符b与之前最大公共前后缀中的前缀a后一个字符b比较,一样,最大公共前后缀是ab,长度再加一,变成2.
下面开始写next数组,首先规定next数组第一位填-1,第二位填0
下标i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
模式串 | a | b | a | a | b | b | a | b | a | a | b |
Next[i] | -1 | 0 | 0 | 1 | 1 | 1 | 2 | 3 | 2 | 3 | 4 |
了解了next数组是怎样算出来的之后,那么来思考一下,怎样再用计算机的思维把这些算出来:
给定字符:a b a a b b b a
第一位字符规定 -1 ,
第二位字符规定0,因为前缀后缀不能是自身,第二位之前就一个字符,1个字符无前缀后缀,更无最大公共前后缀,所以当然是 0
对于next数组。我们只看每个字符串除最后一个字符外的最长公共前后缀,指定j为最长公共前后缀前缀的游标+1,j所指的是最长公共前后缀的下一个字符,若无前后缀,则j=-1,最长公共前后缀长度就为 j+1 = 0,
当 i = 0, next[0] = -1
当 i = 1,串为a b ,只看一号位之前的字符串a,j指向a,j = 0, next[1] = j = 0; b与a不一样,无公共前后缀, j= next[0] = -1 ,
下面将j看成一个学武功的初学者,要去比试武力,增加等级(寻找最大公共前后缀长度),i的等级是不断增加的,因为i一直在学武功,j有时也会偷个懒,没学武功就想和i比试(m[i] !=m[j],不匹配情况)如果m[i] == m[j],表明j 今天学武功的与i今天学武功一样,j没偷懒。
当 i = 2,a b a,二号位前面的字符串为a b , j指向a , j = 0, a与 b不一样,next[2] = j = 0,
比试一下,m[2] == m[0], i++, j++;二号位与0号位字符相同,升级,i++,j++, i = 3,标记等级(a b a a,三号位之前的字符串为 a b a , 由上面知,m[2] = m[0],找到一个相同的前后缀),next[3] = j =1;
接着往下找, m[3] != m[1],(b != a),叮当,不相等了,那之前的有相等的吗?于是就去找1号位之前的字符串 a,它的公共前后缀情况,,j = next[1] = 0,只有一个a,无最长公共前后缀,那三号位就和a比较试试,m[3] == m[0] ,一样,j很高兴,松了一口气,幸好这个招式之前学过,不然就等着被贬为凡胎,于是i++,j++,准备比较下一位,但先把等级升了再说,next[4] = j = 1;
之后又开始比试了,m[4] == m[1] (b == b),一样,兴奋,兴奋就加起来,i++,j++,升等级,next[5] = j = 2;
什么?有人不服,那就比试一场,m[5] != m[2] (b!=a)。果然,功力不够格,等着降级,i = 5,现在字符串为 a b a a b b ,j现在在第二级,第二级之前的字符有匹配的吗,来瞅瞅,next[2] = 0, 0呀,没有神仙/靠山(二号位之前无公共前后缀)来救j了,j的武力是虚假的,被发现了,又无靠山,犯欺君大罪,直接打回原形,j = next[j] = 0。J 不服,又回来比试了,m[5] != m[0](b!=a),再降级,j = next[0] = -1。
J = -1了,从凡胎开始修道,初生牛犊不怕虎,新的开始,当然来场比试,才够刺激。就凭这勇气,给j升级,j++,而i的功力在日益增加,i++,因为j刚开始无匹配,next[6] = j = 0,
开始比试,m[6] == m[0](a == a),匹配上了,兴奋,升级i++,j++,标记等级,next[7] = j = 1,
来劲了,继续比试,m[7] == m[1](b== b),成功匹配,兴奋,升级,i++,j++,快标记,next[8] =j = 2,
信心上来了,快来比试,m[8] == m[2],真棒,匹配成功,升级i++,j++,记功,next[9] = j = 3
胜利要来了,加油,继续比试,m[9] == m[3],(b==b)升级,i++,j++,next[10] = 4
bravo!
指向最大公共前后缀 前缀的下一个更好的理解(next[4]的计算遇到此情况):
给定字符串: a a b s a a t a a b s a a k a a b s a a t a a b s a a ax
计算x的next值,已知a的next值为13,表明a前面有长度为13最大公共前后缀。看x之前的子串最后一个字符是 a ,找 a 之前最大公共前后缀前缀的下一个值是k,字符k对应的下标就是13,这就是最大公共前后缀 前缀的下一个,k与a不一样,再找 k前子串中最大公共前后缀 前缀的下一个 t ,与 a 不一样,再往前找t前子串中最大公共前后缀 前缀的下一个字符 b,与 a不一样,再找b 之前的子串 aa中对应最大公共前后缀 前缀的下一个字符 a,与a一样,x的next值是2
(前缀后缀都是 a,到这又怎么看?
比如一串字符:a a a a a
Next值规定第一位、第二位字符分别是 -1 0,I = 2时,第三位字符 a的next值为 0 前面的字符串为 a a,最长公共前后缀为a,长度为1,注意前缀、后缀都是除了自身。)
得到了next数组,有什么用呢?
3.模式匹配
给出主串S:A B A A B A A B B A B A A A B A A B B A B A A B
模式串M: A B A A B B A B A A B
M遇到与S不匹配的字符时,就去找前面的字符串,找它们最大公共前后缀,找到之后直接将前缀移到后缀再进行比较,因为中间不会有与M匹配的字符串
理解这些,最后就是代码了
4.代码
//KMP
#include<iostream>
using namespace std;
//m:模式串;求next数组
void getNext(string m,int* next)
{
int i = 0;
int j = -1;
next[0] = -1;
while (i<m.length()-1)
{
if (j == -1 || m[i] == m[j])
{
i++;
j++;
next[i] = j;
}
else
{
j = next[j];//找最长公共前后缀前缀的下一个
}
}
}
//在s中寻找匹配模式串m
int KMP(string s ,string m,int* next)
{
getNext(m,next);
int i = 0;//s的游标
int j = 0;//m的游标
int s_len = s.size();//要用一个变量存字符串的长度
int m_len = m.size();
while (i < s_len && j < m_len)
{//如果写 i <s.zie() && j < size() devc++不认,进不去循环
if (s[i] == m[j] || j == -1)
{
i++;
j++;
}
else
{
j = next[j];
}
}
if ( j == m_len)
{
return i - j + 1;
}
return -1;
}
int main()
{
int next[100] = {0};
string s,m;
s = "ABAABAABBABAAABAABBABAAB";
m = "ABAABBABAAB";
if (KMP(s,m,next)==-1)
{
cout<<s<<" 中不存在 "<<m<<endl;
}
else
{
cout<<"匹配成功,目标锁定在 "<<KMP(s,m,next)<<" 处"<<endl;
}
return 0;
}