KMP算法

KMP算法比较抽象,第一次接触的人往往晦涩难懂,博主也是前前后后好几次思索才有了些许的思路,故在此及时记录,防备遗忘。
KMP算法对于朴素匹配算法的改进是引入了一个跳转表next[]。以模式字符串abcabcacab为例,其跳转表为:

j 1 2 3 4 5 6 7 8 9 10
pattern[j] a b c a b c a c a b
next[j] 0 1 1 0 1 1 0 5 0 1
跳转表的用途是,当目标串target中的某个子部target[m…m+(i-1)]与pattern串的前i个字符pattern[1…i]相匹配时,如果target[m+i]与pattern[i+1]匹配失败,程序不会像朴素匹配算法那样,将pattern[1]与target[m+1]对其,然后由target[m+1]向后逐一进行匹配,而是会将模式串向后移动i+1 - next[i+1]个字符,使得pattern[next[i+1]]与target[m+i]对齐,然后再由target[m+i]向后与依次执行匹配。
匹配过程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
b a b c b a b c a b c a a b c a b c a b c a c a b c
a b c a b c a c a b
a b c a b c a c a b
a b c a b c a c a b
a b c a b c a c a b
a b c a b c a c a b
a b c a b c a c a b
next跳转表,在进行模式匹配,实现模式串向后移动的过程中,发挥了重要作用。这个表看似神奇,实际从原理上讲并不复杂,对于模式串而言,其前缀字符串,有可能也是模式串中的非前缀子串,这个问题我称之为前缀包含问题。以模式串abcabcacab为例,其前缀4 abca,正好也是模式串的一个子串abc(abca)cab,所以当目标串与模式串执行匹配的过程中,如果直到第8个字符才匹配失败,同时也意味着目标串当前字符之前的4个字符,与模式串的前4个字符是相同的,所以当模式串向后移动的时候,可以直接将模式串的第5个字符与当前字符对齐,执行比较,这样就实现了模式串一次性向前跳跃多个字符。所以next表的关键就是解决模式串的前缀包含。当然为了保证程序的正确性,对于next表的值,还有一些限制条件,后面会逐一说明。
跳转表的建立
`inline void BuildNext(const char* pattern, size_t length, unsigned int* next)
{
unsigned int i, t;

i = 1;  
t = 0;  
next[1] = 0;  

while(i < length + 1)  
{  
    while(t > 0 && pattern[i - 1] != pattern[t - 1])  
    {  
        t = next[t];  
    }  

    ++t;  
    ++i;  

    if(pattern[i - 1] == pattern[t - 1])  
    {  
        next[i] = next[t];  
    }  
    else  
    {  
        next[i] = t;  
    }  
}  

//pattern末尾的结束符控制,用于寻找目标字符串中的所有匹配结果用  
while(t > 0 && pattern[i - 1] != pattern[t - 1])  
{  
    t = next[t];  
}  

++t;  
++i;  

next[i] = t;  

} `
利用跳转表实现字符串匹配的算法如下:

unsigned int KMP(const char* text, size_t text_length, const char* pattern, size_t pattern_length, unsigned int* matches)  
{  
    unsigned int i, j, n;  
    unsigned int next[pattern_length + 2];  

    BuildNext(pattern, pattern_length, next);  

    i = 0;  
    j = 1;  
    n = 0;  

    while(pattern_length + 1 - j <= text_length - i)  
    {  
        if(text[i] == pattern[j - 1])  
        {  
            ++i;  
            ++j;  

            //发现匹配结果,将匹配子串的位置,加入结果  
            if(j == pattern_length + 1)  
            {  
                matches[n++] = i - pattern_length;  
                j = next[j];  
            }  
        }  
        else  
        {  
            j = next[j];  

            if(j == 0)  
            {  
                ++i;  
                ++j;  
            }  
        }  
    }  

    //返回发现的匹配数  
    return n;  
}  

【参考文献】
《Introduction to Algorithms》Second Edition

by Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest and Clifford .

推荐 “Bill_Hoo专栏” 博客,更多详细内容,可阅读http://billhoo.blog.51cto.com/2337751/411486

posted @ 2016-07-25 21:41  Bryce1010  阅读(85)  评论(0编辑  收藏  举报