KMP算法--终于弄懂了

文章目录

  • 1.什么是KMP算法?
  • 2.如何求next数组?
  • 3.代码实现
  • 4.next数组的优化
  • 5.完整代码

1.什么是KMP算法?

  • KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt三位大佬提出,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。
  • 与BF算法不同的是,当模式串与目标串匹配失败后,模式串并不是一定从头再来,目标串也不一定从起始位置的下一个位置再匹配

KMP算法--终于弄懂了_第1张图片

KMP算法--终于弄懂了_第2张图片

KMP算法--终于弄懂了_第3张图片 所以,具体再从什么地方开始匹配,取决于我们的next[],那么next[]怎么求呢?

2.如何求next数组?

kmp算法的精髓就在于next数组,从而达到跳跃式匹配的高效模式。
而next数组的值是代表着字符串的前缀与后缀相同的最大长度(不能包括自身)。

  • 求next[]数组的规则如下:
    ①一个以0下标开始,一个以j-1下标结尾
    ②找的两个串对应位置相等

下面我们以字符串"ababcabcdabcde"为例,求它的next数组:

j从0下标开始,很显然 j-1 没有元素,所以我们把它设置为-1,j++向后移

KMP算法--终于弄懂了_第4张图片

j来到1下标,j-1=0,那么就是以a开头,以a结尾的字符串,但是不能是同一个串,所以next[1]=0, j++向后移
KMP算法--终于弄懂了_第5张图片

j来到2下标,j-1=1,那么就是以a开头,以b结尾的字符串(不能是同一个串)很显然是没有的,那么next[2]=0, j++向后移
KMP算法--终于弄懂了_第6张图片

j来到3下标,j-1=2,那么就是以a开始,以a结尾的串,串的长度为1,所以next[3]=1,然后j++向后移。
KMP算法--终于弄懂了_第7张图片
j来到4下标,j-1=3,那么就是以a开始,以b结尾的串,串的长度为2,所以next[3]=2,然后j++向后移。
KMP算法--终于弄懂了_第8张图片

j来到5下标,j-1=4,那么就是以a开始,以c结尾的串,很显然没有,所以next[5]=0,然后j++向后移。
KMP算法--终于弄懂了_第9张图片
j来到6下标,j-1=5,那么就是以a开始,以a结尾的串,串的长度为1,所以next[6]=1,然后j++向后移。
KMP算法--终于弄懂了_第10张图片

剩下的也同上面一样,就不在一一讲解,在这里把图给大家

KMP算法--终于弄懂了_第11张图片

两串不相等,为0KMP算法--终于弄懂了_第12张图片
两串不相等,为0KMP算法--终于弄懂了_第13张图片
KMP算法--终于弄懂了_第14张图片
KMP算法--终于弄懂了_第15张图片
KMP算法--终于弄懂了_第16张图片
KMP算法--终于弄懂了_第17张图片
下面这个串供大家练习
在这里插入图片描述

  • 到这里对如何求next数组应该问题不大了,接下来的问题就是,已知next[i]=k;怎么求next[i+1]=?,我们通过肉眼可以很清楚的看出,但是如何让机器知道呢?

  • 如果我们能通过next[i]的值,经过一系列的转换得到next[i+1]的值,那我们就能实现这部分了。

KMP算法--终于弄懂了_第18张图片

KMP算法--终于弄懂了_第19张图片

3.代码实现

KMP算法--终于弄懂了_第20张图片

  • 这里注意,i从2号位置开始,但我们不知到i=2所对应的next是多少;求next数组时,要用i-1对应的值与k进行比较
void GetNext( char*sub ,int*next) 
{
    int lensub = strlen(sub);
    next[0] = -1;
    next[1] = 0;
    int i = 2;
    int k = 0;
    while (i<lensub) 
    {
        if (k == -1 || sub[i - 1] == sub[k]) 
        {
        	//如果i-1与k相等,那么就找到了以字符X开头,以字符X结尾的串
            next[i] = k + 1;
            i++;
            k++;
        }
        else 
        {
            k = next[k];     //如果不相等,让k一直回退,直到sub[i-1]==sub[k]
        }
    }
}
/**
str 目标串
sub 模式串
pos 从模式串的pos位置开始匹配
return 找到模式串在目标串当中的下标
*/
int KMP( char* str,  char* sub ,int pos) 
{
    int lenstr = strlen(str);
    int lensub = strlen(sub);
	if (str == NULL || sub == NULL) //两串不为空
    {
        return -1;
    }
    if (pos < 0 || pos >= lenstr) //开始查找位置不合法
    {
        return -1;
    }
    int* next = (int*)malloc(lensub * sizeof(int));//定义一个和字串一样长的数组
    int i = 0;//遍历主串
    int j = pos;//遍历子串
    GetNext(sub, next);
    while (i < lenstr && j < lensub) 
    {
        if (j==-1||str[i] == sub[j]) 
        {
            i++;
            j++;
        }
        else 
        {     
            j = next[j];
        }
    }
    if (j >= lensub)
    {
        return i - j;
    }
    else 
        return -1;
}
int main() 
{
    printf("%d\n", KMP("ababcabcdabcde", "abcd",0));//5
    printf("%d\n", KMP("ababcabcdabcde", "abcdd",0));//-1
    printf("%d\n", KMP("ababcabcdabcde", "a",0));//0
}

