KMP(Knuth-Morris-Pratt)算法在字符串匹配中的应用,简单详细(附源码)

1. 介绍

欢迎阅读本篇CSDN博客,今天我们将深入探讨KMP(Knuth-Morris-Pratt)算法在字符串匹配中的应用。KMP算法是一种高效的字符串匹配算法,通过预处理模式串,能够在匹配的过程中跳过一些不必要的比较,从而提高匹配的效率。在本文中,我们将通过一个简单的C++代码来实现KMP算法,并逐步解析其中的关键部分。
本篇面向的是初学者,旨在帮助他们了解KMP算法的实现。
提供详细的过程的同时附源码,也是为了让自己的进步,共勉之

1.1 文章目的

本文的目标是介绍KMP算法的基本原理,并通过一个简单的C++实现代码来帮助读者理解该算法的实际应用。我们将会深入解析代码中的Next函数,该函数负责构建KMP算法中的关键部分——next数组。

2. KMP算法概述

在字符串匹配领域,KMP算法是一种高效的字符串匹配算法,它通过预处理模式串(即要匹配的子串),构建一个部分匹配表(也称为next数组),以便在匹配过程中跳过一些不必要的比较。这种优化使得KMP算法相较于朴素的字符串匹配算法更加高效。

2.1 KMP算法原理

KMP算法的核心思想是,当在母串中发现不匹配的字符时,利用已匹配的部分信息,尽量减少比较的次数,直接跳到可能发生匹配的位置。这一点通过next数组的构建来实现。

2.2 为什么使用KMP算法

相对于朴素的匹配算法,KMP算法的主要优势在于其对比较次数的优化。在朴素算法中,每次不匹配都需要回退重新开始比较,而KMP算法通过提前计算好的next数组,避免了这种不必要的回退,从而提高了匹配的效率。

通过对比朴素算法和KMP算法的匹配过程,可以清晰地看到KMP算法如何通过利用已匹配的信息来避免不必要的比较,从而更加高效。

在下一部分,我们将深入解析代码中的关键函数,特别是Next函数,它负责构建这个关键的next数组。

3. 代码解析

3.1 Next 函数

在KMP算法中,Next函数的作用非常关键,它负责构建next数组,为匹配过程提供重要的信息。让我们逐步解析这个函数的实现:

void Next(string sub_string, int next[], int sub_str_len) {
    next[0] = -1;
    int currentIndex = 0, subStringIndex = -1;
    while (currentIndex < sub_str_len) {
        if (subStringIndex == -1 || sub_string[subStringIndex] == sub_string[currentIndex]) {
            subStringIndex++;
            currentIndex++;
            next[currentIndex] = subStringIndex;
        } else {
            subStringIndex = next[subStringIndex];
        }
    }
    int *P = next;
    cout << "打印next数组:" << endl;
    while (P != &next[sub_str_len]) {
        cout << *P << endl;
        ++P;
    }
}
3.1.1 初始化和基本思路
  • next[0] = -1:将next数组的第一个元素初始化为-1。
  • currentIndexsubStringIndex:分别表示在母串和子串中当前比较的位置。
3.1.2 构建 next 数组
  • while (currentIndex < sub_str_len):这是一个主要的循环,负责遍历整个子串。

  • if (subStringIndex == -1 || sub_string[subStringIndex] == sub_string[currentIndex])

    • 如果subStringIndex为-1,或者当前比较的字符相匹配,执行以下操作:
      • subStringIndex++:移动子串指针。
      • currentIndex++:移动当前指针。
      • next[currentIndex] = subStringIndex:将next数组的当前位置记录为已匹配的长度。
  • else

    • 如果当前字符不匹配,将子串指针回退到next[subStringIndex]的位置,继续尝试匹配。
  • 最终,通过循环打印next数组。

3.2 主函数中的 KMP 匹配

在主函数中,我们利用构建好的next数组,实现了KMP算法的匹配过程。具体实现如下:

int indexForSub = 0, indexForMain = 0;
while (indexForMain < main_str_len && indexForSub < sub_str_len) {
    if (indexForSub == -1 || main_string[indexForMain] == sub_string[indexForSub]) {
        indexForSub++;
        indexForMain++;
    } else {
        indexForSub = next[indexForSub];
    }
}
cout << "起始位置:";
if (indexForSub == sub_str_len) {
    cout << indexForMain - indexForSub;
} else {
    cout << "wu";
}
3.2.1 匹配过程
  • while (indexForMain < main_str_len && indexForSub < sub_str_len)

    • 主循环,负责在母串和子串之间进行匹配。
  • if (indexForSub == -1 || main_string[indexForMain] == sub_string[indexForSub])

    • 如果子串指针为-1,或者当前比较的字符匹配,继续向前移动。
  • else

    • 如果字符不匹配,利用next数组回退子串指针。
3.2.2 输出结果
  • 输出匹配的起始位置,如果匹配成功,输出起始位置,否则输出 “wu”。

通过这两个关键步骤,我们完成了KMP算法的实现,利用next数组的构建和匹配过程,提高了字符串匹配的效率。

在下一部分,我们将通过示例运行来验证这段代码的实际效果。

(接下来是第四部分,示例运行)

完整代码:

#include <stdio.h>
#include <string>
#include <iostream>
using namespace std;

void Next(string sub_string, int next[], int sub_str_len) {
    next[0] = -1;
    int currentIndex = 0, subStringIndex = -1;
    while (currentIndex < sub_str_len) {
        if (subStringIndex == -1 || sub_string[subStringIndex] == sub_string[currentIndex]) {
            subStringIndex++;
            currentIndex++;
            next[currentIndex] = subStringIndex;
        } else {
            subStringIndex = next[subStringIndex];
        }
    }
    int *P = next;
    cout << "打印next数组:" << endl;
    while (P != &next[sub_str_len]) {
        cout << *P << endl;
        ++P;
    }
}

