KMP算法

预备知识

字符串的前缀,后缀和部分匹配值

前缀:指除最后一个字符以外,字符串的所有头部子串。
后缀:指除了第一个字符外,字符串的所有尾部子串。
部分匹配值:则为字符串的前缀和后缀的最长相等前后缀长度。

ababa 为例说明

  1. 'a’的前后缀均为空集,最长相等前后缀长度为0
    'ab’的前缀为{a}, 后缀为{b},{a}∩{b}=∅,最长相等前后缀长度为0
  2. 'aba’的前缀为{a,ab}, 后缀为{ba,a}, {a,ab}∩{ba,a}={a},最长相等前后缀长度为1;
  3. 'abab’的前缀为{a,ab,aba}, 后缀为{bab,ab,b}, {a,ab,aba}∩{bab,ab,b}={ab},最长相等前后缀长度为2
  4. 'ababa’的前缀为{a,ab,aba,abab}, 后缀为{baba,aba,ba,a}, {a,ab,aba,abab}∩{baba,aba,ba,a}={a, aba},最长相等前后缀长度为3(即aba的长度)
  5. next[j] = 字符串S1…j-1的最长相等前后缀的长度 + 1

手算过程:

下面只是举例说明过程,按具体的某个next[ j ]来求解,其他位置的求解也类似。KMP的next值是自己和自己一位一位地移动后算出的每一位的next值,无需其他字符串。 规定 next[0]=0, next[1]=1, 其他位置可以按下面方法计算。
Eg1
在这里插入图片描述
求next[5]=1;第5个元素即e用红色标出
依次将串右移一位,因为假设第5位不匹配,则至少要右移一次。
下图中一共右移了4次,直到串的首字母 a 与 e同一列,说明此时在此过程中没有任何一个子串与 绿色的 bcd或bcd的后缀 有完全重合的
所以下一次则应和串的第一个字母比较,即得 next[5]=1
在这里插入图片描述
Eg2
在这里插入图片描述
求next[5]=2;第5个元素即 b 用红色标出
依次将串右移一位,因为假设第5位不匹配,则至少要右移一次。
下图中一共右移了3次,直到有一个子串与 绿色的 bca或bcd的后缀 有完全重合的, 也即第3次移动后,绿色的a和绿色的bca的后缀 a 完全重合,而第1,2次的粉色的均不与 绿色的 bcd或bcd的后缀 有完全重合的
所以下一次则应和串的第二个字母比较,即得 next[5]=2

这里因为第三次移动后就找找了,所以第四次的移动就不需要,下图中显示出来只是为了说明问题。
在这里插入图片描述
Eg3
在这里插入图片描述
求next[6]=4;第6个元素即 a 用红色标出
依次将串右移一位,因为假设第6位不匹配,则至少要右移一次。
下图中一共右移了2次,直到有一个子串与 绿色的 baba或baba的后缀 有完全重合的, 也即第2次移动后,绿色的aba和绿色的baba的后缀 aba 完全重合,而第1次的粉色的不与 绿色的 bcd 完全重合
所以下一次则应和串的第4个字母 b 比较,即得 next[5]=4

这里因为第2次移动后就找找了,所以余下几次的移动就不需要,下图中显示出来只是为了说明问题。

在这里插入图片描述
Eg4
在这里插入图片描述
求next[8]=7;第8个元素即 a 用红色标出
依次将串右移一位,因为假设第8位不匹配,则至少要右移一次。
下图中一共右移了1次,直到有一个子串与 绿色的 aaaaaa或aaaaaa的后缀 有完全重合的, 也即第1次移动后,绿色的 aaaaaa 和 绿色的 aaaaaa 完全重合
所以下一次则应和串的第二个字母比较,即得 next[8]=7

这里因为第1次就找找了,所以余下几次的移动就不需要,下图中显示出来只是为了说明问题。
在这里插入图片描述

KMP算法的实现

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

//串的顺序存储  从数组下标为1开始存储字符
#define MAXLEN 255//预定义最大串长为255
typedef struct{
    char ch[MAXLEN];//每个分量存储一个字符
    int length;//串的实际长度
}SString;