4.next数组的优化

为什么要优化next数组呢?

  • 如下图,假如模式串在5下标与目标串匹配失败了,那它是不是要回退到4下标,然而4下标也和目标串不一样,还得回退,3…2…1…0…-1,那这么一步步回退是不是很麻烦呢?能不能一步回退到位呢?所以我们就引入nextval数组来改进它。

KMP算法--终于弄懂了_第21张图片

  • nextval数组怎么求呢?
    ①回退到的位置和当前位置字符一样,那就写回退的那个位置的nextval的值
    ②如果回退到的位置和当前字符不一样,就写当前字符的next值(也就是k的值)

KMP算法--终于弄懂了_第22张图片

void GetNextValue( char* sub, int* nextvalue)
{
    int lensub = strlen(sub);
    nextvalue[0] = -1;
    nextvalue[1] = 0;
    int i = 2;
    int k = 0;
    while (i < lensub)
    {
        if (k == -1 || sub[i - 1] == sub[k])
        {
            if(sub[i]==sub[k]) 
            {
                nextvalue[i] = nextvalue[k];//如果回退到的位置的数据 等于当前位置对应的数据,则回退到那个位置的nextvalue值
            }
            else //(sub[i] != sub[k])如果回退到的位置的数据 不等于当前位置对应的数据,则当前位置回退到自己next值,即k的值
            {
                nextvalue[i] = k;
            }
            i++;
            k++;
        }
        else
        {
            k = nextvalue[k];     //如果不相等,让k一直回退,直到sub[i-1]==sub[k]
        }
    }
}

5.完整代码

//next数组
void GetNext( char*sub ,int*next) 
{
    int lensub = strlen(sub);
    next[0] = -1;
    next[1] = 0;
    int i = 2;
    int k = 0;
    while (i<lensub) 
    {
        if (k == -1 || sub[i - 1] == sub[k]) 
        {
            next[i] = k + 1;
            i++;
            k++;
        }
        else 
        {
            k = next[k];     //如果不相等,让k一直回退,直到sub[i-1]==sub[k]
        }
    }
}
//nextvalue数组
void GetNextValue( char* sub, int* nextvalue)
{
    int lensub = strlen(sub);
    nextvalue[0] = -1;
    nextvalue[1] = 0;
    int i = 2;
    int k = 0;
    while (i < lensub)
    {
        if (k == -1 || sub[i - 1] == sub[k])
        {
            if(sub[i]==sub[k]) 
            {
                nextvalue[i] = nextvalue[k];//如果回退到的位置的数据 等于当前位置对应的数据,则回退到那个位置的nextvalue值
            }
            else //(sub[i] != sub[k])如果回退到的位置的数据 不等于当前位置对应的数据,则当前位置回退到自己next值,即k的值
            {
                nextvalue[i] = k;
            }
            }
            i++;
            k++;
        }
        else
        {
            k = nextvalue[k];     //如果不相等,让k一直回退,直到sub[i-1]==sub[k]
        }
    }
}
int KMP( char* str,  char* sub ,int pos) 
{
    int lenstr = strlen(str);
    int lensub = strlen(sub);
	if (str == NULL || sub == NULL) //两串不为空
    {
        return -1;
    }
    if (pos < 0 || pos >= lenstr) //开始查找位置不合法
    {
        return -1;
    }
    int* next = (int*)malloc(lensub * sizeof(int));//定义一个和字串一样长的数组
    int i = 0;//遍历主串
    int j = pos;//遍历子串
    GetNextValue(sub, next);
    //GetNext(sub,next);
    while (i < lenstr && j < lensub) 
    {
        if (j==-1||str[i] == sub[j]) 
        {
            i++;
            j++;
        }
        else 
        { 
            j = next[j];
        }
    }
    if (j >= lensub)
    {
        return i - j;
    }
    else 
        return -1;
}
int main() 
{
    printf("%d\n", KMP("ababcabcdabcde", "abcd",0));//5
    printf("%d\n", KMP("ababcabcdabcde", "abcdd",0));//-1
    printf("%d\n", KMP("ababcabcdabcde", "a",0));//0
}

你可能感兴趣的:(算法,数据结构,c语言)