美文网首页
算法—字符串匹配KMP算法

算法—字符串匹配KMP算法

作者: 土豆骑士 | 来源:发表于2020-05-16 12:33 被阅读0次

    有一个主串S = {a, b, c, a, c, a, b, d, c}, 模式串T = {a,b,d} 式串在主串S中第一次出现的位置;
    提示: 不需要考虑字符串大小写问题, 字符均为小写字母。

    KMP算法核心:KMP算法的时间复杂度O(m+n)。
    尽量减少模式串T与主串S的匹配次数以达到快速匹配的目的。主要是通过一个next()函数实现,函数本身包含了模式串T的局部匹配信息以及求得next数组的规律,next数组表示的是一次遍历匹配失败后,模式串T的下标j 该移动到第几位,然后再进行下一次遍历匹配。

    KMP算法探索(PS:图中i 与 j 的初始值 均从1 开始)

    在BF算法解决字符串匹配时,主串S和模式串T中某个字符不相等时,S串需要回退到之前对比过的字符的下一个字符位置 i,T串需要继续回退到 j = 1的位置,进行下一次遍历匹配对比,如此循环,这个过程主串S可能需要回退很多遍,这导致其时间复杂度到了O(n*m)。

    1、BF算法中不必要的对比发现** — 可减少主串S的index回溯
    BF模拟1!

    接下来开始主串S和 模式串T的index 回溯对比


    BF模拟2,3,4,5,6

    操作①中,前5个字母S&T中都相等,T串首字母“a”与后续字母均不相同,可知“a”不与S中2-5的字母相同。以下:

    不相等
    所以,后续的2 3 4 5的对比很多余,只保留①与⑥的对比即可。 BF算法中不必要的对比发现

    这些不必要的对比,不必要的index 回溯,正式KMP算法规避的,也是KMP算法核心之一,算法如此哦。

    2、前缀=后缀的发现,可以使T串中 j 回溯到 j = 3 的位置。** — 减少回溯
    前缀后缀的发现
    没有相等的前缀后缀

    􏰬􏰓􏲀􏰱􏰲 􏲁􏲋􏳪􏱃􏱪􏰱􏰲􏰓􏰏􏰐T串首字符串前缀与自身后边的字符后缀比较,若相等,则j 的值可以变化 减少回溯。
    因此得出以下规律:

    T串遍历, j 回溯规律
    3、next数组值推导**

    case1:模式串中无任何重复字符

    next数组值

    case2: 模式串“abcabx” 包含重复的前后缀“a” 和 "ab"

    前后缀“a” 和 "ab"

    case3: 模式串“aaaaaaab "

    模式串“aaaaaaab "
    得出经验结论:如果前缀后缀有一个字符相等,回溯值k = 2, 2个字符相等k = 3, n个相等 则k = n+1;

    也就是说,next数组里面存放的是要查找的字符串前i个字符串的所有前缀、后缀相等的公共串(多个,“a” "ab")中,公共串的最大的长度值

    4、next数组代码实现

    思路:
    1:遍历模式串T,next数组默认next[1] = 0;
    2:需要i, j ,i表示前缀下标,j 后缀下表,i = 0时,从头开始对比,i++,j++,则next[j] = i;
    3:当T[I] == T[j]时,表示有前后缀字符相等,则i++ j++,next[j] = i;
    4:当T[I] != T[j]时,i需要回退到合理位置,i = next[i];

    void get_next(String T,int *next){
        int i = 0;
        int j = 1;
        next[1] = 0;
        
        //printf("length = %d\n",T[0]);
        while (j < T[0]) {//T[0]为模式串T的长度
            //printf("i = %d j = %d\n",i,j);
            if(i ==0 || T[i] == T[j]){
                //考虑 T = a b c a b x
                //next = [0,1,1,1,2,3]
                
                ++i;    //T[i] 表示前缀的单个字符;
                ++j;    //T[j] 表示后缀的单个字符;
                next[j] = I;
                printf("next[%d]=%d\n",j,next[j]);
            }else {
                
                i = next[i];//如果字符不相同,则i值回溯;
            }
        }
    }
    
    //KMP 匹配算法(1)
    //返回子串T在主串S中第pos个字符之后的位置, 如不存在则返回0;
    int Index_KMP(String S,String T,int pos){
        
        //i 是主串当前位置的下标准,j是模式串当前位置的下标准
        int i = pos;
        int j = 1;
        
        //定义一个空的next数组;
        int next[MAXSIZE];
        
        //对T串进行分析,得到next数组;
        get_next(T, next);
        count = 0;
        //注意: T[0] 和 S[0] 存储的是字符串T与字符串S的长度;
        //若i小于S长度并且j小于T的长度是循环继续;
        while (i <= S[0] && j <= T[0]) {
            
            //如果两字母相等则继续,并且j++,i++
            if(j == 0 || S[i] == T[j]){
                I++;
                j++;
            }else{
                //如果不匹配时,j回退到合适的位置,i值不变;
                j = next[j];
            }
        }
        
        if (j > T[0]) {
            return i-T[0];
        }else{
            return -1;
        }
        
    }
    

    KMP算法next数组获取优化

    当S = "aaaabcde" T = "aaaaax"时,next = [0,1,2,3,4,5],初次遍历到 x 时,I = 5,j = 5, next[5] = 4, j 回溯到 j = 4 为“a”, I = 5 为b ,对比不相等,继续的话按照next[j-1] 再对比依然不匹配,对比操作多余了,可优化。为了节省对比,需要next = [0,0,0,0,0,5]
    解读一下示例:


    优化next数组
    优化解析

    思路:
    在求解nextVal数组的5种情况:

    1. 默认nextval[1] = 0;
    2. T[i] == T[j] 且++i,++j 后 T[i] 依旧等于 T[j] 则 nextval[i] = nextval[j]
    3. i = 0, 表示从头开始i++,j++后,且T[i] != T[j] 则nextVal = j;
    4. T[i] == T[j] 且++i,++j 后 T[i] != T[j] ,则nextVal = j;
    5. 当 T[i] != T[j] 表示不相等,则需要将i 退回到合理的位置. 则 i = next[i];
    void get_next(String T,int *next) {
        
        int i = 0;
        int j = 1;
        next[1] = 0;
        
        while (j < T[0]) {
            if (i == 0 || T[i] == T[j]) {
                i++;
                j++;
                if (T[i] != T[j]) {//如果当前字符与前缀不同,则当前的j为next 在i的位置的值
                    next[j] = i;
                } else {//如果当前字符与前缀相同,则将前缀的next值 值赋值给next[j] 在i的位置
                    next[j] = next[i];
                }
                
            } else {
                i = next[i];//如果字符不相同,则i值回溯;
            }
        }
    }
    
    int main(int argc, const char * argv[]) {
        // insert code here...
        printf("Hello, KMP匹配算法实现!\n");
        int i,*p,*t;
        String s1,s2;
        int Status;
        
        /*关于next数组的求解*/
        StrAssign(s1,"aaaaax");
        printf("子串为: ");
        StrPrint(s1);
        i=StrLength(s1);
        p=(int*)malloc((i+1)*sizeof(int));
        get_next(s1,p);
        printf("Next为: ");
        NextPrint(p,StrLength(s1));
        t=(int*)malloc((i+1)*sizeof(int));
        get_nextVal(s1, t);
        printf("NextVal为: ");
        NextPrint(t,StrLength(s1));
        printf("\n");
        
        //KMP算法调用
        StrAssign(s1,"abcababca");
        printf("主串为: ");
        StrPrint(s1);
        StrAssign(s2,"abcdex");
        printf("子串为: ");
        StrPrint(s2);
        Status = Index_KMP(s1,s2,0);
        printf("主串和子串在第%d个字符处首次匹配(KMP算法)[返回位置为负数表示没有匹配] \n",Status);
        Status = Index_KMP(s1, s2, 1);
        printf("主串和子串在第%d个字符处首次匹配(KMP_2算法)[返回位置为负数表示没有匹配] \n\n",Status);
        
        StrAssign(s1,"abccabcceabc");
        printf("主串为: ");
        StrPrint(s1);
        StrAssign(s2,"abcce");
        printf("子串为: ");
        StrPrint(s2);
        Status = Index_KMP(s1,s2,-2);
        printf("主串和子串在第%d个字符处首次匹配(KMP算法)[返回位置为负数表示没有匹配] \n",Status);
        Status = Index_KMP(s1, s2, 1);
        printf("主串和子串在第%d个字符处首次匹配(KMP_2算法)[返回位置为负数表示没有匹配] \n\n",Status);
        
        StrAssign(s1,"aaaabcde");
        printf("主串为: ");
        StrPrint(s1);
        StrAssign(s2,"aaaaax");
        printf("子串为: ");
        StrPrint(s2);
        Status = Index_KMP(s1,s2,1);
        printf("主串和子串在第%d个字符处首次匹配(KMP算法)[返回位置为负数表示没有匹配] \n",Status);
        Status = Index_KMP(s1, s2, 1);
        printf("主串和子串在第%d个字符处首次匹配(KMP_2算法)[返回位置为负数表示没有匹配] \n\n",Status);
        
        return 0;
    }
    // 打印
    Hello, KMP匹配算法实现!
    子串为: aaaaax
    next[2]=1
    next[3]=2
    next[4]=3
    next[5]=4
    next[6]=5
    Next为: 012345
    NextVal为: 000005
    
    主串为: abcababca
    子串为: abcdex
    next[2]=1
    next[3]=1
    next[4]=1
    next[5]=1
    next[6]=1
    主串和子串在第-1个字符处首次匹配(KMP算法)[返回位置为负数表示没有匹配] 
    主串和子串在第-1个字符处首次匹配(KMP_2算法)[返回位置为负数表示没有匹配] 
    
    主串为: abccabcceabc
    子串为: abcce
    next[2]=1
    next[3]=1
    next[4]=1
    next[5]=1
    主串和子串在第5个字符处首次匹配(KMP算法)[返回位置为负数表示没有匹配] 
    主串和子串在第5个字符处首次匹配(KMP_2算法)[返回位置为负数表示没有匹配] 
    
    主串为: aaaabcde
    子串为: aaaaax
    next[2]=1
    next[3]=2
    next[4]=3
    next[5]=4
    next[6]=5
    主串和子串在第-1个字符处首次匹配(KMP算法)[返回位置为负数表示没有匹配] 
    主串和子串在第-1个字符处首次匹配(KMP_2算法)[返回位置为负数表示没有匹配] 
    

    相关文章

      网友评论

          本文标题:算法—字符串匹配KMP算法

          本文链接:https://www.haomeiwen.com/subject/iapanhtx.html