首页 > 其他分享 >KMP

KMP

时间:2024-09-10 15:35:51浏览次数:1  
标签:pat int next KMP 匹配 txt

KMP

KMP-字符串匹配算法,pat模式串,长度为M; txt文本串,长度为N。KMP算法是在txt中查找子串pat,如果存在,返回起始索引,否则返回-1 。

https://zhuanlan.zhihu.com/p/83334559这个知乎专栏讲得很好

根据上面的理解

1、如果是暴力枚举的话,就是在 txt枚举每一个字符,当这个字符与pat开头相同时,就继续枚举下去,直到不匹配,又从txt下一个字符开始匹配。这样太费时间,如:

img

当字符串中有很多重复的字符时,就会进行很多不必要的操作,比如pat中根本没有 c 这个字符,应该直接从txtc 后面的 a 开始匹配,但是暴力枚举只是单一的遍历txt的每一个字符。

2、我们想记录pat匹配的状态,记录当前pat匹配到哪一步了,这样就可以直接避免txt又回到前面重新遍历一次。

3、image-20211204211751578

假设pat = ABABC,初始状态为0,箭头上的字母表示在当前状态下匹配到该字母的下一状态。这样就实现了一直遍历txt,然后判断pat当前的状态就行了。也就说pat的状态数组只与匹配串pat有关 。遇到不属于pat 的字符状态回到起始 0。

4、如何构建pat的状态数组

状态转移: 当前的匹配状态遇到的字符

KMP实现算法

设模式串为 p,匹配串为s

思考

next数组:如next[j]表示p[1~j]串中前缀和后缀相同的最大长度(部分匹配值)。

设p串: a b c a b

p a b c a b
下标 1 2 3 4 5
next[] 0 0 0 1 2

next[5]:前缀 ab、 后缀 ab共同长度为2.

核心思想:在每次失配时,不是把p串往后移一位,而是把p串往后移动至下一次可以和前面部分匹配的位置,这样就可以跳过大多数的失配步骤。而每次p串移动的步数就是通过查找next[ ]数组确定的。

比如要在s = abababc寻找模式串p = ababc

  • 前三个abc已经找到相同,但是第 5个 a != c-s[5] != p[4+1]
  • 这时候kmp 可以根据next[]数组,因为next[4] = 2 ,说明前4个中组成的串前缀和后缀公共的部分有 2 个(ab ab),j = next[4] = 2,直接从第 2 + 1个开始比较,前两个已经匹配过了,此时s[5] = ap[2+1] = a,匹配到了aba,加上后面的bcj到达了m,匹配完成。

匹配代码

匹配串s长度为n,模式串p为m

// 数据下标从1开始
for(int i = 1; j = 0; i <= n;i++) {
    while(j && s[i] != p[j+1])	j = ne[j];
    if(s[i] == p[j+1])	j++;
    if(j == m) {
        // 输出以0开始的匹配子串的首字母
        cout << i - m; // 若从1开始则 +1
        j = ne[j]; // 匹配多次就用这个
    }
}

模式串next数组

构建next数组和匹配代码几乎一模一样,就是在模式串p找相同的前缀。

for(int i = 2, j = 0; i <= n;i++) {
    while(j && p[i] != p[j+1])	j = ne[j];
    if(p[i] == p[j+1])	j++; // 相同,之前的子串相同前缀后缀为j,相同统计直接+1
    ne[i] = j; // 1-i中的相同前后缀长度为j
}

image-20211205101910413

如上图。s[a,b] = p[1,j],但是当s[i]!=p[j+1]时,可以发现在匹配串p中1串等于3串s中的2串和p中的3串相等也就是说我们可以直接从1串开始匹配,即j = next[j](前缀1和后缀3相同同的长度),然后从比较p[j+1] 和p[i]开始。这样就避免了s串的回头

ACwingKMP字符串

#include<iostream>
using namespace std;
const int N = 100010, M = 1000010;
int ne[N];
char p[N], s[M]; // p是模式串、s是匹配串
int main() {
    int n,m;
    cin >> n >> p+1 >> m >> s+1;
    // 求next数组,模式串与自己匹配
    for(int i = 2, j = 0;i <= n;i++) {
        while(j && p[i] != p[j+1])  j = ne[j]; // 相当与从前缀(和后缀相同的位置)j
        if(p[i] == p[j+1])   j++;
        ne[i] = j;
    }
    
    // 利用next数组匹配需要匹配的串
    for(int i = 1,j = 0; i <= m;i++) {
        while(j && s[i] != p[j+1])  j = ne[j];
        if(s[i] == p[j+1])  j++; // 相同的字符数量
        if(j == n) {
            cout << i - n << " "; // 本来是 i -n + 1,但我们初始时下标为1,本题是0
            j = ne[j]; // 匹配下一个,从j位置开始,当前j相当于从ne[j]的位置再次匹配
        }
    }
    
    return 0;
}

