IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 浅议kmp算法 -> 正文阅读

[数据结构与算法]浅议kmp算法

需求提出
给定一个模式串 pattern = “ddywabcdababcdabd” 和一个子串 substr = “abcdabd”,需判断 pattern 中是否包含 substr,如果包含,返回 substr 第一次在 pattern 中出现的位置;如果不包含,返回 -1(常用手段)

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

using namespace std;
/*
* Author: 酒馆店小二
* Description: kmp算法学习
* Date: 2022-2-17 22:41:52 星期四
* FileName: kmp.cpp
* Location: D:\VSCODE_CPP\algorithm\kmp\kmp.cpp
*/

vector<int> kmpNext(string substr) {  //KMP next 数组
    int n = substr.size();
    vector<int> next(n);
    for (int i = 1, j = 0; i < n; ++i) {
        while (j > 0 && substr[i] != substr[j]) {
            j = next[j - 1];
        }
        if (substr[i] == substr[j]) {
            j++;
        }
        next[i] = j;
    }
    return next;
}

int kmpMatch(string pattern, string substr) {  // KMP匹配法
    int pLen = pattern.size();
    int sLen = substr.size();
    vector<int> next = kmpNext(substr);
    for (int i = 0, j = 0; i < pLen; i++) {
        while (j > 0 && pattern[i] != substr[j]) {
            j = next[j - 1];
        }
        if (pattern[i] == substr[j]) {
            j++;
        }
        if (j == sLen) {
            return i - j + 1;
        }
    }
    return -1;
}

int main(int argc, char *argv[]){
    string pattern = "ddywabcdababcdabd";
    string substr = "abcdabd";
    cout << "Match position is: " << kmpMatch(pattern, substr) << endl;

    return 0;
}

在这里插入图片描述

赶时间的小伙伴可以拿去应急了。

第一章 暴力匹配实现

在这里插入图片描述
第一次匹配不符合后,子串向后移一位,从头再开始匹配
在这里插入图片描述
省略若干次移动。。。
就这样一位位的匹配、一位位的移动,最后当 j == substr.size() 时,匹配结束
ps: 当最后一位 d 匹配时,j 会再加一位,代码中的 ++j,所以 j == substr.szie()
在这里插入图片描述

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

using namespace std;
/*
* Author: 酒馆店小二
* Description: kmp算法学习
* Date: 2022-2-17 22:41:52 星期四
* FileName: kmp.cpp
* Location: D:\VSCODE_CPP\algorithm\kmp\kmp.cpp
*/

int violentMatch(const string &pattern, const string &substr) {
    int pLen = pattern.size();
    int sLen = substr.size();
    int i = 0, j = 0; // i、j 分别指向 pattern 和 substr 的下标
    while (i < pLen && j < sLen) {
        if (pattern[i] == substr[j]) { // 当前字符匹配成功
            i++;
            j++;
        } else { // 当前字符匹配失败
            i = i - j + 1;
            j = 0;
        }
    }
    if (j == sLen) { // 匹配成功,返回子串在模式串中的位置,否则返回 -1
        return i - j;
    } else {
        return -1;
    }
}

int main(int argc, char *argv[]){
    string pattern = "ddywabcdababcdabd";
    string substr = "abcdabd";
    cout << "Match position is: " << violentMatch(pattern, substr) << endl;

    return 0;
}

在这里插入图片描述
上面的程序是没有问题的,但不够好!(正如高中时候数学老师的一句话:我不能说你错,只能说你不对~~~)
上述程序的时间复杂度是O(mn),m、n是两个字符串的长度。聪明的程序员们是无法接受这样的时间开销的,于是就有了KMP算法的产生。

第二章 KMP历史渊源

Knuth-Morris-Pratt 算法,由 Donald Knuth、James H. Morris 和 Vaughan Pratt 三人于 1977 年联合发表,因此人们称他为克努特—莫里斯—普拉特操作(简称KMP算法)(所谓名垂青史就是这么个情况)。KMP主要应用在字符串匹配的场景中,其主要思想是当出现字符串不匹配的情况时,记录之前部分已经匹配的文本内容,利用 next 数组避免从头匹配,从而提高匹配效率,next 数组是KMP算法的核心和难点。KMP算法的时间复杂度O(m+n),而使用暴力匹配的时间复杂度则是O(mn),匹配规模越大,KMP算法的优势越明显。

第三章 KMP算法原理

