kmp算法 入门理解 +例题

kmp算法是用来解决字符串匹配问题的

给定一个str1字符串和str2字符串,看一下str1字符串中是否有str2字符串,这就相当于集合中的包含关系,看一下str1字符串是否包含str2字符串。

以下内容不是原创

kmp算法的基本思想:

 

这种算法不太容易理解,网上有很多解释,但读起来都很费劲。直到读到Jake Boxer的文章,我才真正理解这种算法。下面,我用自己的语言,试图写一篇比较好懂的KMP算法解释。

  1.

kmp算法 入门理解 +例题_第1张图片

  首先,str1字符串"BBC ABCDAB ABCDABCDABDE"的第一个字符与str2字符串"ABCDABD"的第一个字符,进行比较。因为B与A不匹配,所以str2字符串后移一位。

  2.

kmp算法 入门理解 +例题_第2张图片

  因为B与A不匹配,str2字符串再往后移。

  3.

kmp算法 入门理解 +例题_第3张图片

  就这样,直到字符串有一个字符,与str2字符串的第一个字符相同为止。

  4.

kmp算法 入门理解 +例题_第4张图片

  接着比较str1字符串和str2字符串的下一个字符,还是相同。

  5.

kmp算法 入门理解 +例题_第5张图片

  直到str1字符串有一个字符,与str2字符串对应的字符不相同为止。

  6.

kmp算法 入门理解 +例题_第6张图片

  这时,最自然的反应是,将str2字符串整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把"搜索位置"移到已经比较过的位置,重比一遍。

  7.

kmp算法 入门理解 +例题_第7张图片

  一个基本事实是,当空格与D不匹配时,你其实知道前面六个字符是"ABCDAB"。KMP算法的想法是,设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,继续把它向后移,这样就提高了效率。

  8.

kmp算法 入门理解 +例题_第8张图片

  如何得到的这个表和这个表每个位置上的数表示的是什么你先不用着急直到,后面会讲,现在你会用就行

  9.

kmp算法 入门理解 +例题_第9张图片

  已知空格与D不匹配时,前面六个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符D对应表中的数字是2,那么“搜索位置”就从最后一个字符往前跳到str2字符串的第3个位置(str2字符串往后移动4位),如下图

  10.

kmp算法 入门理解 +例题_第10张图片

  因为空格与C不匹配,此时“搜索位置”为str2字符串的3位置,对应表中的数字为0,故搜索位置跳到str2字符串的0位置(str2字符串往后移动2位),如下图

  11.

kmp算法 入门理解 +例题_第11张图片此时A与空格不匹配,”搜索位置“不变,str2往后移动1位

  12.

kmp算法 入门理解 +例题_第12张图片

  逐位比较,直到发现C与D不匹配。查表可知,“搜索位置”往前跳到str2字符串的3位置(str2字符串往后移动4位),

  13.

kmp算法 入门理解 +例题_第13张图片

  逐位比较,直到str2字符串的最后一位,发先str2字符串的最后一位也匹配成功,说明str1字符串中存在str2字符串,算法结束

  "

 next数组如何得到的:

  首先,要了解两个概念:"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。

  15.

kmp算法 入门理解 +例题_第14张图片

  "部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"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.

kmp算法 入门理解 +例题_第15张图片

  "部分匹配"的实质是,有时候,字符串头部和尾部会有重复。比如,"ABCDAB"之中有两个"AB",那么它的"部分匹配值"就是2("AB"的长度)。str2字符串移动的时候,第一个"AB"向后移动4位(字符串长度-部分匹配值),就可以来到第二个"AB"的位置。

代码如下:

​

