KMP算法
来源:互联网 发布:上古卷轴5御姐捏脸数据 编辑:程序博客网 时间:2024/06/08 09:29
看到好多的KMP讲解,都不太明白next数组,知道找到 阮一峰大神对KMP的讲解 结果豁然开朗。
废话不多说,上图。
举例来说,有一个字符串"BBC ABCDAB ABCDABCDABDE",我想知道,里面是否包含另一个字符串"ABCDABD"?
许多算法可以完成这个任务,Knuth-Morris-Pratt算法(简称KMP)是最常用的之一。它以三个发明者命名,起头的那个K就是著名科学家Donald Knuth。
这种算法不太容易理解,网上有很多解释,但读起来都很费劲。直到读到Jake Boxer的文章,我才真正理解这种算法。下面,我用自己的语言,试图写一篇比较好懂的KMP算法解释。
1.
首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一个字符与搜索词"ABCDABD"的第一个字符,进行比较。因为B与A不匹配,所以搜索词后移一位。
2.
因为B与A不匹配,搜索词再往后移。
3.
就这样,直到字符串有一个字符,与搜索词的第一个字符相同为止。
4.
接着比较字符串和搜索词的下一个字符,还是相同。
5.
直到字符串有一个字符,与搜索词对应的字符不相同为止。
6.
这时,最自然的反应是,将搜索词整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把"搜索位置"移到已经比较过的位置,重比一遍。
7.
一个基本事实是,当空格与D不匹配时,你其实知道前面六个字符是"ABCDAB"。KMP算法的想法是,设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,继续把它向后移,这样就提高了效率。
8.
怎么做到这一点呢?可以针对搜索词,算出一张《部分匹配表》(Partial Match Table)。这张表是如何产生的,后面再介绍,这里只要会用就可以了。
9.
已知空格与D不匹配时,前面六个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符B对应的"部分匹配值"为2,因此按照下面的公式算出向后移动的位数:
移动位数 = 已匹配的字符数 - 对应的部分匹配值
因为 6 - 2 等于4,所以将搜索词向后移动4位。
10.
因为空格与C不匹配,搜索词还要继续往后移。这时,已匹配的字符数为2("AB"),对应的"部分匹配值"为0。所以,移动位数 = 2 - 0,结果为 2,于是将搜索词向后移2位。
11.
因为空格与A不匹配,继续后移一位。
12.
逐位比较,直到发现C与D不匹配。于是,移动位数 = 6 - 2,继续将搜索词向后移动4位。
13.
逐位比较,直到搜索词的最后一位,发现完全匹配,于是搜索完成。如果还要继续搜索(即找出全部匹配),移动位数 = 7 - 0,再将搜索词向后移动7位,这里就不再重复了。
14.
下面介绍《部分匹配表》是如何产生的。
首先,要了解两个概念:"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。
15.
"部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例,
- "A"的前缀和后缀都为空集,共有元素的长度为0;
- "AB"的前缀为[A],后缀为[B],共有元素的长度为0;
- "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
- "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
- "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;
- "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;
- "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。
16.
"部分匹配"的实质是,有时候,字符串头部和尾部会有重复。比如,"ABCDAB"之中有两个"AB",那么它的"部分匹配值"就是2("AB"的长度)。搜索词移动的时候,第一个"AB"向后移动4位(字符串长度-部分匹配值),就可以来到第二个"AB"的位置。
算法模板:
void set_naxt()//子串的next数组,如果只看代码的话,很难得出值的确定方法。
{
int i=0,j=-1;
next[0]=-1;
while(i<lenb)
{
if(j==-1||b[i]==b[j])
{
i++;j++;
next[i]=j;
}
else
j=next[j];
}
}
int kmp()
{
int i=0,j=0;
set_naxt();
while(i<lena)
{
if(j==-1||a[i]==b[j])//这里j=-1的情况就是第一个字符不匹配的情况。
{
i++;j++;
}
else
j=next[j];//如果匹配不上,这里回溯j,
if(j==lenb)//如果匹配成功了。
returni-j+1;//回溯j最终的位子。
}
return -1;
}
入侵检测
Time Limit: 1000 MS
Memory Limit: 32767 K
Total Submit: 491(153 users)
Total Accepted: 183(123 users)
Rating:
Special Judge: No
Description
入侵检测(Intrusion Detection)是对入侵行为的检测。它通过收集和分析网络行为、安全日志、审计数据、其它网络上可以获得的信息以及计算机系统中若干关键点的信息,检查网络或系统中是否存在违反安全策略的行为和被攻击的迹象。入侵检测作为一种积极主动地安全防护技术,提供了对内部攻击、外部攻击和误操作的实时保护,在网络系统受到危害之前拦截和响应入侵。因此被认为是防火墙之后的第二道安全闸门,在不影响网络性能的情况下能对网络进行监测。入侵检测通过执行以下任务来实现:监视、分析用户及系统活动;系统构造和弱点的审计;识别反映已知进攻的活动模式并向相关人士报警;异常行为模式的统计分析;评估重要系统和数据文件的完整性;操作系统的审计跟踪管理,并识别用户违反安全策略的行为。
其中特征检测(Signature-based detection) 又称Misuse detection ,假设入侵者活动可以用一种模式来表示,系统的目标是检测主体活动是否符合这些模式。它可以将已有的入侵方法检查出来。
现在给出主体活动和一个入侵者活动,判断改主体活动是否包含该入侵者活动。
Input
每行两个字符串,表示主体活动和入侵者活动,主体活动串不长与200000,入侵者活动串不长于2000
Output
如果主体活动包含入侵活动,输出”yes”,否则输出”no”,并换行。
Sample Input
abcdefg
abcd
abcde
bcdef
Sample Output
yes
no
直接上KMP模板就行咯~];
#include<stdio.h>
#include<string.h>
using namespace std;
char a[200005];
char b[2005];
int next[2005];
int lena;
int lenb;
void set_naxt()//子串的next数组
{
int i=0,j=-1;
next[0]=-1;
while(i<lenb)
{
if(j==-1||b[i]==b[j])
{
i++; j++;
next[i]=j;
}
else
j=next[j];
}
}
int kmp()
{
int i=0,j=0;
set_naxt();
while(i<lena)
{
if(j==-1||a[i]==b[j])
{
i++;j++;
}
else
j=next[j];
if(j==lenb)
return 1;
}
return 0;
}
int main()
{
while(~scanf("%s%s",a,b))
{
memset(next,0,sizeof(next));
lena=strlen(a);
lenb=strlen(b);
if(kmp()==1)printf("yes\n");
else printf("no\n");
}
}
亲和串
Time Limit: 3000/1000 MS(Java/Others) Memory Limit: 32768/32768 K (Java/Others)
TotalSubmission(s): 10763 Accepted Submission(s): 4899
Problem Description
人随着岁数的增长是越大越聪明还是越大越笨,这是一个值得全世界科学家思考的问题,同样的问题Eddy也一直在思考,因为他在很小的时候就知道亲和串如何判断了,但是发现,现在长大了却不知道怎么去判断亲和串了,于是他只好又再一次来请教聪明且乐于助人的你来解决这个问题。
亲和串的定义是这样的:给定两个字符串s1和s2,如果能通过s1循环移位,使s2包含在s1中,那么我们就说s2 是s1的亲和串。
Input
本题有多组测试数据,每组数据的第一行包含输入字符串s1,第二行包含输入字符串s2,s1与s2的长度均小于100000。
Output
如果s2是s1的亲和串,则输出"yes",反之,输出"no"。每组测试的输出占一行。
Sample Input
AABCD
CDAA
ASD
ASDF
Sample Output
yes
no
思路:把主串循环一圈,相当于得到了这些个串:
ABCDA
BCDAA
CDAAB
DAABC
这么些个子串也可以想象成一个串:AABCD|AABCD。
|后边表示循环的内容。
这个时候我们可以用KMP来实现:
#include<stdio.h>
#include<string.h>
using namespace std;
char aa[100005];
char b[100005];
char a[300000];
int next[100005];
int lena,lenb;
void set_naxt()//子串的next数组
{
int i=0,j=-1;
next[0]=-1;
while(i<lenb)
{
if(j==-1||b[i]==b[j])
{
i++; j++;
next[i]=j;
}
else
j=next[j];
}
}
int kmp()
{
int i=0,j=0;
set_naxt();
while(i<lena)
{
if(j==-1||a[i]==b[j])
{
i++;j++;
}
else
j=next[j];
if(j==lenb)
return 1;
}
return 0;
}
int main()
{
while(~scanf("%s%s",aa,b))
{
strcpy(a,aa);
strcat(a,aa);
lena=strlen(a);
lenb=strlen(b);
if(kmp())
{
printf("yes\n");
}
else
printf("no\n");
if(strstr(a,b)!=0)
printf("yes\n");
else
printf("no\n");
}
}
strstr函数实现
#include<stdio.h>
#include<string.h>
using namespace std;
char aa[100005];
char b[100005];
char a[300000];
int main()
{
while(~scanf("%s%s",aa,b))
{
strcpy(a,aa);
strcat(a,aa);
if(strstr(a,b)!=0)
printf("yes\n");
else
printf("no\n");
}
}
- KMP算法详解 【KMP】
- 【KMP】KMP算法模板
- KMP hihoCoder1015 KMP算法
- kmp算法
- KMP算法
- KMP算法
- KMP算法
- KMP算法
- KMP 算法
- kmp算法
- KMP算法
- kmp算法
- KMP算法
- KMP算法
- kmp算法
- kmp算法
- KMP算法
- KMP算法
- 等差素数列
- leetcode#437 Path Sum III
- Educational Codeforces Round 19 B. Odd sum
- unity3d用欧拉角(eulerAngles)给刚体直接加速度(velocity )的算法举例
- javascript---生成器(generator)
- KMP算法
- 设计模式-访问者模式
- python调用matlab环境配置,非常详细!!!
- java 类加载-初始化顺序
- ZOJ2388 Beat the Spread!
- Maven scope介绍
- 二叉树、满二叉树、完全二叉树、二叉排序树(二叉查找树)、平衡二叉树
- LeetCode 026 Remove Duplicates from Sorted Array
- Jquery-zTree的基本用法