KMP终结者

目录

KMP算法

0.问题

1.next数组的计算与含义

2.使用Next数组


【题记】每次遇到KMP,要么不了了之,要么简单套用模板,前几天刚背完模板,过几天啥都不记得了,没有一点点进步。

消除恐惧的最好方法就是面对恐惧。坚持,才是胜利。加油,奥里给!

本文参考了网上诸多关于KMP算法的解释与理解,力求通俗易懂。


KMP算法

0.问题

字符串匹配是计算机的基本任务之一。

字符串匹配是字符串搜索算法String searching algorithms)又称字符串比对算法string matching algorithms),是一种搜索算法,是字符串算法中的一类,用以试图在一长字符串或文章中,找出其是否包含某一个或多个字符串,以及其位置。

1.next数组的计算与含义

朴素算法就不说了,相信大家都会,不会也没事,因为会了也派不上用场。

KMP算法最重要的是“部分匹配表”(即next数组,感觉自我匹配表这个名字更合适),这个是KMP算法的核心,也是最重要且最难理解的部分。

以下表为例进行解释:

首先,我们需要知道真.前缀和真.后缀

以“PYTHON”为例:

真.前缀:“P”,“PY”,“PYT”,“PYTH”,“PYTHO”

真.后缀:“YTHON”,“THON”,“HON”,“ON”,“N”


而所谓的部分匹配值就是:某字符串的真前缀和真后缀的最长匹配大小。


回到上表中,我们每次只看从第一个字符到现在观察的这个字符。

对于第一个元素a,真前缀和真后缀都为空,因此a的部分匹配值为0;

对于第二个元素b,我们只观察前两个元素"ab",此时,真前缀有:"a",真后缀有:"b",前缀和后缀不匹配,所以b的部分匹配值也为0;

对于第三个元素a,我们只观察前三个元素"aba",此时,真前缀有:"a","ab",真后缀有:"ba","a",因为"a"和"a"匹配,所以第三个元素a的部分匹配值为1;

依次类推,我们观察第7个元素"c",对于前7个元素"abababc",我们不需要一个一个看真前后缀了,通过分析,其每个真后缀一定包含元素"c",而其真前缀没有包含"c"的,因此,"c"的模式匹配值为0;

对于最后一个元素"a"来说,"abababca"的开头和结尾都是"a",因此其模式匹配值至少为1。除了"a"这个真后缀外,其余所有真后缀都是包含"c"的,而其所有真前缀中只有"abababc"包含"c",且这个真前缀不等于相同长度的真后缀"bababca",因此最后一个元素的部分匹配值为1。

由此,我们计算得到了部分匹配表的所有值。

2.使用Next数组

字符串匹配的朴素解法是一个字符一个字符的匹配,如果遇到不同的,就将短串整个后移一位,再从头开始比较。

例:

当a和c不匹配时,将"babca"向右移一位再开始比较

而KMP的想法是当a和c不匹配时,前面的"babc"都是匹配的,这应该可以用上,可以一次向后多移动几位,提高效率。而向后移动几位可以通过Next数组求出。

规则如下:如果最后一个匹配字符的部分匹配值大于1,则 向后移动位数 = 当前字符串已经匹配的长度 - 该部分匹配值,否则向后移动1位。

具体样例如下:

bacbababaabcbab
 |
 abababca

由前面计算出来的结果,第一个元素的部分匹配值为0,因此就向后移动1位。

bacbababaabcbab
    |||||
    abababca

当前最后一位匹配元素a的部分匹配值为3,且当前已匹配 5 位,因此下一步将向前移动 5 - 3 = 2 位,如下(x表示跳过):

bacbababaabcbab
    xx|||
      abababca

由此便介绍完了KMP算法的使用。还有对next数组计算的优化,以及代码详解有时间再更。

 


KMP字符串

给定一个模式串S,以及一个模板串P,所有字符串中只包含大小写英文字母以及阿拉伯数字。

模板串P在模式串S中多次作为子串出现。

求出模板串P在模式串S中所有出现的位置的起始下标。

输入格式

第一行输入整数N,表示字符串P的长度。

第二行输入字符串P。

第三行输入整数M,表示字符串S的长度。

第四行输入字符串S。

输出格式

共一行,输出所有出现位置的起始下标(下标从0开始计数),整数之间用空格隔开。

#include<iostream>
#include<cstring>
using namespace std;

const int N = 100010;

char a[N],b[N];
int ne[N],f[N];

void kmp(int n,char a[],int m,char b[])
{
    for(int i=2,j=0;i<=n;i++)
    {
        while(j>0 && a[i]!=a[j+1]) j = ne[j];
        if(a[i] == a[j+1]) j++;
        ne[i] = j;
    }
    for(int i=1,j=0;i<=m;i++)
    {
        while(j>0 && (j==n || b[i]!=a[j+1])) j = ne[j];
        if(b[i] == a[j+1]) j++;
        f[i] = j;
        if(f[i] == n) cout<<i - j<<' ';
    }
}

int main()
{
    int n, m;
    cin>>n>>a+1>>m>>b+1;
    kmp(n,a,m,b);
    return 0;
}

下标从0开始的模板 :

const int N = 100010;

int ne[N],f[N];

void kmp(string a,string b)
{
    ne[0] = -1;
    for(int i=1,j=-1;i<a.size();i++)
    {
        while(j>=0 && a[i]!=a[j+1]) j = ne[j];
        if(a[i]==a[j+1]) j++;
        ne[i] = j;
    } 
    for(int i=0,j=-1;i<b.size();i++)
    {
        while(j!=-1 &&(j== a.size()-1 ||  b[i]!=a[j+1])) j = ne[j];
        if(b[i] == a[j+1]) j++;
        if(j == a.size()-1) 
        //找到
    }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值