1、求下标 K 所对应的 next 数组对应值的具体方法:
- 观察下标 K-1 对应 字符串 的值,令 X 为下标 K -1 对应的 next 数组的值(X = next [ K-1 ])
- 若 X 为 -1,则下标 K 对应的 next 数组值为 0,否则进入步骤3
- 观察下标为 X 的 字符串 的值是否与下标 K-1 对应 字符串 的值相等,若相等则下标 K 对应的 next 数组值为 X+1,否则进入步骤4
- 令X = next[X] ,返回步骤3
对字符串 “abaababaa” 共 9 个字符求 next 数组,下表为结果:
字符数组 | a | b | a | a | b | a | b | a | a |
---|---|---|---|---|---|---|---|---|---|
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
next数组 | -1 | 0 | 0 | 1 | 1 | 2 | 3 | 2 | 3 |
分解求解步骤
1、初始化,令next[0] = -1;
字符 | a | b | a | a | b | a | b | a | a |
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
next | -1 |
2、求解下标为1对应的next数组,观察 next[0] 的值 X = -1,则 next[1] = 0;
字符 | a | b | a | a | b | a | b | a | a |
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
next | -1 | 0 |
3、求解下标为2对应的next数组,观察 next[1] 的值 X = 0,观察下标 1 对应的字符 b ,
下标为 X(0) 对应的字符为 a ,两者不一致,则令 X=next [0](X=0)即 X = -1,即 next[2] = 0;
字符 | a | b | a | a | b | a | b | a | a |
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
next | -1 | 0 | 0 |
4、求解下标为3对应的next数组,观察 next[2] 的值 X = 0,观察下标 2 对应的字符 a ,
下标为 X(0) 对应的字符为 a ,两者一致,则 next [3] = next[2] + 1,即 next[3] = 1;
字符 | a | b | a | a | b | a | b | a | a |
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
next | -1 | 0 | 0 | 1 |
5、求解下标为4对应的next数组,观察 next[3] 的值 X = 1,观察下标 3 对应的字符 a ,
下标为 X(1) 对应的字符为 b ,两者不一致,则令 X=next [X] (X=1)即 X = 0,
下标为 X(0) 对应的字符为 a,两者一致,则 next [4] = next[1] + 1,即 next[4] = 1;
字符 | a | b | a | a | b | a | b | a | a |
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
next | -1 | 0 | 0 | 1 | 1 |
6、求解下标为5对应的next数组,观察 next[4] 的值 X = 1,观察下标 4 对应的字符 b ,
下标为 X(1) 对应的字符为 b ,两者一致,则 next [5] = next[4] + 1,即 next[5] = 2;
字符 | a | b | a | a | b | a | b | a | a |
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
next | -1 | 0 | 0 | 1 | 1 | 2 |
7、求解下标为6对应的next数组,观察 next[5] 的值 X = 2,观察下标 5 对应的字符 a ,
下标为 X(2) 对应的字符为 a ,两者一致,则 next [6] = next[5] + 1,即 next[6] = 3;
字符 | a | b | a | a | b | a | b | a | a |
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
next | -1 | 0 | 0 | 1 | 1 | 2 | 3 |
8、求解下标为7对应的next数组,观察 next[6] 的值 X = 3,观察下标 6 对应的字符 b ,
下标为 X 对应的字符为 a ,两者不一致,则令 X=next [X] (X=3)即 X = 1,
下标为 X(1) 对应的字符为 b,两者一致,则 next [7] = next[3] + 1,即 next[7] = 2;
字符 | a | b | a | a | b | a | b | a | a |
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
next | -1 | 0 | 0 | 1 | 1 | 2 | 3 | 2 |
9、求解下标为8对应的next数组,观察 next[7] 的值 X = 2,观察下标 7 对应的字符 a ,
下标为 X 对应的字符为 a ,两者一致,则 next [8] = next[7] + 1,即 next[8] = 3;
字符 | a | b | a | a | b | a | b | a | a |
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
next | -1 | 0 | 0 | 1 | 1 | 2 | 3 | 2 | 3 |
2、观察法求出 next 数组:
- 求下标 K 的 next 数组的值,即从字符串首往后到下标 K - 1 观察得出前缀,从下标 K -1 往前到串首观察得出后缀
- 观察前缀和后缀一致时的长度最大值,该值即为下标 K对应的next 数组的值
前缀:整个字符串"abaababaa"的前缀为 a | ab | aba | abaa | abaab | abaaba | abaabab | abaababa
后缀:整个字符串"abaababaa"的前缀为 a | aa | baa | abaa | babaa | ababaa | aababaa | baababaa
例子1:求解下标为 4 的 next 数组的值,即观察 abaa 的前缀 [ a , ab , aba ],后缀 [ a , aa , baa ],
前缀后缀一致的只有 a ,长度为 1,则 next [ 4 ] = 1;
例子2:求解下标为 6 的 next 数组的值,即观察 abaaba 的前缀 [ a,ab,aba,abaa,abaab ],
后缀 [ a,ba,aba,aaba,baaba],前后缀一致的有 a,aba,长度分别为 1 和 3,则取最大值 next [ 6 ] = 3;
字符 | a | b | a | a | b | a | b | a | a |
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
next | -1 | 0 | 0 | 1 | 1 | 2 | 3 | 2 | 3 |
3、求解next数组,C++代码实现:
vector<int> GetNext(string s)
{
vector<int> next(s.size(), 0); // 初始化一个 s 长度的 next 数组
next[0] = -1; // 初始化下标 0 next 数组的值
int k = 0; // 存储字符串从开始到目前下标前一位的前后缀的对称程度
for (int i = 2; i < s.size(); ++i)
{
while (k > 0 && s[i - 1] != s[k])
k = next[k];
if (s[i - 1] == s[k])
k++;
next[i] = k;
}
return next;
}
发生不匹配时的操作步骤详解:
当匹配字符串为“abaababaa”时,有下表:
字符 | a | b | a | a | b | a | b | a | a |
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
next | -1 | 0 | 0 | 1 | 1 | 2 | 3 | 2 | 3 |
例子3:当下标 3 不匹配时,返回到对称的前缀的下一位查看,如 abaa 中,有前缀和后缀 a 相同,当发生不匹配时,可直接从 abaa 返回到 abaa ,然后继续进行匹配;
例子4:当下标 6 不匹配时,观察 abaabab ,有前缀和后缀 aba 相同,当发生不匹配时,可直接从 abaabab 返回到 abaabab ,下标返回到 3 ,然后继续进行匹配;
例子5:当下标 8 不匹配时,观察 abaababaa ,有前缀和后缀 aba 相同,当发生不匹配时,可直接从 abaababaa 返回到 abaababaa,下标返回到 3 ,然后继续进行匹配;
通过例子3-5可知:前后缀相同时,它们的长度代表已匹配成功的字符串长度,因此只需回到前缀的下一位继续进行匹配即可,因为通过后缀匹配成功可证明前缀必匹配;
4、KMP算法,C++代码实现:
int KMP(string S, string str_match)
{
vector<int> next = getNext(str_match);
int i = 0; // 被匹配字符串的下标
int j = 0; // 匹配字符串的下标
while (S[i] != '\0' && str_match[j] != '\0')
{
if (j == -1||S[i] == str_match[j])
{
++i;
++j;
}
else
{
j = next[j];
}
}
if (str_match[j] == '\0')
return i - j;
else
return -1;
}
KMP详解:
逻辑1:当 S[ i ] 与 str_match [ 0 ] 不匹配时(j = 0),将 i 往后移动一位,j 设置为 0;
逻辑2:当 S[ i ] 与 str_match [ j ] 匹配时(j >= 0),将 i ,j 同时往后移动一位;
逻辑3:当 S[ i ] 与 str_match [ j ] 不匹配时(j >= 0),将找到与后缀相同的前缀的下一位,即 j = next [ j ];
思考(如何将逻辑1和逻辑3统一):
- 当 j = 0时,若S[ i ] 与 str_match [ j ] 不匹配,由逻辑1和3知, 需要将 i 往后移动一位且保持 j = 0,且令j = next [ 0 ] 且;
- 即 (i++,j 为0)和(j = next [ 0 ] );
- 观察逻辑2的操作可知,逻辑2也需要将 i 往后移动一位,但同时将 j 往后移动一位;
- 即( i++,j++);
- 若先进行(j = next [ 0 ] )的操作,并在下一步进行 i++ 操作且保证 j = 0 ,思考是否可行;
- 若令 next [ 0 ] = -1,通过 j = next [ 0 ] 得到 j = -1,之后进行 i++ 操作,j此时需要保证 j = 0,则可进行 j++ 操作,产生逻辑4(这也是为何要将next [ 0 ] 初始化为 -1 的原因);
逻辑4:当 j 为 -1 时,将 i ,j 同时往后移动一位;
字符串匹配的其他算法:
BM算法、Horspool算法、Sunday算法、RK算法