返回

新手入门:理解 KMP 算法,轻松匹配字符串

见解分享

KMP 算法:字符串匹配新手指南

简介

对于探索计算机科学世界的初学者而言,KMP(Knuth-Morris-Pratt)算法是一个不可或缺的工具。作为一种高效的字符串匹配算法,它能够快速确定给定子字符串在源字符串中的位置,解决了一个看似简单的任务。

KMP 算法的原理

KMP 算法的核心在于“部分匹配表”,一个记录了子字符串中每个字符匹配长度的数据结构。该表优化了匹配过程,避免了不必要的回溯。

部分匹配表的构建基于一个递归函数 f(i),它表示子字符串 T 中前 i 个字符与 T 本身的最大匹配长度。根据以下规则,我们可以构建部分匹配表:

  • f(1) = 0
  • f(i) = f(j) + 1,若 T[i] == T[f(j) + 1]
  • f(i) = 0,否则

例如,对于子字符串“abcd”,其部分匹配表为:

i T[i] f(i)
1 a 0
2 b 0
3 c 0
4 d 3

在源字符串中查找子字符串

有了部分匹配表,我们在源字符串中查找子字符串的过程变得更加高效。设源字符串为 S,子字符串为 T,长度分别为 m 和 n。

  1. 初始化 i 和 j 为 1(S 和 T 的开始位置)。
  2. 如果 S[i] == T[j],则 i 和 j 均加 1,继续匹配下一个字符。
  3. 如果 S[i] != T[j],则有两种情况:
    • 如果 j > 1,则将 j 设置为 f(j-1) + 1,然后回到步骤 2。
    • 如果 j == 1,则将 j 设置为 1,i 不变,然后回到步骤 2。
  4. 如果 j == n,则表示匹配成功,T 从 S 中的第 i-n 个字符开始匹配。

示例代码

以下是使用 C++ 编写的 KMP 算法示例代码:

#include <iostream>
#include <vector>

using namespace std;

vector<int> kmp(string T) {
  int n = T.size();
  vector<int> f(n);
  f[0] = 0;
  int j = 0;
  for (int i = 1; i < n; i++) {
    while (j > 0 && T[i] != T[j]) j = f[j - 1];
    if (T[i] == T[j]) j++;
    f[i] = j;
  }
  return f;
}

int main() {
  string S = "abcdhabcdfj";
  string T = "abcdf";
  vector<int> f = kmp(T);
  int i = 0, j = 0;
  while (i < S.size() && j < T.size()) {
    if (S[i] == T[j]) { i++; j++; }
    else if (j > 0) { j = f[j - 1]; }
    else { i++; }
    if (j == T.size()) { cout << "Match found at index: " << i - j << endl; }
  }
  return 0;
}

结论

KMP 算法最初可能看起来令人望而生畏,但通过透彻理解其原理和实现,我们可以在字符串匹配任务中获得显著优势。随着经验的积累,KMP 算法将成为你算法工具箱中不可或缺的一部分,助你轻松应对复杂的文本处理难题。

常见问题解答

1. KMP 算法比其他字符串匹配算法有什么优势?
KMP 算法利用部分匹配表避免了不必要的回溯,大大提高了效率。

2. 部分匹配表是如何构建的?
部分匹配表是基于递归函数 f(i) 构建的,它表示子字符串 T 中前 i 个字符与 T 本身的最大匹配长度。

3. 在源字符串中查找子字符串的步骤是什么?

  1. 初始化 i 和 j 为 1。
  2. 如果 S[i] == T[j],则 i 和 j 均加 1。
  3. 如果 S[i] != T[j],则根据情况调整 i 和 j。
  4. 如果 j == n,则表示匹配成功。

4. KMP 算法适用于哪些应用场景?
KMP 算法广泛应用于文本编辑器、搜索引擎和病毒扫描等需要进行字符串匹配的领域。

5. KMP 算法是否有局限性?
虽然 KMP 算法在大多数情况下非常高效,但在某些情况下,其时间复杂度可能达到 O(mn),其中 m 和 n 分别是源字符串和子字符串的长度。