KMP模式匹配算法

来源:互联网 发布:idea java工程 编辑:程序博客网 时间:2024/06/08 15:25

本文将讲解串匹配中的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)。

原创粉丝点击