1、首先,用 pattern 的第一个字符和 substr 的第一个字符去比较,不符合,substr 向后移动一位在这里插入图片描述
2、比较二者字符,不符合,后移。
在这里插入图片描述
3、比较二者字符,不符合,后移。
在这里插入图片描述
4、直到遇到匹配的元素,于是双方的下标向后移动,遇到 pattern 有一个字符与 substr 对应的字符不符合。在这里插入图片描述
5、此时按照暴力的思想是将子串向后移一位,再从头比较(亏损做法)在这里插入图片描述
6、其实这样很不划算,因为此时”abcdab”已经比较过了,我们知道了
pattern[5] = substr[1] = b,但是后移一位的效果是 pattern[5] 跟 substr[0] 比较,而
substr[0] != substr[1],所以肯定匹配失败。KMP算法的想法是,设法利用这个已知信息,不要把”搜索位置“移回已经比较过的位置,而是继续把他(子串下标)向后移,这样就提高了效率。怎么做到把刚刚重复的步骤省略掉?可以对 substr 计算出一张《匹配表》,这张表的产生在后面介绍。
在这里插入图片描述
7、此时需要看子串已匹配字符的前一个字符对应的下标,此处是模式串的 a 和子串的 d 不匹配,于是需要看子串前一个字符在匹配表中对应的下标,也就是 b 的下标,根据查表可以得知,b 下标应该是2。这是什么意思呢?— 就是说,模式串不动,子串移动到下标为2的位置来与模式串匹配,暴力匹配是模式串的 a 与子串的 d 比较,KMP算法是模式串的 a 与子串的 c 比较,可以发现,模式串并没有回溯较多位置,子串也没有从头比较。
在这里插入图片描述

8、模式串的 a 与子串的 c 不匹配,于是我们查看此时子串的前一个字符在匹配表中对应的下标,即 b 对应的下标,为 0,意思就是模式串不动,将子串的下标移到 0 处,挨着挨着比较,最终匹配成功。返回结果是10(因为从模式串的下标10的位置处第一次出现了完整的子串)。在这里插入图片描述
KMP 算法如此高效,匹配表功不可没,俗称:next 数组。next数组的介绍如下。

第四章 KMP的匹配表(next数组)

介绍匹配表如何产生之前,我们首先介绍什么是前缀什么是后缀?

  • 什么是前缀:包含首字母但不包含尾字母的所有子串。
  • 什么是后缀:包含尾字母但不包含首字母的所有子串。

这里以模式串“abcdab”为例,该模式串的前缀和后缀依次如下表:
在这里插入图片描述
于是匹配表就做好了,使用方法见前面。在这里插入图片描述

第五章 KMP算法代码实现

代码随想录的作者 carl 大佬在b站有视频:
理论篇:https://www.bilibili.com/video/BV1PD4y1o7nd
代码篇:https://www.bilibili.com/video/BV1M5411j7Xx

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

using namespace std;
/*
* Author: 酒馆店小二
* Description: kmp算法学习
* Date: 2022-2-17 22:41:52 星期四
* FileName: kmp.cpp
* Location: D:\VSCODE_CPP\algorithm\kmp\kmp.cpp
*/

vector<int> kmpNext(string substr) {  //KMP next 数组
    int n = substr.size();
    vector<int> next(n);
    for (int i = 1, j = 0; i < n; ++i) {
        while (j > 0 && substr[i] != substr[j]) {
            j = next[j - 1];
        }
        if (substr[i] == substr[j]) {
            j++;
        }
        next[i] = j;
    }
    return next;
}

int kmpMatch(string pattern, string substr) {  // KMP匹配法
    int pLen = pattern.size();
    int sLen = substr.size();
    vector<int> next = kmpNext(substr);
    for (int i = 0, j = 0; i < pLen; i++) {
        while (j > 0 && pattern[i] != substr[j]) {
            j = next[j - 1];
        }
        if (pattern[i] == substr[j]) {
            j++;
        }
        if (j == sLen) {
            return i - j + 1;
        }
    }
    return -1;
}

int main(int argc, char *argv[]){
    string pattern = "ddywabcdababcdabd";
    string substr = "abcdabd";
    cout << "Match position is: " << kmpMatch(pattern, substr) << endl;

    return 0;
}

在这里插入图片描述
笔者水平有限,有不妥的地方,请多指教。

最是人间留不住,朱颜辞镜花辞树。
2022.2.17

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-02-19 01:25:35  更:2022-02-19 01:25:51 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 15:45:25-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码