KMP算法

KMP算法

问题求解:给你两个字符串 文本串 和 模式串,请你在 文本串 haystack 中找出 模式串 needle 出现的第一个位置。

实现 strStr() 函数:
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。
如果不存在,则返回 -1 。

说明: 当 needle 是空字符串时应当返回 0 ,与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符

实现strStr()方法1 – 暴力解决

j 指向模式串的当前匹配字符下标needle[j] ,i 指向文本串的当前匹配下标haystack[i],遇到不匹配就重新开始,即当needle[j] != haystack[i] 时,将 i 置为 i - j + 1,j 置为 0。

public int strStr(String haystack, String needle) {
        if(needle.length() == 0) return 0;//当 needle 是空字符串时返回 0 
        int j = 0;
        int i = 0;
        while(i < haystack.length()){
            if(haystack.charAt(i) == needle.charAt(j)){
                j++;
                i++;
            }else{//回退
                i = i - j + 1;
                j = 0;
            }
            if( j == needle.length())//说明模式串完全匹配
                return i - j;
        }
        return -1;//遍历到文本串的末尾 ,模式串不是文本串的子串 即题意的不存在
    }

实现strStr()方法2 – KMP算法

KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与文本串的匹配次数以达到快速匹配的目的。

具体实现: 通过一个getNext()函数得到next数组,数组包含了模式串的局部匹配信息。

1. next 数组

next[i]: 求解 i 下标的前面字符串的最长相等前后缀长度。

以 aaabaaa 为例 求解最长相等前后缀长度

前缀: 不包含尾字符 – > 小于字符串长度的从首字符开始且不包含尾字符的相邻字符的组合

  1. a
  2. aa
  3. aaa
  4. aaab
  5. aaaba
  6. aaabaa

后缀:不包含首字符 小于字符串长度的从尾字符开始且不包含首字符的相邻字符的组合

  1. a
  2. aa
  3. aaa
  4. baaa
  5. abaaa
  6. aabaaa

即可得 最长公共相等前后缀长度 == 3 (aaa)

最长相等前后缀:
对于模式串而言,此部分与文本串匹配,且前后相等,那么不需要重新开始,直接退到最长相等前缀长度的后一个字符进行重新匹配即可。

假设 文本串 为 aabaabaafaab 模式串 为aabaaf

当 字符不匹配时 即

在这里插入图片描述

模式串 不匹配字符 f 前的部分与文本串匹配,即 aabaa 部分;aabaa 部分的 b字符前的aab字符后的aa 相等,说明b字符前的aa 与 文本串的中 原等于 b字符后的aa字符 的那部分字符 相等;

应用数学公式表示 1部分 = 3 部分 , 2 部分 = 4 部分 ;又 3 部分 = 4 部分 所以 3 部分 = 2 部分

在这里插入图片描述

因此我们只需回退到模式串的b字符,并且文本串匹配位置保持不变,继续进行匹配即可

在这里插入图片描述

2. 求解next数组代码

即求解最长相等前后缀长度 :j 指向前缀末尾,i 指向后缀末尾

  • 如匹配则分别 +1;
  • 如不匹配 则使得 j 回退到next[ j - 1],直至对应的下标的字符匹配 或 到达 0 下标(说明最长相等前后缀长度为0)循环停止。
//getNext函数代码
private void getNext(int[] next, String needle) {
    int j = 0;
    next[0] = 0;//第一个字符不含有前后缀,故初始化为0
    for(int i = 1; i < needle.length(); i++){
        while(j > 0 && needle.charAt(j) != needle.charAt(i))
            j = next[j-1];//回退

        if(needle.charAt(j) == needle.charAt(i))
            j++;

        next[i] = j;
    }
}

为什么是回退到next[ j - 1],其实这也类似kmp算法回退过程,只不过这里是前缀在跟后缀匹配

j 和 i 指向的末尾的前面部分相等:以aaabaaa 为例,最长相等 前缀aaa 后缀aaa

若此时求解 aaabaaaf 的最长相等前后缀长度,b不等于f,next[ j - 1] 即 最长相等前后缀长度为2

用对应位置来识别字符,如第一个a 为 1a
12a 与 23a 相等,由于前后缀相等说明 56a 与 67a 相等,又 12a 与 56a 相等,那么 12a 与 67a 也相等,因此我们只需比较3a与f是否匹配,无需从头开始。

理论上下一个匹配字符是 最长相等前后缀长度 的下一个字符,又数组下标从0开始,即无需next[ j - 1] +1 , 下一步即是 j 指向 3a , 3a 跟 f 匹配。

3. KMP算法代码

算法思路: i 指向文本串,j 指向模式串,即 文本串的当前匹配下标haystack[i],模式串的当前匹配字符下标needle[j]

  • 匹配则分别+1;
  • 当不匹配, 即needle[j] != haystack[i] 时,j 回退到 next[j - 1] , i 不变,即不动i的位置,只移动j的位置。
public int strStr(String haystack, String needle) {
        if(needle.length() == 0)  return 0;//当 needle 是空字符串时返回 0 
    
        int[] next = new int[needle.length()];
        getNext(next,needle);
    
        int j = 0;
        for(int i = 0; i < haystack.length(); i++){
            while(j > 0 && haystack.charAt(i) != needle.charAt(j))
                j = next[j-1];//回退
            
            if(haystack.charAt(i) == needle.charAt(j))
                j++;
            
            if(j == needle.length())
                return i - j + 1;
        }
        return -1;
}
private void getNext(int[] next, String needle) {
    int j = 0;
    next[0] = 0;//第一个字符不含有前后缀,故初始化为0
    for(int i = 1; i < needle.length(); i++){
        while(j > 0 && needle.charAt(j) != needle.charAt(i))
            j = next[j-1];//回退

        if(needle.charAt(j) == needle.charAt(i))
            j++;

        next[i] = j;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值