//KMP算法求next值
void get_next(SString T, int next[]){
    int i=1,j=0;//i依次指向 T 的每一位,
    //j表示假设T.ch[i]不匹配,则下一次应和串的第j个字母比较
    /*这里不容易理解:
    只有满足T.ch[i]==T.ch[j],(为了说明问题,记m=++i; n=++j)
    而后经过++i,++j,  才会执行到next[m]=n
    说明前面的n-1个字符是完全重合的,
    则当下一次T和其他所给的主串S进行匹配时,
    若到m时出现不匹配情况
    则下一次应和串T的第n个字符比较,即next[m]=n;
    */
    next[1]=0;//规定next[1]为0
    while(i<T.length){//@@@
        if(j==0||T.ch[i]==T.ch[j]){//
            ++i;
            ++j;
            next[i]=j;
        }
        else{
            j=next[j];
        }
    }
}

void PrintString(SString S){
    printf("\nString is:");
    for(int i=1;i<=S.length;i++){
        printf("%c",S.ch[i]);
    }
    printf("\n");
}

int Index_KMP(SString S,SString T){
    int i=1,j=1;
    int next[255];
    get_next(T,next);
    printf("\nnext array is:\n");
    for(int k=1;k <= T.length;k++){//next数组从1开始存数
        printf("%d ",next[k]);
    }
    printf("\n");
    while(i<=S.length && j<=T.length){
        if(j==0 || S.ch[i]==T.ch[j]){
        //j=0说明此时S.ch[i]和模式串T的任何一个字符都不匹配,所以i,j都要向后移
            ++i;++j;//继续比较后继字符
        }
        else{
            j=next[j];//S.ch[i]!=T.ch[j]说明下一次要和T.ch[ next[j] ]进行比较
        }
    }
    if(j>T.length)
        return i-T.length;//匹配成功
    else
        return 0;//匹配失败
}


int main(){
    SString S1,S2;
    //char str1[40]="hello?How are you world";
    //char str2[40]="How are you";
    char str1[40]="aaabaaaab";
    char str2[40]="aaaab";

    for(int i=0;i<strlen(str1);i++){
        S1.ch[i+1]=str1[i];
    }
    S1.length=strlen(str1);


    for(int i=0;i<strlen(str2);i++){
        S2.ch[i+1]=str2[i];
    }
    S2.length=strlen(str2);
    PrintString(S1);
    PrintString(S2);
    int x = Index_KMP(S1,S2);
    printf("\nindex is %d\n",x);
    return 0;
}

在这里插入图片描述
在这里插入图片描述

KMP算法的进一步优化


上图中S[4]和T[4]失配,如果用next数组,因为next[4]=3,则下一个需要用T[3]和S[4]比较,而T[3]为a,也失配,同理于next[3],next[2],next[1], 可以发现T的前三个字符和当前失配的a字符即T[4]是相同的,则再和S[4]相比较,必然会导致继续失配,这样的比较毫无意义,所以就产生了nextval数组。

如果出现上述情况即T[j]=T[ next[j] ],则需要再次递归,将next[j]修正为next[ next[j] ],
直到两者不相等为止,更新后的数组命为nextval.

Eg1
在这里插入图片描述

  1. j=2, T[2]=b, next[2]=1; b!= T[next[2]] 即(T[1] : a), 所以next[2]不变
  2. j=3, T[3]=a, next[3]=1; a = T[next[3]] 即(T[1] : a), 所以next[3]应修改为 next[next[3]]=next[1]=0
  3. j=4, T[4]=b, next[4]=2; b = T[next[4]] 即(T[2] : b), 所以next[4]应修改为 next[next[4]]=next[2]=1
  4. j=5, T[5]=a, next[5]=3; b = T[next[5]] 即(T[3] : b),所以next[5]应修改为next[next[5]]=next[3], 而next[3]在第二步中已经修改为0,故next[5]也修改为最新的next[3]即0
  5. j=6, T[6]=a, next[6]=4; a != T[next[6]] 即(T[4] : b), 所以next[6]不变
  6. 同理next[7]也不变

Eg2
在这里插入图片描述
下面代码可以运行出上面两个示例的 next 和 nextval 结果

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

