KMP模式匹配算法

朴素匹配算法

算法的基本思想是:从主串中的指定位置的字符开始和模式串中的第一个字符比较,若相等,则继续逐个比较后续字符;否则从主串的下一个字符起再重新和模式串的字符依次比较。

    public int getIndex(String mainStr, String patternStr, int index){
        int i = index, j = 0;   //从指定下标开始匹配模式串
        int msLength = mainStr.length();    //主串的长度
        int psLength = patternStr.length(); //模式串的长度
        while(i < msLength && j < psLength){
            if(mainStr.charAt(i) == patternStr.charAt(j)){
                i++;    //字符相同则i,j都向后移动以为继续比较
                j++;
            } else {
                i = i - j + 1;  //字符不同则i,j都进行回溯
                j = 0;
            }
        }
        return j >= psLength ? i - j : -1;  //这里的i,j代表的是字符在字符串中的下标
    }

这种算法是最普通的匹配算法,算法效率低,时间复杂度在处理不同情况下不同,有[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DewiOysN-1678323993269)(null#card=math&code=O(n+m)]和O(n*m)&id=e8g42)两种情况。

KMP模式匹配算法

在模式串中有重复出现的字串,kmp算法可以减少部分主串和模式串已经重合部分的字符比较,提高效率。相比于上述算法每次匹配失败,i指针回溯,在kmp算法中i指针不用回溯,只有j指针回溯,主串相当于只遍历了一次。
如图,朴素模式匹配和kmp模式比较:
image.png
image.png
代码如下:和朴素算法唯一不同就是i指针不用回溯,j回溯位置有next数组中指定。

public int getIndex(String mainStr, String patternStr, int index){
    int i = index, j = 0;   //从指定下标开始匹配模式串
    int msLength = mainStr.length();    //主串的长度
    int psLength = patternStr.length(); //模式串的长度
    int[] next = getNext(patternStr);   //获取next数组,数组中记录了回溯位置
    while(i < msLength && j < psLength){
        if(j == -1 || mainStr.charAt(i) == patternStr.charAt(j)){
            i++;    //字符相同则i,j都向后移动以为继续比较
            j++;
        } else {
            j = next[j];    //只有j指针回溯,回溯位置有next数组指定
        }
    }
    return j >= psLength ? i - j : -1;  //这里的i,j代表的是字符在字符串中的下标
}

Next数组推导及算法实现

next数组中记录了j指针回溯位置,是kmp算法的关键。
下面举例说明推导过程:注意这里的num是字符是第几个而不是下标位置。

num=index+1123456789
模式串Tababaaaba
next[index]011234223
  1. 当num=1时,next[index]=0。第一个字符前面没有任何字符串。
  2. 当num=2时,此时字符前面的字符串为"a",属于其他情况,需要模式串从头开始比较,next[index]=1。
  3. 当num=3时,此时字符前面的字符串为"ab",属于其他情况,next[index]=1。
  4. 当num=4时,此时字字符前面的字符串为"aba",前缀字符"a",后缀"a",next[index]=2。
  5. 当num=5时,此时字字符前面的字符串为"abab",前缀字符"ab",后缀"ab",next[index]=3。
  6. 当num=6时,此时字字符前面的字符串为"ababa",前缀字符"aba",后缀"aba",next[index]=4。
  7. 当num=7时,此时字字符前面的字符串为"ababaa",前缀字符"a",后缀"a",next[index]=2。
  8. 当num=8时,此时字字符前面的字符串为"ababaaa",前缀字符"a",后缀"a",next[index]=2。
  9. 当num=9时,此时字字符前面的字符串为"ababaaab",前缀字符"ab",后缀"ab",next[index]=3。

解释说明:

  • next数组中记录的值就是该字符前面字符串前缀和后缀能够重合字符的个数再加一。但是在代码处理的部分可以整体都减一直接可以作为移动的下标。详细可见示例代码。
    public int[] getNext(String patternStr){
        int[] next = new int[patternStr.length()];  //建立next数组存储模式串中移动下标
        int i = -1, j = 0;  //i指向字符前 前缀字符串的最后一位下标 j是遍历模式串
        next[0] = -1;       //标记为回溯到头没有重合部分,主串和模式串都向后移动一位进行比较
        while(j < next.length - 1){
            if(i == -1 || patternStr.charAt(i) == patternStr.charAt(j)){
                i++;
                j++;
                next[j] = i;    //记录重合前缀字符长度,从重合部分的下一位比较
            } else {
                i = next[i];    //回溯i指针到有重合部分的前缀字符串最后一位
            }
        }
        return next;
    }

KMP模式匹配算法改进

当主串为"aaaabcde",模式串为"aaaaax",它的next数组为[-1, 0, 1, 2, 3, 4],而在i指针回溯的时候 i = 3,2,1,0。直接由i=3回溯到i=-1,从头开始比较。

num123456789
模式串Tababaaaba
next[num]011234223
nextval[num]010104210

说明这里不是从0开始的下标计数。

  1. 当num=1,nextval[index]=0。
  2. 当num=2,因为第二位字符"b"的next值是1,而第一位就是"a",它们不相等,所以nextval[2]=next[2]=1,维持原值。
  3. 当num=3,因为第三位字符"a"的next值是1,而第一位就是"a",它们相等,所以nextval[3]=next[1]=0。
  4. 当num=4,因为第四位字符"b"的next值是2,而第二位就是"b",它们相等,所以nextval[4]=next[2]=1。
  5. 当num=5,因为第五位字符"a"的next值是3,而第三位就是"a",它们相等,所以nextval[5]=next[3]=0。
  6. 当num=6,因为第六位字符"a"的next值是4,而第四位就是"b",它们不相等,所以nextval[6]=4。
  7. 当num=7,因为第七位字符"a"的next值是2,而第二位就是"b",它们不相等,所以nextval[7]=2。
  8. 当num=8,因为第八位字符"b"的next值是1,而第一位就是"a",它们不相等,所以nextval[7]=1。
  9. 当num=9,因为第九位字符"a"的next值是3,而第三位就是"a",它们相等,所以nextval[7]=next[3]=0。
    public int[] getNextVal(String patternStr){
        int[] next = new int[patternStr.length()];
        int i = -1, j = 0;
        next[0] = -1;
        while(j < next.length - 1){
            if(i == -1 || patternStr.charAt(i) == patternStr.charAt(j)){
                i++;
                j++;
                if(patternStr.charAt(j) != patternStr.charAt(i)){
                    next[j] = i;    //若字符不等 当前i值为该字符回溯下标
                } else {
                    next[j] = next[i];  //若字符相等 当前字符回溯下标和回溯字符的回溯下标相同
                }
            } else {
                i = next[i];
            }
        }
        return next;
    }

改进过的KMP算法,它是在计算出next值的同时,如果a位字符于它next值指向的b位字符相等,则该a位的nextval就指向b位的nextval值,如果不相等,则该a位的next值就是它自己a位的next的值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
KMP算法(Knuth-Morris-Pratt算法)是一种用于解决字符匹配问题的高效算法。它的主要思想是利用匹配失败时的信息,尽量减少比较次数,提高匹配效率。 KMP算法的核心是构建一个部分匹配表(Partial Match Table),也称为Next数组。这个表记录了在匹配失败时应该将模式向右移动的位置。 构建部分匹配表的过程如下: 1. 首先,将模式中的第一个字符的Next值设为0,表示当匹配失败时,模式不需要移动; 2. 然后,从模式的第二个字符开始,依次计算Next值; 3. 当第i个字符与前面某个字符相同的时候,Next[i]的值为该字符之前(不包括该字符)的相同前缀和后缀的最大长度; 4. 如果不存在相同的前缀和后缀,则Next[i]的值为0。 有了部分匹配表之后,KMP算法的匹配过程如下: 1. 用i和j来分别表示模式和主的当前位置; 2. 如果模式中的字符和主中的字符相同,那么i和j都向右移动一位; 3. 如果模式中的字符和主中的字符不同,那么根据部分匹配表来确定模式的下一个位置; 4. 假设当前模式的位置为i,根据部分匹配表中的值Next[i],将模式向右移动Next[i]个位置; 5. 重复上述步骤,直到找到匹配或者主遍历完毕。 KMP算法的时间复杂度为O(m + n),其中m和n分别是模式和主的长度。相比于暴力匹配算法的时间复杂度为O(m * n),KMP算法能够大幅减少比较次数,提高匹配效率。 综上所述,KMP模式匹配算法通过构建部分匹配表并利用匹配失败时的信息,实现了高效的字符匹配。在实际应用中,KMP算法被广泛地应用于文本编辑、数据搜索和字符处理等领域。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值