int main() {
    string main_string, sub_string;
    int next[100];
    cout << "输入母串" << endl;
    cin >> main_string;
    cout << "输入子串" << endl;
    cin >> sub_string;
    int sub_str_len = sub_string.size();
    int main_str_len = main_string.size();
    Next(sub_string, next, sub_str_len);
    int indexForSub = 0, indexForMain = 0;
    while (indexForMain < main_str_len && indexForSub < sub_str_len) {
        if (indexForSub == -1 || main_string[indexForMain] == sub_string[indexForSub]) {
            indexForSub++;
            indexForMain++;
        } else {
            indexForSub = next[indexForSub];
        }
    }
    cout << "起始位置:";
    if (indexForSub == sub_str_len) {
        cout << indexForMain - indexForSub;
    } else {
        cout << "wu";
    }

    return 0;
}

KMP优化:

			0	1	2	3	4	
​			a	a	a	a	a
next:      -1	0	1	2	3

其实我们发现当[2]和主串不匹配时,即使跳到了[1]因为他俩相等,[1]也不会和主串匹配。所以我们进行了优化

sub_string[currentIndex] == sub_string[subStringIndex]为真时,说明当前位置的字符与之前某个位置的字符相等。就把next[1]赋给next[2]

优化思路是,如果我们已经知道在之前的位置上已经发生了匹配,而且在这两个位置上的字符是相等的,那么在当前位置的比较中是不可能发生不匹配的。因此,我们可以直接将next[currentIndex]设置为next[subStringIndex],跳过这些不必要的比较步骤。

完整代码:

#include <iostream>
#include <stdio.h>
#include <string>

using namespace std;

void computeNextArray(string sub_string, int next[], int sub_str_len) {
    int currentIndex = 0, subStringIndex = -1;
    next[0] = -1;
    while (currentIndex < sub_str_len) {
        if (subStringIndex == -1 || sub_string[currentIndex] == sub_string[subStringIndex]) {
            subStringIndex++;
            currentIndex++;
            if (sub_string[currentIndex] != sub_string[subStringIndex]) {
                next[currentIndex] = subStringIndex;
            } else {
                next[currentIndex] = next[subStringIndex];
            }
        } else {
            subStringIndex = next[subStringIndex];
        }
    }
}

int main() {
    string main_string, sub_string;
    cout << "输入主串:" << endl; 
    cin >> main_string;
    cout << "输入子串:" << endl; 
    cin >> sub_string;
    int next[100];
    int sub_str_len = sub_string.size();
    int main_str_len = main_string.size();
    computeNextArray(sub_string, next, sub_str_len);

    int *P = next;
    cout << "打印next数组:" << endl;
    while (P != &next[sub_str_len]) {
        cout << *P << endl;
        ++P;
    }

    int main_index = 0, sub_index = 0;
    while (main_index < main_str_len && sub_index < sub_str_len) {
        if (main_string[main_index] == sub_string[sub_index] || sub_index == -1) {
            main_index++;
            sub_index++;
        } else {
            sub_index = next[sub_index];
        }
    }

    if (sub_index == sub_str_len) {
        cout << "起始位置:" << main_index - sub_index;
    } else {
        cout << "空";
    }
    return 0;
}

  • 10
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
串匹配是指在一个文本串查找另一个模式串的过程。常用的串匹配算法有Naïve算法、Rabin-Karp算法Knuth-Morris-Pratt算法。 1. Naïve算法 Naïve算法是最简单的串匹配算法,也称为暴力匹配算法。它的思路是从文本串的第一个字符开始,依次比较文本串的每个字符是否与模式串的字符相等。若不相等,则继续向后比较;若相等,则比较下一个字符,直到找到完全匹配的子串或文本串被匹配完为止。 Naïve算法的时间复杂度是O(mn),其m和n分别是模式串和文本串的长度。当模式串和文本串长度相等时,最坏情况下时间复杂度达到O(n^2)。 2. Rabin-Karp算法 Rabin-Karp算法是一种基于哈希值的串匹配算法。它的思路是先将模式串和文本串都转换为哈希值,然后比较它们的哈希值是否相等。如果哈希值相等,则再逐个比较模式串和文本串的字符是否相等。这种方法可以有效地减少比较次数,提高匹配效率。 Rabin-Karp算法的时间复杂度是O(m+n),其m和n分别是模式串和文本串的长度。但是,由于哈希函数的不完全性和哈希冲突的存在,Rabin-Karp算法在某些情况下可能会出现误判。 3. Knuth-Morris-Pratt算法 Knuth-Morris-Pratt算法是一种基于前缀函数的串匹配算法。它的思路是先计算出模式串的前缀函数,然后利用前缀函数的信息来跳过已经匹配过的部分,减少比较次数。 具体来说,KMP算法在匹配过程维护一个指针i和一个指针j,其i指向文本串当前匹配的位置,j指向模式串当前匹配的位置。如果当前字符匹配成功,则i和j同时向后移动一位;如果匹配失败,则通过前缀函数计算出j需要跳转到的位置,使得前j-1个字符与文本串的对应字符已经匹配成功,然后将j指向这个位置,i不变,继续比较下一个字符。 KMP算法的时间复杂度是O(m+n),其m和n分别是模式串和文本串的长度。由于利用了前缀函数的信息,KMP算法可以在最坏情况下达到O(n)的时间复杂度,比Naïve算法和Rabin-Karp算法更加高效。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值