KMP的next数组构建详解
1. next数组的作用
-
核心功能:在KMP算法中,当模式串与主串发生不匹配时,next数组决定模式串指针回退的位置,避免无效匹配。
-
定义:
next[i]
表示子串s[0...i]
的最长公共前后缀长度。例如,"ABAB"
的next[3] = 2
(前缀AB
和后缀AB
)。
2. 代码逐行解析
void getNext(int* next, const std::string& s) {
int j = 0; // j指向前缀末尾,初始为0
next[0] = j; // 第一个字符无前后缀,长度为0
for (int i = 1; i < s.size(); i++) { // 从第二个字符开始处理
// 不匹配时,通过next数组回溯j
while (j > 0 && s[i] != s[j]) {
j = next[j - 1]; // 关键:利用已计算的next值回退
}
// 匹配时,j后移
if (s[i] == s[j]) {
j++;
}
// 记录当前i对应的最长公共前后缀长度
next[i] = j;
}
}
3. 关键问题:为什么用while循环回退?
-
逐步缩短前缀:当
s[i]
与s[j]
不匹配时,需找到更短的公共前后缀继续尝试。例如,模式串"ABABAC"
,在i=5
(字符C
)时,若j=3
(字符B
),此时需回退到next[2]=1
(字符A
)继续比较。 -
避免遗漏:使用
while
确保所有可能的前缀都被尝试,直到匹配或退到0。若用if
,仅回退一次,可能错过有效前缀。
4. 实例演示
模式串:"ABABAC"
步骤分析:
i | s[i] | 初始j | while循环过程 | 最终j | next[i] | 解释 |
---|---|---|---|---|---|---|
0 | A | 0 | - | 0 | 0 | 初始化 |
1 | B | 0 | 不匹配,j保持0 | 0 | 0 | AB无公共前后缀 |
2 | A | 0 | s[2]=A匹配s[0] | 1 | 1 | ABA最长公共"A" |
3 | B | 1 | s[3]=B匹配s[1] | 2 | 2 | ABAB最长公共"AB" |
4 | A | 2 | s[4]=A匹配s[2] | 3 | 3 | ABABA最长公共"ABA" |
5 | C | 3 | 两次回退到j=0 | 0 | 0 | ABABAC无公共前后缀 |
最终next数组:[0,0,1,2,3,0]
5. 完整KMP搜索示例
int kmpSearch(const string& text, const string& pattern) {
int n = pattern.size();
if (n == 0) return 0;
int* next = new int[n];
getNext(next, pattern);
int j = 0; // 模式串指针
for (int i = 0; i < text.size(); i++) { // i遍历主串
// 不匹配时,回退模式串指针
while (j > 0 && text[i] != pattern[j]) {
j = next[j - 1];
}
// 匹配时,后移j
if (text[i] == pattern[j]) j++;
// 完全匹配时返回起始位置
if (j == n) {
delete[] next;
return i - n + 1;
}
}
delete[] next;
return -1;
}
使用示例:
int main() {
string text = "ABABABACABABAC";
string pattern = "ABABAC";
int pos = kmpSearch(text, pattern); // 输出:Pattern found at index 2
// 解释:text[2..7] = "ABABAC"与模式匹配
}
6. 总结
-
核心思想:通过模式串自我匹配构建next数组,利用已匹配信息避免重复比较。
-
时间复杂度:O(n+m),n和m分别为模式串和主串长度,远优于暴力法的O(nm)。
-
关键点:理解next数组的动态构建过程,以及如何通过回溯高效寻找最长公共前后缀。