csdn_spider/blog/ds19991999/原创-- KMP算法详解.md

6.0 KiB
Raw Permalink Blame History

原创

KMP算法详解

KMP算法详解

要求:对一个串中某个子串进行定位操作,返回匹配到的串的起始位置
假设所有串的起始字符索引为1

数据结构定义

typedef struct
{
    char *ch;
    int length;
}Str;

一、简单模式匹配

//指向原串的索引i需要回溯从原串中的每一个字符重新进行匹配直到匹配成功
int index(Str str,Str substr)
{
    //i,j分别为原串和模式串的索引
    //k记录匹配时上一次的起始位置
    int i=1,j=1,k=i;
    while(i<=str.length&&j<=str.length)
    {
        if(str.ch[i]==substr.ch[j])++i,++j;
        //匹配失败则回溯到上一次开始匹配的位置加1
        else j=1,i=++k; //i的回溯  
    }
    if(j>substr.length) return k;//返回匹配成功索引
    else return 0;//没有匹配到
}

二、KMP算法

1.思路

  • 主体思路:
    1i 不用进行回溯当原串和模式串不发生匹配时先找出模式串中的不匹配字符pj取其模式串的子串F=p1p2p3…pj-1找出F的前部分FL和后部分FR最先发生相重合的位置将模式串后移到该位置即j重新指向的位置是F串中前后重合的子串长度加1
    2) 我们可以定义一个next[j]数组表示模式串中第j个字符不发生匹配时应该从next[j]处的字符重新与原串比较。- 特殊情况:
    1模式串的第一个字符与原串就不匹配则从原串的下一个位置同模式串进行匹配
    2当串F中不存在前后重合的部分则从原串中不发生匹配的字符同模式串的第一个字符开始比较- 求next[j]数组例子
模式串ABABABB
j1234567 next[j]0112345

将上述描述转换成简洁的代码描述:

//t=next[j]
//这一点是KMP的核心仔细琢磨
if(pj=pt)next[j+1]=t+1;
else t=next[t];

2.next[ j ]数组代码实现

void getnext(Str substr, int next[])
{
    int i=1,j=0;
    //对next[1]进行初始化,即next[i]=j,这一点也很重要
    next[1]=0;
    while(i<substr.length)
    {
        //模式串匹配
        //如果pi=pj,则next[i+1]=j+1
        //如果不匹配则j=next[j];
        if(j==0||substr.ch[i]==substr[j])next[++i]=++j;
        else j=next[j];
    }
}

3.著名的KMP算法

int KMP(Str str, Str substr, int next[])
{
    int i=1,j=1;
    while(i<=str.length&&j<=str.length)
    {
        if(j==0||str.ch[i]==substr.ch[j])++i,++j;
        else j=next[j];
        //没有了i的回溯这是KMP算法的精髓
        //充分利用了模式串的重复性
        //即使不存在重复字段,在比较时,实现最大的移动量
    }
    if(j>substr.length)return i-substr.length;//返回匹配成功索引
    else return 0;//没有匹配到
}

三、上述KMP算法的改进

上述KMP算法在一种特殊情况下有些匹配显得有些多余
例如下面这个next数组:

模式串AAAAAB
j123456 next[j]012345
当j = 5时发生不匹配时因next[5] = 4则需将j回溯4进行比较而next[4]=3则需将j回溯到3进行比较。。。j需要一次回溯到5、4、3、2、1的位置上我们可以改进一下next数组直接跳过位置1~4的回溯定义改进后的数组为nextval[ j ]。

代码实现步骤:

//k=next[j]
//1.当j=1时和next数组一样将nextval[1]=0;
//2.当pj=pk时nextval[j]=nextval[k];
//3.pj!=pk时nextval[j]=k;
//第二步是算法改进的关键

//这是next数组放在一起比较
//t=next[j]
//这一点是KMP的核心仔细琢磨
if(pj=pt)next[j+1]=t+1;
else t=next[t];

//于是可以改进为
//匹配成功
++i;++j;
if(substr.ch[i]!=substr.ch[j])nextval[i]=j;
else nextval[i]=nextval[j];
//匹配失败,直接跳转到nextval[j]
j=nextval[j];

nextval数组函数

void getenextval(Str substr, int nextval[])
{
    int i=1,j=0;
    nextval[1]=0;
    while(i<substr.length)
    {
        if(j==0||substr.ch[i]=substr.ch[j])
        {
            ++i;++j;
            if(substr.ch[i]!=substr.ch[j]) nextval[i]=j;
            else nextval[i]=nextval[j];
        }
        else j=nextval[j];
    }
}

四、最完整的KMP算法实现

typedef struct
{
    char *ch;
    int length;
}Str;

void getenextval(Str substr, int nextval[])
{
    int i=1,j=0;
    nextval[1]=0;
    while(i<substr.length)
    {
        if(j==0||substr.ch[i]=substr.ch[j])
        {
            ++i;++j;
            if(substr.ch[i]!=substr.ch[j]) nextval[i]=j;
            else nextval[i]=nextval[j];
        }
        else j=nextval[j];
    }
}

int KMP(Str str, Str substr, int nextval[])
{
    int i=1,j=1;
    while(i<=str.length&&j<=str.length)
    {
        if(j==0||str.ch[i]==substr.ch[j])++i,++j;
        else j=nextval[j];
    }
    if(j>substr.length)return i-substr.length;
    else return 0;
}