KMP算法

KMP算法详解

KMP(Knuth-Morris-Pratt)算法是一种用于在大串中寻找小串的字符串匹配算法。它通过在字符串匹配过程中避免不必要的重复比较,显著提高了效率。KMP算法的核心思想是利用字符串中已经匹配的部分信息来优化匹配过程,减少回溯操作。

KMP算法的基本思想

KMP算法的关键是使用"部分匹配表"(也叫做"前缀表"或者"失配函数"),该表记录了每个子串的前缀与后缀的相似信息。通过这个表,可以在字符匹配失败时,跳过一些无意义的比较,直接跳到下一个可能匹配的位置。

1. 前缀和后缀的定义
  • 前缀:一个字符串的前缀是它的一个子串,且不包括字符串的最后一个字符。例如,字符串 "ABCD" 的前缀有 ""、"A"、"AB"、"ABC"。
  • 后缀:一个字符串的后缀是它的一个子串,且不包括字符串的第一个字符。例如,字符串 "ABCD" 的后缀有 "BCD"、"CD"、"D"。

在KMP算法中,我们需要关注的是前缀后缀的重合部分。特别是,对于模式串中的每个位置,我们需要知道它的最大前缀和后缀的匹配长度。

2. 部分匹配表(前缀表)

部分匹配表(也叫"失配函数")记录了模式串中每个位置的前缀和后缀的最长匹配长度。具体来说,前缀表的第 i 个值表示的是模式串从位置 0i-1 的子串的最长前缀后缀匹配长度。

例如,对于模式串 ABABAC,它的部分匹配表为:

java 复制代码
索引:     0  1  2  3  4  5
模式串:   A  B  A  B  A  C
前缀表:  0  0  1  2  3  0

解释:

  • A 没有前缀和后缀匹配,因此为 0
  • AB 没有前缀和后缀匹配,因此为 0
  • ABA 有前缀和后缀 A 匹配,因此为 1
  • ABAB 有前缀和后缀 AB 匹配,因此为 2
  • ABABA 有前缀和后缀 ABA 匹配,因此为 3
  • ABABAC 没有前缀和后缀匹配,因此为 0
3. 构建部分匹配表

为了有效实现KMP算法,需要首先构建部分匹配表。我们从模式串的第二个字符开始,逐个计算出每个位置的最长前缀后缀匹配长度。

构建过程:

  • 初始化一个数组 prefixTable,大小为模式串的长度,初始值为 0
  • 使用两个指针:一个指向模式串的当前字符(i),一个指向前缀长度(j)。
  • 遍历模式串,对于每个字符,如果它与前一个字符匹配,则 prefixTable[i] = j + 1,否则通过 prefixTable[j-1] 来跳过一些字符,避免重复计算。
4. KMP匹配过程
  • 初始化两个指针,一个指向文本串 T(大串),一个指向模式串 P(小串)。
  • 比较模式串中的字符与文本串中的字符:
    • 如果匹配,则继续比较下一个字符。
    • 如果不匹配,则根据部分匹配表跳到模式串中的一个位置,避免了从头开始重新匹配。
5. KMP匹配的时间复杂度
  • 构建部分匹配表的时间复杂度为 O(m),其中 m 是模式串的长度。
  • 匹配过程的时间复杂度为 O(n),其中 n 是文本串的长度。
  • 因此,KMP算法的总时间复杂度为 O(m + n),相比传统的暴力匹配算法 O(m * n),大大提高了效率。
例题:
6. KMP算法的实现
java 复制代码
public class KMP {
    // 构建部分匹配表
    public static int[] buildPrefixTable(String pattern) {
        int m = pattern.length();
        int[] prefixTable = new int[m];
        int j = 0; // 前缀长度

        for (int i = 1; i < m; i++) {
            while (j > 0 && pattern.charAt(i) != pattern.charAt(j)) {
                j = prefixTable[j - 1];
            }
            if (pattern.charAt(i) == pattern.charAt(j)) {
                j++;
            }
            prefixTable[i] = j;
        }
        return prefixTable;
    }

    // KMP字符串匹配
    public static int kmpSearch(String text, String pattern) {
        int[] prefixTable = buildPrefixTable(pattern);
        int n = text.length();
        int m = pattern.length();
        int j = 0; // 模式串的指针

        for (int i = 0; i < n; i++) {
            while (j > 0 && text.charAt(i) != pattern.charAt(j)) {
                j = prefixTable[j - 1]; // 跳到前缀表的位置
            }
            if (text.charAt(i) == pattern.charAt(j)) {
                j++;
            }
            if (j == m) {
                return i - m + 1; // 匹配成功,返回匹配的起始位置
            }
        }
        return -1; // 匹配失败
    }

    public static void main(String[] args) {
        String text = "ABABDABACDABABCABAB";
        String pattern = "ABABCABAB";
        int result = kmpSearch(text, pattern);
        System.out.println("Pattern found at index: " + result); // 输出: Pattern found at index: 10
    }
}
7. 总结
  • KMP算法通过构建前缀表来优化字符串匹配的效率,避免了暴力匹配中的重复计算。
  • 在字符串匹配过程中,利用已经匹配的部分信息来跳过不必要的比较,节省时间。
  • 相比传统的暴力匹配算法,KMP算法的时间复杂度大大降低,是高效的字符串匹配算法。
相关推荐
JD技术委员会20 分钟前
Rust 语法噪音这么多,是否适合复杂项目?
开发语言·人工智能·rust
tekin22 分钟前
Go、Java、Python、C/C++、PHP、Rust 语言全方位对比分析
java·c++·golang·编程语言对比·python 语言·php 语言·编程适用场景
Hello.Reader23 分钟前
Rust 中的 `Drop` 特性:自动化资源清理的魔法
开发语言·rust·自动化
liruiqiang0524 分钟前
机器学习 - 投票感知器
人工智能·算法·机器学习
Vitalia27 分钟前
从零开始学 Rust:基本概念——变量、数据类型、函数、控制流
开发语言·后端·rust
whisperrr.1 小时前
【JavaWeb12】数据交换与异步请求:JSON与Ajax的绝妙搭配是否塑造了Web的交互革命?
前端·ajax·json
小禾苗_1 小时前
C++ ——继承
开发语言·c++
李长渊哦1 小时前
Java 虚拟机(JVM)方法区详解
java·开发语言·jvm
进击ing小白1 小时前
Qt程序退出相关资源释放问题
开发语言·qt
烂蜻蜓2 小时前
前端已死?什么是前端
开发语言·前端·javascript·vue.js·uni-app