• 数据结构与算法---字符串(中)


    KMP模式匹配算法

       我们的前辈D.E.Knuth、J.H.Morris和V.R.Pratt发表了一个模式匹配算法,可以大大避免重复遍历的情况,我们把它称为克努特--莫里斯----普拉特算法,简称KMP算法。我们不直接看算法,我们先通过实例来研究它的原理,懂了原理,算法学起来就easy了。

    假设主串S=”abcdefgab“,子串T=”abcdex“。如果是按朴素的模式匹配算法,需要经过以下步骤:

    image

    我们通过①可以知道:1.子串T的首位字符a与其它字符互不相等.2.子串T的前5位字符,与主串前五位字符相等.由已知条件,我们可以知道子串T的首字符a一定与主串2,3,4,5位置的字符不相等。因此,②③④⑤判断是可以省略掉的。这样,是不是效率提高了很多。(注意,字串首字符a与子串其中字符不相等,是KMP的前提。)有朋友会问:”如果子串首字符a有子串中其他字符有相同,那么还可以这么高效么?”我们一起来检验一下:

    S=“abcabcabc”,T=“abcabcbx”。分析一下处理经过:

    image

    因为子串首字符a与子串第二个字符b、第三个字符c不相等。又因为字符b、c与主串的第二位,第三位字符相等。那么可以省略掉②③步,又因为子串第一个字符a、第二个字符b,分别与子串第五个字符a、第六个字符b相等,又因为字符a、字符b分别与主串第五位、第六位字符相等,所以④⑤可以省略。这样开来,即使子串出现了与首字符相等的字符,也是可以省略掉一部分不必要的判断步骤的。对比这两个例子,我们发现,①中我的i值也就是主串的下标为6,②③④⑤分别为2,3,4,5。通过观察,我们②③④⑤是可以省略的,也就是说i值不需要回溯(朴素的模式匹配算法,是通过i判断字符是否相等,i值回溯)。KMP的原理就是控制i 的值,使其不能变小,从而减除了很多不必要的重复判断。既然i值不回溯了,那么就让j回溯。通过观察,我们发现j值得变化与主串没有关系。我们屡屡提到子串的首字符与自身后面的字符比较,我们发现,当首字符与自身后面的字符不同时,j值的变化就会不同。j值的变化取决字串T的结构中是否有重复的问题。例如上面T=“abcdex”,当中没有重复的,所以j值就由6变为1。T=“abcabx”,由于前缀ab与后缀ab是相等的,所以j由6变为了3。因此,我们得出结论:”j值得多少,取决于当前字符之前的串的前后缀的相似度”。我们把T串 各个位置j值得变化,定义为一个数组next。那么next的长度就是T串的长度。我们可以得出下面的函数定义:

    image

    next数组推导:

    T=”abcdex”

    image

    当j=1时,next[1]=0;

    当j=2时,T串2位置之前只有a,属于其他情况。next[2]=1;

    当j=3时,T串3位置之前有a、b,不相等,属于其他情况。next[3]=1;

    当j=4时,T串的4位置之前有a、b、c,不相等,属于其他情况。next[4]=1;

    当j=5时,T串的5位置之前有a、b、c、d,不相等,属于其他情况。next[5]=1;

    当j=6时,T串的6位置之前有a、b、c、d、e,不相等,属于其他情况。next[6]=1;

    所以子串T各个位置j值的变化数组为next[6]=011111

    T=”abcabx”

    image

    当j=1时,T串的一位置之前没有字符,所以next[1]=0;

    当j=2时,T串2位置之前只有一个字符a,属于其他情况.next[2]=1;

    当j=3时,T串3位置之前有字符a、b,a b不相等,所以属于其他情况.next[3]=1;

    当j=4时,T串4位置之前有abc,互不相等,所以属于其他情况next[4]=1;

    当j=5时,T串5位置之前有abca,其中位置1与位置4相等。根据’p1…pk-1’=pj-k+1…..pj-1,p1=p4,得K=2,next[5]=2;

    当j=6时,   T串6位置之前有abcab,其中位置1、2与位置4、5相等。根据’p1….pk-1=pj-k+1….pj-1,得k=3,next[6]=3;

    通过next[]数组推导,我们可以发现,当前位置字符之前的串的,如果前后缀相等字符的个数为n,那么当前位置的j值为n+1.

    KMP模式匹配算法实现

    /*
    功能:获取子串各个位置的j值变化数组
    */
    void get_next(string t, int *next)
    {
        int i=1, j=0;
        next[1] = 0;
        while (i<t[0])  //t[0]存放的是串t的长度    
        {
            if (j == 0 || t[i] == t[j])
            {
                ++i;
                ++j;
                next[i] = j;
            }
            else
            {
                j = next[j];   //若字符不符,j值回溯。
            }
        }
    }

    这段代码是为了,获得子串各个位置j值的变化。

    /*
    功能:返回子串T在主串S中pos后,出现的位置。如果没有,返回0
    */
    int index_kmp(string s, string t, int pos)
    {
        int i = pos;
        int j = 0;
        int next[255]; //用来获取子串next数组
        get_next(t, next);
        while (i <= s[0] && j <= t[0])
        {
            if (j == 0 || s[i] == s[j])
            {
                ++i;
                ++j;
            }
            else
            {
                j = next[j]; //获取j位置j的变化  (朴素算法,是将i回溯,j回溯到子串的第一位。kmp算法的不同之处,就是i保持不变,而将j回溯到合适的位置)
            }
    
        }
        if (j > t[0])
            return i - t[0];
        else
            return 0;
    }

    这段代码是kmp模式匹配的完整算法。

    KMP模式匹配算法改进

    我们发现这个KMP模式匹配算法还是有缺陷的。例如:主串S=”aaaabcde”,子串=”aaaaax”.next数组值分别为012345。看图:

    image

    当i=5,j=5,主串b与子串的a不相等。j回溯到4位置。此时同样是字符a,还是与主串的5位置b不等。j回溯到3位置,此时还是字符a,与主串的5位置b不等。依次类推,直到i改变位置。那么②③④⑤步骤,都是可以省略的。我们可以使用next[1]来代替相等字符的next[j]。例如,在①中,此时i=5,j=5,字符不相等,j应该回溯到4位置。此时我们判断4位置的字符是否与4位置相等,直接回溯到4位置对应的next数组的值3.我们看一下改进的算法代码:

    void get_nextval(string t, int *nextval)
    {
        nextval[1] = 0;
        int i = 1;
        int j = 0;
        while (i < t[0]) //i应该小于子串的长度
        {
            if (j == 0 || t[i] == t[j])
            {
                ++i;
                ++j;
                if (t[i] != t[j])   //当前字符与前缀字符不同
                    nextval[i] = j; //则将j给当前j为nextval数组的i位置的值
                else
                    nextval[i] = nextval[j];    //当前字符与前缀字符相同,则将前缀字符的nextval值给nextval在i位置的值
            }
            else
            {
                j = nextval[j];   
            }
        }
    }
  • 相关阅读:
    PHP快速入门
    redis命令_ZREVRANGEBYSCORE
    redis命令_ZRANGE
    redis命令_ZREM
    redis命令_ZINCRBY
    redis命令_ZADD
    redis命令_SETEX
    编译过程的一点心得
    关于c语言中的program_invocation_short_name
    关于toolchain(工具链)的一点知识
  • 原文地址:https://www.cnblogs.com/VitoCorleone/p/3942110.html
Copyright © 2020-2023  润新知