KMP算法
KMP算法是一种不太容易理解的算法,也可能只是对于我这种蒟蒻来说(┭┮﹏┭┮)
我想分享一下我对KMP算法的理解,里面会着重介绍我的学习过程中遇到的一些卡壳点
什么是KMP算法呢?
通俗地讲,就是关于字符串匹配问题
例如给你一个字符串“ab”,又给了你另一个字符串“abcbabab”,然后让你在第二个字符串中找到第一个字符串出现的起始位置
我相信大多数人看到这个题的第一想法都是无脑暴力,我也不例外,当然提交的时候就会出现limited time这样的字眼
先来说一下无脑暴力怎么做吧:
就是先定义两个指针,分别指向两个字符串,然后从左到右一个一个匹配,当匹配成功就输出,举个例子:
abcbabab
ab
这样显然是成功的,输出1,接着第二个字符串的第一个字母指向第一个字符串的第二个字母
abcbabab
ab
匹配不成功,第二个字符串的指针继续后移
abcbabab
ab
还是不成功,继续后移
就这样一步一步的后移,直到匹配完所有的字符串
当然,我们会发现这种做法有一个很大的问题,就是我们每次遇到匹配不成功的地方,都需要把第二个字符串从第一个位置开始匹配,这样做法效率显然不高,那我们有没有办法能够找到一种方法,优化这样的回溯呢?
答案显然是有的,这就是我今天要讲的KMP算法
先来介绍一下前缀集与后缀集的概念,例如字符串 ”abhawkdjbbkj“ ,则由开头a开始且连续的字符串都属于该字符串的前缀集,同理,由结尾j作为结尾的连续字符串都属于该字符串的后缀集,我们把最大的除字符串本身外前缀集和后缀集相同的那个子串定义为最大重复子串。
我们先来看一下最大重复子串有什么意义
比如说,我们要匹配字符串abcabc与abcababdcabcabc
当我们匹配到
abcababdcabcabc
abcabc
发现最后一个字符串不相同,如果按照暴力做法我们显然要从头开始遍历,但是我们发现在abcabc的子串abcab中,最大重复子串为ab也就是说我们可以移动到
abcababdcabcabc
ababc
再继续进行比较,是不是要比暴力做法快得多呢?
那一步移动这么多会不会漏掉答案呢?那必然不会啊,要不然这怎么会成为算法呢,现在让我来打消你的疑虑吧
假设我们在移动这么多步的过程中漏掉了解,那是不是说明我们会存在更长的重复子串来达到让两个字符串匹配的目的呢?那这样岂不是与我们最大重复子串的定义相违背了?
那我们到底应该移动多少呢?通过样例不难发现,我们要移动的步数就是在待匹配字符前面的子串中最大重复子串的长度。
那如何来求出每一个子串的最大重复子串的长度呢?不妨定义为next数组,你可以想象为两个相同的字符串比较比如abcabc
首先,由定义可知next[1]=0;
abcabc
a
不匹配,故next[2]=0;
abcabc
a
不匹配,故next[3]=0;
abcabc
a
匹配,故next[4]=1;
其实求next数组的本质就是比较两个相同的字符串
下面给出一道例题及其代码,这个算法真的是困扰了我好久,希望大家好好理解
给定一个模式串 S,以及一个模板串 P,所有字符串中只包含大小写英文字母以及阿拉伯数字。
模板串 P 在模式串 S 中多次作为子串出现。
求出模板串 P 在模式串 S中所有出现的位置的起始下标。
输入格式
第一行输入整数 N,表示字符串 P 的长度。
第二行输入字符串 P。
第三行输入整数 M,表示字符串 S 的长度。
第四行输入字符串 S。
输出格式
共一行,输出所有出现位置的起始下标(下标从 0 开始计数),整数之间用空格隔开。
数据范围
1≤N≤10^5
1≤M≤10^6
输入样例:
3
aba
5
ababa
输出样例:
0 2
#include<bits/stdc++.h> using namespace std; int ne[100002]; char p[100002],s[1000002]; int main() { int n,m; cin>>n>>p+1>>m>>s+1;//KMP算法数组下标最好从1开始 for(int i=2,j=0;i<=n;i++)//ne[1]=0 { //用p字符串与自身第二个字母开始匹配,求出最大的前缀和后缀相同的部分 while(j&&p[i]!=p[j+1]) j=ne[j]; if(p[i]==p[j+1]) j++; ne[i]=j; } 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) { printf("%d ",i-n);//别忘了原题中下表是从0开始的 j=ne[j]; } } return 0; }
希望大家能够喜欢!