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");
    }
}




0 0
原创粉丝点击