#include
#define MAXN 9999
using namespace std;
string str1,str2;//str1为总的字符串   str2为匹配的字符串
int next[MAXN];//next[i]代表str2字符串i位置之前的最长前缀
void getnextarray()//这个函数是求next数组的
{
    if(str2.size()==1)//匹配的字符串只有一个字符
    {
        next[0]=-1;//我们人为规定next[0]为-1,next[1]为0
        next[1]=0;
        return ;
    }
    next[0]=-1;
    next[1]=0;
    int i=2;//i代表填充next数组的i位置(str2字符串i位置前面的字符串的最长前缀)
    int cn=0;//cn始终代表str2字符串i-1位置前面的字符串的最长前缀的下一个字符的位置
    while (i<=str2.size())
    {
        if(str2[i-1]==str2[cn])//如果str2字符串i-1位置上的字符等于str2字符串cn位置上的字符的话,直接在next[i]的基础上加1即可
            next[i++]=++cn;
        else if(cn>0)//这个条件满足,说明可以往前跳,让cn往前跳
            cn=next[cn];
        else
            next[i++]=0;//str2字符串i位置前面的字符串没有前缀
    }
}
int kmp()//kmp算法,如果匹配成功,返回1,否则,返回0
{
    int s1=0,s2=0;//这是两个指针,s1刚开始指向str1字符串的第一个字符,s2刚开始指向str2字符串的第一个字符
    while (s1>str1>>str2;
    getnextarray();
    if(kmp())
        cout<<"匹配成功"<

例题一:

【HDU 1711】

题目:http://acm.hdu.edu.cn/showproblem.php?pid=1711

题意:

给定两个数组,问能不能再第一个数组中匹配得到第二个数组,如果可以,那么输出最早匹配的起始位置,否则输出-1

思路:

模板题

代码:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mod 1000000007
typedef long long ll;
using namespace std;
int a[1000001];
int b[10001];
int n,m,t;
int next1[1000001];//next[i]代表str2字符串i位置之前的最长前缀
void getnextarray()//这个函数是求next数组的
{
    if(m==1)//匹配的字符串只有一个字符
    {
        next1[0]=-1;//我们人为规定next[0]为-1,next[1]为0
        next1[1]=0;
        return ;
    }
    next1[0]=-1;
    next1[1]=0;
    int i=2;//i代表填充next数组的i位置(str2字符串i位置前面的字符串的最长前缀)
    int cn=0;//cn始终代表str2字符串i-1位置前面的字符串的最长前缀的下一个字符的位置
    while (i<=m)
    {
        if(b[i-1]==b[cn])//如果str2字符串i-1位置上的字符等于str2字符串cn位置上的字符的话,直接在next[i]的基础上加1即可
            next1[i++]=++cn;
        else if(cn>0)//这个条件满足,说明可以往前跳,让cn往前跳
            cn=next1[cn];
        else
            next1[i++]=0;//str2字符串i位置前面的字符串没有前缀
    }
}
int kmp()//kmp算法,如果匹配成功,返回1,否则,返回0
{
    int s1=0,s2=0;//这是两个指针,s1刚开始指向str1字符串的第一个字符,s2刚开始指向str2字符串的第一个字符
    while (s1<=n&&s2<=m)//两个指针都没有到达最后一个字符时,执行下面过程
    {
        if(a[s1]==b[s2])//str1字符串s1位置上的字符和str2字符串s2位置上的字符相等
        {
            s1++;//让两个指针都往后移动一位
            s2++;
        }
        else if(next1[s2]==-1)//如果程序运行到这里,说明上面的条件不满足,str1字符串s1位置上的字符和str2字符串s2位置上的字符不相等,如果这个条件满足,说明str1字符串第一个字符就不和str2字符串匹配,让s1往后移动一个位置
            s1++;
        else
            s2=next1[s2];//这是语句是kmp算法的核心内容,也是kmp算法为什么快的原因
        if(s2==m)
            return s1-m+1;//如果这个条件满足,说明已经模式串已经全部匹配完成了,这次肯定是最早的的一次,返回最早匹配成功的起始位置
    }
    return 0;
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(int i=0;i

例题二:

HDU1358:

题目链接:HDU1358

题意:

一个字符串,从头到某个位置,字符串的前缀最多重复了多少次

思路:

通过next数组判断字符串是否为周期串。next数组的一个应用。具体实现看代码

代码:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mod 1000000007
typedef long long ll;
using namespace std;
string str2;
int n;
int next1[1000001];
void getnextarray()//这个函数是求next数组的
{
    memset(next1,0,sizeof(next1));
    if(n==1)//匹配的字符串只有一个字符
    {
        next1[0]=-1;//我们人为规定next[0]为-1,next[1]为0
        next1[1]=0;
        return ;
    }
    next1[0]=-1;
    next1[1]=0;
    int i=2;//i代表填充next数组的i位置(str2字符串i位置前面的字符串的最长前缀)
    int cn=0;//cn始终代表str2字符串i-1位置前面的字符串的最长前缀的下一个字符的位置
    while (i<=n)
    {
        if(str2[i-1]==str2[cn])//如果str2字符串i-1位置上的字符等于str2字符串cn位置上的字符的话,直接在next[i]的基础上加1即可
            next1[i++]=++cn;
        else if(cn>0)//这个条件满足,说明可以往前跳,让cn往前跳
            cn=next1[cn];
        else
            next1[i++]=0;//str2字符串i位置前面的字符串没有前缀
    }
}
int main()
{
    int l=1;
    while(scanf("%d",&n))
    {
        if(n==0)
            break ;
        cin>>str2;
        getnextarray();
        printf("Test case #%d\n",l++);
        for(int i=2;i<=n;i++)//这个while循环就是通过next数组来判断前缀是否为周期串的
        {
            int m=i-next1[i];//如果前缀是周期串,那么m就为一个周期的子串的长度
            if(i%m==0&&next1[i]!=0)
                printf("%d %d\n",i,i/m);//i/m就为一共有多少个周期
        }
        printf("\n");
    }
}

例题三:

hdu(1686)——Oulipo

题目链接:

http://acm.hdu.edu.cn/showproblem.php?pid=1686

题意:

给你两个串A,B,让你求A串在B串中的出现次数。

思路:

这个题目有两种做法,第一种就是用扩展kmp做,模板题,在这里我就不讲这种做法了,太简单了!

第二种做法就是用kmp做,利用kmp算法中的next数组来解,我们多求一位next数组,即整个字符串的最长公共前后缀,然后我们进行kmp匹配时,当模式串匹配完成一次后,我们让模式串的那个指针等于多求的那位next数组,继续匹配。具体看代码

代码:

#include
#define MAXN 9999
using namespace std;
char str1[1000001],str2[10001];//str1为总的字符串   str2为匹配的字符串
int next1[10001];//next[i]代表str2字符串i位置之前的最长前缀
int t;
void getnextarray()//这个函数是求next数组的
{
    int size_str2=strlen(str2);
    if(size_str2==1)//匹配的字符串只有一个字符
    {
        next1[0]=-1;//我们人为规定next[0]为-1,next[1]为0
        next1[1]=0;
        return ;
    }
    next1[0]=-1;
    next1[1]=0;
    int i=2;//i代表填充next数组的i位置(str2字符串i位置前面的字符串的最长前缀)
    int cn=0;//cn始终代表str2字符串i-1位置前面的字符串的最长前缀的下一个字符的位置
    while (i<=size_str2+1) //多算一个
    {
        if(str2[i-1]==str2[cn])//如果str2字符串i-1位置上的字符等于str2字符串cn位置上的字符的话,直接在next[i]的基础上加1即可
            next1[i++]=++cn;
        else if(cn>0)//这个条件满足,说明可以往前跳,让cn往前跳
            cn=next1[cn];
        else
            next1[i++]=0;//str2字符串i位置前面的字符串没有前缀
    }
}
int kmp()//kmp算法,如果匹配成功,返回1,否则,返回0
{
    int size_str1=strlen(str1);
    int size_str2=strlen(str2);
    int s1=0,s2=0,cnt=0;//这是两个指针,s1刚开始指向str1字符串的第一个字符,s2刚开始指向str2字符串的第一个字符
    while (s1

下面的代码是扩展kmp的版本:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXN 100000
#define mod 1000000007
typedef long long ll;
using namespace std;
int extend[1000001];//entend[i]代表S[i~~~S.size()]和T字符串的最长公共前缀
int next1[10001];//next[i]代表T[i~~~T.size()]和T字符串的最长公共前缀
int t;
char S[1000001],T[10001];
void GetNext()//这个函数求的是next数组
{
    int a,p,j;//j代表当前状态p与i之间的距离
    int len=strlen(T);
    next1[0]=len;
    for(int i=1,j=-1;i=p)//j<0说明i位置和p位置重合||第二种情况或者第三种情况
        {
            if(j<0)//i位置与p位置重合,让j从T字符串0位置开始遍历,p从S字符串i位置开始遍历
            {
                j=0;
                p=i;
            }
            while (p=p)
        {
            if(j<0)
            {
                j=0;
                p=i;
            }
            while (p

例题四:

【POJ2752】

题目链接:http://poj.org/problem?id=2752

题意:

给你一个字符串,让你找出这个字符串中所有即是前缀又是后缀的字串的长度

很显然,这个字符串本身就是我们要找的字符串

我们很快可以发现,我们需要找的字符串一定是该字符串的相同的最长前缀和最长后缀的字串

比如说 
ababcababababcabab

满足条件的子串有 ababcabab abab ab

思路:

这就是kmp中next数组的应用,next数组求得是最长的相等前后缀,而这个题目是要你求出所有的相等前后缀,所以我们按照kmp求next数组的方法,当求出next数组后,我们再对字符串最后的位置(str.size()  位置)求一遍next数组,只不过不同的是,当我们验证相等后,不是立即结束,而是寻找其它的更短的满足条件的相等前后缀。

代码:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mod 1000000007
typedef long long ll;
using namespace std;
char str1[400001];
int next1[400001];
int JieGuo[400001];
int k;
void getnextarray()//这个函数是求next数组的
{
    memset(JieGuo,0,sizeof(JieGuo));
    memset(next1,0,sizeof(next1));
    int len_str1=strlen(str1);
    next1[0]=-1;
    next1[1]=0;
    int i=2;//i代表填充next数组的i位置(str1字符串i位置前面的字符串的最长前缀)
    int cn=0;//cn始终代表str1字符串i-1位置前面的字符串的最长前缀的下一个字符的位置
    while (i<=len_str1)
    {
        if(str1[i-1]==str1[cn])//如果str1字符串i-1位置上的字符等于str1字符串cn位置上的字符的话,直接在next[i]的基础上加1即可
            next1[i++]=++cn;
        else if(cn>0)//这个条件满足,说明可以往前跳,让cn往前跳
            cn=next1[cn];
        else
            next1[i++]=0;//str1字符串i位置前面的字符串没有前缀
    }
    int j=len_str1;
    k=0;
    JieGuo[k++]=len_str1;
    int z=next1[j];
    while(z>=0)//这个while循环是求整个字符串的所有的相等前后缀的,存在JieGuo数组中,最后逆序输出
    {
        if(str1[z-1]==str1[len_str1-1])
            JieGuo[k++]=z;
        z=next1[z];
    }
}
int main()
{
    while (scanf("%s",str1)!=EOF)
    {
        int len=strlen(str1);
        if(len==1)
        {
            printf("1\n");
            continue ;
        }
        getnextarray();
        for(int i=k-1;i>=0;i--)
            printf("%d ",JieGuo[i]);
        printf("\n");
    }
}

 

你可能感兴趣的:(算法)