KMP算法和字符串匹配

KMP算法是一个很经典的字符串匹配算法,以前只在算法课上写过作业,现在想起来,发现还是不会写了。所以再次温习记录一下。

字符串匹配

我们考虑如果不用KMP算法的话,暴力法做字符串匹配,简单写一下就是一个二重循环的写法,也就是O(m*n),复杂度是相当高的。

int n_strStr(string str1, string str2)    // 在str1 中匹配 str2 字符串
{
    int len1 = str1.size();    // 原始字符串
    int len2 = str2.size();    // 需匹配的字符串
    int j = 0;
    for(int i=0;i<len1;i++){
        if(str1[i]==str2[0]){    
            for(j=1;i<len2;j++){        
                if(str1[i+j]!=str2[j]) break;    // 中间没有匹配成功则重新匹配
            }
        }
        if(j==len2) return i; // 匹配成功则直接返回原字符串中的位置,否则返回-1
    }
    return -1;
}

KMP算法

分析暴力的字符串匹配算法,发现每一次匹配未成功之后都是重新开始,从头开始匹配。这之间肯定有很多重复的比对工作,如果能通过一种方式将重复的比对避免过去,那这复杂度就会大大降低。KMP算法就是做这个事情的。KMP算法的核心就是匹配失败后,尽量减少再次匹配的次数,快速回溯,以实现字符串的快速匹配。

以栗子说明:主串为S_{1}....S_{n},待匹配项为P_{1}....P_{6},则如果是常规字符串匹配方法,则就算匹配到了T_{6},发生了不匹配,则是重新从T_{1}开始匹配,主串往后跳一格,则是到S_{2}。在KMP算法中,如果存在T_{123}=T_{345},因为T_{345}已经和S_{345}匹配,所以则可以直接得出,T_{123}=S_{345}即是待匹配项从T_{4}开始匹配,而主串则还是在S_{6}处等待匹配

所以,KMP算法通过记录前后重叠子串,来实现发生不匹配时的快速回溯,KMP用一个next数组来记录这个数值

这时候的字符串算法发生了改变:

int kmp_strStr(string str1, string str2)    // 在str1 中匹配 str2 字符串
{
    int len1 = str1.size();    // 原始字符串,主串
    int len2 = str2.size();    // 需匹配的字符串
    next[0] = -1;              // 置第一位的next为-1
    int j = 0;
    for(int i=0;i<len1;){
        /** 
         * j=-1表示已经回溯到了0,且发生了不匹配,这时候i也会向前增加;
         * 发生匹配时,i,j 正常都增加
        **/
        if(j==-1 || str1[i]==str2[j]){
            i++;
            j++;
        } else {    // j!=-1,且发生了不匹配,则 j 进行回溯
            j = next[j];
        }
        if(j==len2) return i-j; // 匹配成功则直接返回原字符串中的位置,否则返回-1
    }
    return -1;
}

next数组的求解

 问题关键在于next数组的求解,因为next数组是代表当前子串的前后重叠字符串,所以其实本质上也是一个字符串匹配的过程,只不过这个把待匹配串在主串中匹配的位置固定了,简单来说就是:待匹配串是从0到i,在主串中匹配的位置是l-i到l。具体求解跟KMP算法一样,只不过这个需要求当前位置的next数组,具体见:

int main()  
{
    int len = pattern.size();    // pattern为待匹配项
    next[0] = -1;              // 置第一位的next为-1
    int j = -1;   //i 初始值要比 j 大1; next[1] = 0, 所以设置j从-1开始,这里区别KMP匹配过程
    for(int i=0;i<len-1;){    // i 控制在 len-2,因为next[len-1]会在本次循环中求解出
        /** 
         * j=-1表示已经回溯到了0,且发生了不匹配,这时候i也会向前增加;
         * 发生匹配时,i,j 正常都增加
        **/
        if(j==-1 || pattern[i]==pattern[j]){
            i++;
            j++;
            next[i] = j;        //  因为 i-1 和 j-1 已经发生了匹配,则 next[j] 即是当前 i
        } else {    // j!=-1,且发生了不匹配,则 j 进行回溯;
            j = next[j];
        }
    }
    return 0;
}

可以发现和KMP算法匹配本身没有多大区别,就是一个 i,j 的初始值需要注意一下。利用next的定义,在求next[i]之前,i-1j-1如果已经发生匹配,则说明next[i]=j,否则将 j 进行回溯。

文章参考:https://www.tomorrow.wiki/archives/1828

下一篇以LeetCode实例介绍KMP算法的应用。

传送门:leetcode题集: 28. Implement strStr()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值