标签:pat,int,next,KMP,匹配,txt
From: https://www.cnblogs.com/ddja/p/18406489

相关文章

  • 代码随想录算法训练营第九天 | Javascript | 力扣Leetcode | 手撕KMP的一天 | 28. 找
    目录前言简介题目链接:28.找出字符串中第一个匹配项的下标题目链接:459.重复的子字符串前言踏平坎坷成大道,斗罢艰险又出发!自律的尽头是自控,自控的尽头是硬控。愿道友们披荆斩棘,终能得偿所愿。简介本人是小几年经验的前端开发,算法基础只有力扣几十道题,非常薄弱。......
  • KMP 算法
    \(Question:\)给定一个模式串P和一个主串S,求模式串P在主串S中出现的位置(字符串下标均从1开始)\(Solution:\)模式串中next函数next[i]表示模式串P[1,i]中相等前后缀的最长长度运用双指针:i扫描模式串,j扫描前缀初始化ne[1]=0,i=2,j=0;ne[1]=0;......
  • LeetCode_0028. 找出字符串第一个匹配项的下标,KMP算法的实现
    题目描述  给你两个字符串haystack和needle,请你在haystack字符串中找出needle字符串的第一个匹配项的下标(下标从0开始)。如果needle不是haystack的一部分,则返回-1。示例1:输入:haystack="sadbutsad",needle="sad"输出:0解释:"sad"在下标0和6处匹......
  • 扩展KMP (ex_KMP)
    一些约定:字符串下标从1开始s[1,i]表示S的第一个到第i个字符组成的字符串解决的题型:给你两个字符串A,B(A.size()=n,B.size()=m),求p数组p[i]表示最大的len使得A[i,i+len-1]=B[1,len]即A的第i位与B前缀的最大匹配的长度比如;A:aaaabaaB:aaaap数组就是{4321021}算......
  • KMP 算法
    学习笔记我认为我这个算法可能无法讲明白,而且工作量巨大,所以为了让你快速学会我推荐学习下列笔记。学习笔记1学习笔记2学习笔记3例题感觉经过上述的学习,你一定有所收获吧(如果没有的话还是菜就多练吧),所以接下来我会举出一些题目,应该会对你的学习有些帮助。1.【模板】KMP(洛谷......
  • KMP
    呼——终于看懂了KMP——磕了三天了。题目直达Q:KMP是干什么的?是查找字符串用的,可以查找到\(S2\)字符串在\(S1\)字符串中出现的位置(当然,你可以统计出次数)。Q:那复杂度是多少的?通常,我们认为他的复杂度是\(O(|S1|)\),虽然有点常数。常规的的比较方法是直接比较,枚......
  • 代码训练营 Day9 | 151.翻转字符串里的单词 | 认识KMP算法
    151.翻转字符串里的单词这道题的难度在于如何高效率的处理空格对于python来说不能实现空间O(1)的复杂度,不过对于其他语言来说下面思路可以使用双指针方法同时指向数组的头部循环遍历整个字符串直到数组末尾快指针不能为空,因为快指针是要获取字母的,慢指针是用来获取新的字......
  • KMP-笔记
    tip:以下内容仅本人理解,如有问题,欢迎指出前言(?首先我们要知道KMP是干嘛的KMP是一个字符串匹配算法,相当于AC自动机的弱化版,如果你完全理解了KMP和Trie树的话,那你也离学会AC自动机不远了对于字符串匹配,我们有一个字符串和一个模式串,需要求字符串的子串里有没有这个模式串。......
  • 「字符串」前缀函数|KMP匹配:规范化next数组 / LeetCode 28(C++)
    概述为什么大家总觉得KMP难?难的根本就不是这个算法本身。在互联网上你可以见到八十种KMP算法的next数组定义和模式串回滚策略,把一切都懂得特别混乱。很多时候初学者的难点根本不在于这个算法本身,而是它令人痛苦的百花齐放的定义。有的next数组从0下标开始,有的从1开始;有的表......
  • 28:KMP算法
    KMP算法的用途是:在一个字符串中找到某一个字串的位置。时间复杂度是O(N)代码:packagealgorithmbasic.basicsets.class28;publicclassKMP{publicstaticintgetIndexOf(Strings1,Strings2){if(s1==null||s2==null||s1.length()<1||s2.......