KMP 算法

KMP算法


解决的问题:给定字符串s(长度为n)和模式串f(长度为m),判断f是否在s中出现。如果存在,则返回f首次出现的位置。

子字符串通用的方法是遍历目的串和模式串,先遍历目的串s,然后遍历模式串f,复杂度是O(n*m)


KMP算法,利用构造跳转表next,字符串的前后缀。


目的串s为:BBCABCDAB ABCDABCDABDE

模式串f为:ABCDABD


匹配过程:

1、从字符从s的第一个字符开始与模式串比较,s[0]= 'B'f[0]= 'A',不匹配,向后搜索直到第一个相匹配的位置。

BBCABCDABABCDABCDABDE

ABCDABD


2s[3]= f[0] = 'A',接着搜索sf的下一个字符,

s[4]= f[1] = 'B' s[5] = f[2] = 'C' s[6] = f[3] = 'D' s[7] = f[4] = 'A'

s[8]= f[5] = 'B' s[9] = 'A' != f[6] = 'B'

BBCABCDABABCDABCDABDE

ABCDABD


3、如果按照遍历的方法,s应该搜索下一个字符串,并与f的第一个字符相比较

s[4]= 'B' f[0] = 'A' 不相等,继续向后搜索,

BBCABCDABABCDABCDABDE

ABCDABD

直到下一个相同的字符,s[7]= 'A' f[0] = 'A'

BBCABCDABABCDABCDABDE

ABCDABD

实际上,在步骤2中已经比较了模式串f字符串中的这一部分,步骤3中又重复比较了这一部分的字符,重复了这一部分。


4KMP算法利用了已经比较的模式串f的字符信息,跳过步骤3,直接比较s[7]= f[0]

如何直接跳到s[7],就要利用下面的部分匹配表了。

字符串的前缀、后缀。

BBCABCDABABCDABCDABDE

ABCDABD


5、字符串的后缀与 最长前缀 相匹配的长度

部分匹配表

模式串f A B C D A B D

部分匹配值 0 0 0 0 1 2 0


后缀和前缀均不包括满字符串,判断后缀与前缀相匹配的最大长度,最长前缀

i=0 A 后缀:无前缀:无 前缀后缀最长匹配:0

i=1 AB 后缀:B 前缀:A 最长匹配:0

i=3 ABC 后缀:CBC 前缀:AAB 最长匹配:0

i=4ABCD 后缀:DCD BCD 前缀:AAB ABC 最长匹配:0

i=5ABCDA 后缀:ADA CDA BCDA 前缀:AAB ABC ABCD 最长匹配:1A

i=6ABCDAB 后缀:BAB DAB CDAB BCDAB

前缀:AAB ABC ABCD ABCDA 最长匹配:2AB

I=7ABCDABD 后缀:DBD ABD DABD CDABD BCDABD

前缀:AAB ABC ABCD ABCDA ABCDAB 最长匹配:0

得到如下的部分匹配表

部分匹配表

模式串f A B C D A B D

部分匹配值 0 0 0 0 1 2 0


6、继续解释步骤4

当步骤2中得到 s[3]= f[0] = 'A' s[9] = 'A' f[6] = 'D' 不相等时,

BBCABCDABABCDABCDABDE

ABCDABD

根据步骤5中的部分匹配表,f字符串中f[6]='D'时不匹配,不包含f[6]字符串,f[0]~f[5]中最长前缀后缀匹配为2

i=6ABCDAB 后缀:BAB DAB CDAB BCDAB

前缀:AAB ABC ABCD ABCDA 最长匹配:2AB

字符串s移动的位数为:

移动位数=已匹配的字符数– 对应的部分匹配值

4 = 6 - 2

s向后移动4位,如下。s[7]= f[0] = 'A' s[8] = f[1] = 'B'

s[9]= 'A' f[2] = 'C'不相等

BBCABCDABABCDABCDABDE

-- - -ABCDABD


7、继续利用KMP算法

s[9]= 'A' f[2] = 'C'不相等 不包含f[2]='C',查找部分匹配值

i=1 AB 后缀:B 前缀:A 最长匹配:0

字符串s移动的位数为:

移动位数=已匹配的字符数– 对应的部分匹配值

2 = 2 - 0

字符串s移动的位数为2

BBCABCDABABCDABCDABDE

-- ABCDABD

8、继续利用KMP算法s[11]= 'A' f[0] = 'A'

s[17]= 'C' f[6] = 'D'不相等 不包含f[2]='D',查找部分匹配值

i=1 AB 后缀:B 前缀:A 最长匹配:0

字符串s移动的位数为:

i=6ABCDAB 后缀:BAB DAB CDAB BCDAB

前缀:AAB ABC ABCD ABCDA 最长匹配:2AB

字符串s移动的位数为:

移动位数=已匹配的字符数– 对应的部分匹配值

4 = 6 - 2

s向后移动4位,

BBCABCDABABCDABCDABDE

-- - - ABCDABD

完全匹配,搜索结束。


9、后缀和前缀

前缀"指除了最后一个字符以外,一个字符串的全部头部组合;

"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。


10KMP算法中的next数组,相当于最大前缀后缀右移一位,初始值设为-1。向右移动位数为:j - next[j]。其中,j为模式串中不匹配字符的位置,且j0开始计数。


模式串f A B C D A B D

最大前缀后缀 0 0 0 0 1 2 0

next数组 -10 0 0 0 1 2


为什么有了最大前缀后缀数组,还要采用next数组呢?因为数组默认是从0开始,而最大前缀后缀数组中不匹配的字符为f[6]= 'D'时,需要得到前一位'B'对应的最大前缀后缀长度2,而利用next数组右移一位后,则可以直接采用next[6]= 2

感觉用最大后缀前缀数组比较容易理解。

// 很巧妙的获取next数组,不是很好理解
void getNext(char* p, int next[])
{
    int pLen = strlen(p);  
    next[0] = -1;  
    int k = -1;  
    int j = 0;  
    while (j < pLen - 1)  
    {  
        //p[k]表示前缀,p[j]表示后缀  
        if (k == -1 || p[j] == p[k])   
        {  
            ++k;  
            ++j;  
            next[j] = k;  
        }  
        else   
        {  
            k = next[k];  
        }  
    }      
}


int KmpSearch(char* s, char* p)
{
    int i = 0;
    int j = 0;
    int sLen = strlen(s);
    int pLen = strlen(p);

    while(i < sLen && j < pLen)
    {
         if(j == -1 || s[i] == p[j])
         {
            i++;
            j++;
         }
         else
         {
            j = next[j];
         }
     }
     if (j == pLen)
        return i - j;
     else
        return -1;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值