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算法的时间复杂度大大降低,是高效的字符串匹配算法。
相关推荐
大桔骑士v1 分钟前
【算法学习笔记】34:扩展欧几里得算法
算法·acm·数论·最大公约数·扩展欧几里得算法
我是苏苏3 分钟前
C#高级:用控制台程序模拟WebAPI处理接口请求信息
开发语言·c#
寒燧4 分钟前
Lightning初探
开发语言·python
程序员大澈5 分钟前
每天一个技术知识:Nuxt服务端渲染原理
前端·javascript·vue.js
上海拔俗网络16 分钟前
“AI 大模型内容安全审核软件系统:守护网络世界的卫士
java·团队开发
hakesashou23 分钟前
python如何导出数据到excel文件
开发语言·python·excel
yttandb24 分钟前
《重生到现代之从零开始的C++生活》—— 类和对象2
开发语言·c++
m0_7482510834 分钟前
PHP For 循环
android·java·php
忘不了情37 分钟前
react中,使用antd的Upload组件切片上传.zip文件及压缩包的下载
前端·react.js·前端框架
lichong95137 分钟前
【React】win系统环境搭建
前端·react.js·前端框架·api·postman·win·smartapi