//串的顺序存储  从数组下标为1开始存储字符
#define MAXLEN 255//预定义最大串长为255
typedef struct{
    char ch[MAXLEN];//每个分量存储一个字符
    int length;//串的实际长度
}SString;

//KMP算法求next值
void get_next(SString T, int next[]){
    int i=1,j=0;//i依次指向 T 的每一位,
    //j表示假设T.ch[i]不匹配,则下一次应和串的第j个字母比较
    /*这里不容易理解:
    只有满足T.ch[i]==T.ch[j],(为了说明问题,记m=++i; n=++j)
    而后经过++i,++j,  才会执行到next[m]=n
    说明前面的n-1个字符是完全重合的,
    则当下一次T和其他所给的主串S进行匹配时,
    若到m时出现不匹配情况
    则下一次应和串T的第n个字符比较,即next[m]=n;
    */
    next[1]=0;//规定next[1]为0
    while(i<T.length){//@@@
        if(j==0||T.ch[i]==T.ch[j]){//
            ++i;
            ++j;
            next[i]=j;
        }
        else{
            j=next[j];
        }
    }
}



void PrintString(SString S){
    printf("\nString is:");
    for(int i=1;i<=S.length;i++){
        printf("%c",S.ch[i]);
    }
    printf("\n");
}

//KMP算法求nextval值
void get_nextval(SString T, int nextval[]){
    int i=1,j=0;//i依次指向 T 的每一位,
    nextval[1]=0;//规定next[1]为0
    while(i<T.length){//@@@
        if(j==0||T.ch[i]==T.ch[j]){//
            ++i;
            ++j;
            if(T.ch[i]!=T.ch[j]){
                nextval[i]=j;
            }
            else{//T.ch[i]==T.ch[j]
//则若遇到T.ch[i]!=S.ch[i']时,也必有T.ch[j]!=S.ch[i'],
/*求next[i]就是为了T和S在匹配过程中发生了i之前的都匹配
而T.ch[i]!=S.ch[i'],为了避免 i’ 回溯,所以要看next[i]的值,
而当T.ch[i]==T.ch[j],也必有T.ch[j]!=S.ch[i'],
也即有nextval[i]=nextval[j]
*/
                nextval[i]=nextval[j];
            }
        }
        else{
            j=nextval[j];
        }
    }
}

int Index_KMP(SString S,SString T){
    int i=1,j=1;
    int nextval[255];


    int next[255];
    get_next(T,next);
    printf("\nnext array is:\n");
    for(int k=1;k <= T.length;k++){//next数组从1开始存数
        printf("%d ",next[k]);
    }
    printf("\n");


    get_nextval(T,nextval);
    printf("\nnextval array is:\n");
    for(int k=1;k <= T.length;k++){//next数组从1开始存数
        printf("%d ",nextval[k]);
    }
    printf("\n");

    while(i<=S.length && j<=T.length){
        if(j==0 || S.ch[i]==T.ch[j]){
        //j=0说明此时S.ch[i]和模式串T的任何一个字符都不匹配,所以i,j都要向后移
            ++i;++j;//继续比较后继字符
        }
        else{
            j=nextval[j];//S.ch[i]!=T.ch[j]说明下一次要和T.ch[ next[j] ]进行比较
        }
    }
    if(j>T.length)
        return i-T.length;//匹配成功
    else
        return 0;//匹配失败
}


int main(){
    SString S1,S2;
    //char str1[40]="hello?How are you world";
    //char str2[40]="How are you";
    char str1[40]="aaabaaaab";
    //char str2[40]="aaaab";
    //char str2[40]="ababaaaba";
    char str2[40]="aaaaaaaab";
    for(int i=0;i<strlen(str1);i++){
        S1.ch[i+1]=str1[i];
    }
    S1.length=strlen(str1);


    for(int i=0;i<strlen(str2);i++){
        S2.ch[i+1]=str2[i];
    }
    S2.length=strlen(str2);
   // PrintString(S1);
    PrintString(S2);
    int x = Index_KMP(S1,S2);
   // printf("\nindex is %d\n",x);
    return 0;
}

对应改进KMP算法 示例中的Eg1
对应改进KMP算法 示例中的Eg2

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值