引入
在字符串匹配时, 如果运用暴力匹配法:
text: abababc
pattern: ababc
暴力匹配法:
1.
abababc
ababc
2.
abababc
a
3.
abababc
ababc
而这种匹配的效率并不高, 我们通常更希望的做法是:
1.
abababc
ababc
2.
abababc
ababc
而如果想像上面那样直接+2去匹配的方式,则可以运用kmp的算法
kmp算法原理
- 首先我们得解决一个问题, 就是每次匹配不成功的话, 应该要跳到哪里再开始匹配, 因此这里可以引入一个next[ ]的数组来记录要回溯的位置:
1. pattern的前后缀
前缀: 指除了最后的一个字符外, 从前面开始的所有字符串集合
abababc —> a, ab, aba, abab, ababa, ababab
后缀: 指处理最开始的一个字符外, 从后面开始的所以字符串集合
abababc —> c, bc, abc, babc, ababc, bababc
2. next[ ]数组
而next[ ]数组就是用来记录pattern字符串的最大匹配数
- 注: 下图列举了前缀和后缀的匹配情况, ( ) 内的字符为超出idx部分, 因此不算入pmt的值里
idx | 0 | 1 | 2 | 3 | 4 | 5 | 6 | pmt (count) |
---|---|---|---|---|---|---|---|---|
char | a | b | a | b | a | b | c | |
0 | / | / | / | / | / | / | 0 | |
1 | ( a )/ | ( b )/ | / | / | / | 0 | ||
2 | a | ( b )/ | ( a )/ | / | / | 1 | ||
3 | a | b | ( a )/ | ( b )/ | / | 2 | ||
4 | a | b | a | ( b )/ | ( c )/ | 3 | ||
5 | a | b | a | b | ( c ) | 4 | ||
6 | a | b | a | b | c | 5 |
- next[ ]数组:
idx | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
char | a | b | a | b | a | b | c |
pmt | 0 | 0 | 1 | 2 | 3 | 4 | 5 |
3. kmp匹配
- 有了next[ ]数组我们就可以用kmp来匹配字符串text[ ]了:
第一次匹配, q == 6, k == 6时, text[6] != pattern[6]
因此需要回溯, pattern回溯到 next[k-1] == 4, 即pattern[4]和text[6]做比较, 如果相等, 那么继续比较下一位, 如果不等, 则pattern继续回溯到next[k-1], 如此循环:
kmp代码实现:
int kmp(const char *text, const char *pattern, int *next)
{
int n = strlen(text);
int m = strlen(pattern);
int q, k;
int ret = -1;
make_next(pattern, next);
for (q = 0, k = 0; q < n; q++)
{
while (k > 0 && text[q] != pattern[k])
{
k = next[k-1];
}
if (text[q] == pattern[k])
{
k++;
}
if (k == m)
{
ret = q - k + 1;
break;
}
}
return ret;
}
求next[ ]的代码:
- 求next[]数组其实也是看成一次kmp的匹配过程, 只是将text[ ]换成pattern[ ]
void make_next(const char *pattern, int *next)
{
int q, k;
int m = strlen(pattern);
next[0] = 0;
for (q = 1, k = 0; q < m; q++)
{
while (k > 0 && pattern[q] != pattern[k])
{
k = next[k-1];
}
if (pattern[q] == pattern[k])
{
k++;
}
next[q] = k;
}
}
完整代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void make_next(const char *pattern, int *next)
{
int q, k;
int m = strlen(pattern);
next[0] = 0;
for (q = 1, k = 0; q < m; q++)
{
while (k > 0 && pattern[q] != pattern[k])
{
k = next[k-1];
}
if (pattern[q] == pattern[k])
{
k++;
}
next[q] = k;
}
}
int kmp(const char *text, const char *pattern, int *next)
{
int n = strlen(text);
int m = strlen(pattern);
int q, k;
int ret = -1;
make_next(pattern, next);
for (q = 0, k = 0; q < n; q++)
{
while (k > 0 && text[q] != pattern[k])
{
k = next[k-1];
}
if (text[q] == pattern[k])
{
k++;
}
if (k == m)
{
ret = q - k + 1;
break;
}
}
return ret;
}
int main()
{
int i;
int next[20] = {0};
char *text = "ababababcxab";
char *pattern = "abababc";
int idx = kmp(text, pattern, next);
printf("idx: %d\n", idx);
printf("next:\n");
for (i = 0; i < strlen(pattern); i++)
{
printf("%4d", next[i]);
}
printf("\n");
return 0;
}