KMP模式匹配算法

本文将讲解串匹配中的KMP模式匹配算法。

模式匹配

首先,讲解什么是模式匹配。一个子串在主串中的定位操作通常称做串的模式匹配。在串匹配中,一般将主串称为目标串,将子串称为模式串。本文将统一用S表示目标串,T表示模式串,将从目标串S中查找模式串T的过程称为模式匹配。

朴素的模式匹配算法

朴素的模式匹配算法是一种最简单暴力的模式匹配算法。该算法从目标串S的第一个字符开始和模式串T的第一个字符进行比较,如果相等则进一步比较二者的后继字符,否则从目标串的第二个字符开始再重新与模式串T的第一个字符进行比较,以此类推,直到模式串T与目标串S中的一个子串相等,称为匹配成功,返回T在S中的位置;或者S中不存在值与T相等的子串,称匹配失败,返回-1。

简单来说,就是对主串的每一个字符作为子串开头,与要匹配的字符串进行匹配。对主串做大循环,每个字符开头做T的长度的小循环,直到匹配成功或者全部遍历完成为止。

下面是朴素模式匹配算法的Java实现

 /**
   * 朴素模式匹配算法
   * 返回子串T在主串S中第pos个字符之后的位置。若不存在,则返回-1
   */
   public int Brute_Force(String S, String T, int pos){
        int i = pos;  //i用于主串S中当前位置下标
        int j = 0;    //j用于子串T中当前位置下标值

        while(i < S.length() && j < T.length()){  //若i小于S长度且j小于T的长度
            if(S.charAt(i) == T.charAt(j)){ //两字母相等则继续
                i++;
                j++;
            }else{//如果不相等,则主串S中当前位置下标回溯到原先位置的下一位
                i = i-j+1;
                j = 0;     //子串当前位置退回到首位
            }
        }

        if(j >= T.length())
            return i-T.length();
        else
            return -1;
    }

KMP模式匹配算法

朴素的模式匹配算法虽然比较简单,也比较容易理解。但是它的效率是十分低下的,因为需要逐个遍历。因此,D.E.Knuth, J.H.Morris, V.R.Pratt三位前辈共同研究出了KMP模式匹配算法, 朴素模式匹配算法之所以低效,是因为它需要不断的重复遍历主串,而KMP算法则可以大大避免重复遍历的情况。

KMP算法,是不需要对目标串S进行回溯的模式匹配算法。我们发现这种匹配算法的关键在于当出现失配情况时,应能够决定将模式串T中的哪一个字符与目标串S的失配字符进行比较。所以呢,那三位前辈就通过研究发现,使用模式串T中的哪一个字符进行比较,仅仅依赖于模式串T本身,与目标串S无关。

这里就要引出KMP算法的关键所在next数组,next数组的作用就是当出现失配情况S[i] != T[j]时,next[j]就指示使用T中的以next[j]为下标的字符与S[i]进行比较(注意在KMP算法中,i是永远不会进行回溯的)。还需要说明的是当next[j] = -1时,就表示T中的任何字符都不与S[i]进行比较,下一轮比较从T[0]与S[i+1]开始进行。由此可见KMP算法在进行模式匹配之前需要先求出关于模式串T各个位置上的next函数值。即next[j],j = 0,1,2,3,…n-1。

下面将举例说明如何求解next数组

子串T = “ababaaaba”

1) 当j = 0时,next[0] = -1;

2) 当j = 1时,j由0到j-1就只有字符”a”,所以next[1]=1;

3) 当j = 2时,j由0到j-1的串是”ab”,显然”a”和”b”不相等,所以next[2] = 1;

4) 当j = 3时,j由0到j-1的串是”aba”,前缀字符”a”与后缀”a”相等,所以next[3]=2;

5) 当j = 4时,j由0到j-1的串是”abab”,前缀字符”ab”与后缀”ab”相等,所以next[4]=3;

6) 当j = 5时,j由0到j-1的串是”ababa”,前缀字符”aba”与后缀”aba”相等,所以next[5]=4;

7) 当j = 6时,j由0到j-1的串是”ababaa”,前缀字符”ab”与后缀”aa”并不相等,只有”a”相等,所以next[6]=2;

8) 当j = 7时,j由0到j-1的串是”ababaaa”,只有”a”相等,所以next[7]=2;

9) 当j = 8时,j由0到j-1的串是”ababaab”,前缀字符”ab”与后缀”ab”相等,所以next[8]=3;

因此,next = {-1,1,1,2,3,4,2,2,3}。

下面则是KMP模式匹配算法的Java实现

 /**
   * 通过计算返回子串T的next数组
   */
    public int[] get_next(String T){
        int i,j;
        i = 0;
        j = -1;
        int[] next = new int[T.length()];
        next[0] = -1;
        while(i < T.length()-1){
            if(j == -1 || T.charAt(i) == T.charAt(j)){
                i++;
                j++;
                next[i] = j;
            }else{
                j = next[j];
            }
        }
        return next;
    }

    public int KMP(String S, String T, int pos) {
        int i = pos;    //i用于主串S当前位置下标值
        int j = 0;      //j用于子串T中当前位置下标值
        //得到next数组
        int[] next = get_next(T);
        while (i < S.length() && j < T.length()) {
            if (j == -1 || S.charAt(i) == T.charAt(j)) {
                i++;
                j++;
            } else {
                //根据next数组的指示j进行回溯,而i永远不会回溯
                j = next[j];
            }
        }
        if (j == T.length()) {
            return i - j;
        } else {
            return -1;
        }
    }

KMP算法的核心就是计算子串T的每一个位置之间的字符串的前缀和后缀公共部分的最大长度(不包括字符串本身,否则最大长度始终是字符串本身)。然后,就可以利用该最大公共长度快速和主串S比较。当每次比较到两个字符串的字符不同时,则可以根据最大公共长度将子串T向前移动(已匹配长度-最大公共长度)位,再继续比较下一个位置。这样也就是说,不用将主串S回溯,并且子串T也不用挨个遍历,既然前缀和后缀部分相等,则当出现不同字符时,只需从后缀部分的下一位接着匹配即可。这样就可以省略很多遍历次数,大大提高了算法效率,时间复杂度也由O(n*m)降为O(n+m)。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值