KMP算法(Knuth-Morris-Pratt Algorithm)是一种非常高效的字符串匹配算法,是由Knuth,Morris和Pratt三位与1977年发布的算法。最坏复杂度为O(n+m)
首先我们用一个例子来演示这个算法:
原串为babababcbababababb
模式串为bababb
模式串的失配数组为0,1,1,2,3,4
当i = 6, j = 6时,出现了第一次不匹配,于是获取到失配指针fail[j],使j = fail[j]继续进行比较,即此时的i = 6, j = fail[j] = 4。如下图所示:
结果发现在i = 8, j = 6的时候,再次失配,于是j再次赋值为失配指针数值。依次匹配,发现最后当i = 8,j = 1的时候仍然无法匹配,则j = 0,如下图:
循环往复,直到得到最后的结果:
可以直观的发现,其实KMP算法的精髓就在于失配指针上面。
那么什么是失配指针呢?如何获得失配指针的值呢?
首先,失配指针就是当匹配失败时,所能跳转到的最近的位置。换种说法,失配指针的值就是模式串的[1..j]子串的前缀与后缀的最大匹配值+1。
所以获取这个的值就可以这么写:
fail[j] = max{k | pattern.substring(1...k - 1) == pattern.substring(j - k + 1, j)};
根据这个公式,于是我们可以获得示例中的模式串的失配数组的取值。
在匹配的过程中,就按照示例中的匹配方式一样,如果匹配相同的就继续向下匹配,如果匹配到失败,就使得j = fail[j]继续匹配。然后就可以得到匹配的结果了。
KMP算法Java代码如下:
public static void getNext( String pattern ) { fail = new int[ pattern.length() ]; fail[0] = -1; for( int i = 0, j = -1, len = pattern.length(); i < len - 1; ++i, ++j ) { while( j != -1 && pattern.charAt(i) != pattern.charAt(j) ) j = fail[j]; fail[i + 1] = j + 1; } } public static int kmp( String pattern, String origin ) { getNext( pattern ); int ans = 0; for( int i = 0, j = 0, len = origin.length(), lenP = pattern.length(); i < len; ++i, ++j ) { while( j != -1 && pattern.charAt(j) != origin.charAt(i) ) j = fail[j]; if( j == lenP - 1 ) { ++ans; j = fail[j]; while( j != -1 && pattern.charAt(j) != origin.charAt(i) ) j = fail[j]; } } return ans; }
附上hihocoder-1015-kmp算法ac代码:
import java.io.BufferedInputStream; import java.io.IOException; import java.util.Scanner; public class Main { /** * @param args * @author wiklvrain */ static int[] fail; public static void getNext( String pattern ) { fail = new int[ pattern.length() ]; fail[0] = -1; for( int i = 0, j = -1, len = pattern.length(); i < len - 1; ++i, ++j ) { while( j != -1 && pattern.charAt(i) != pattern.charAt(j) ) j = fail[j]; fail[i + 1] = j + 1; } } public static int kmp( String pattern, String origin ) { getNext( pattern ); int ans = 0; for( int i = 0, j = 0, len = origin.length(), lenP = pattern.length(); i < len; ++i, ++j ) { while( j != -1 && pattern.charAt(j) != origin.charAt(i) ) j = fail[j]; if( j == lenP - 1 ) { ++ans; j = fail[j]; while( j != -1 && pattern.charAt(j) != origin.charAt(i) ) j = fail[j]; } } return ans; } public static void main(String[] args) throws IOException { // TODO Auto-generated method stub Scanner in = new Scanner( new BufferedInputStream(System.in) ); int n = Integer.parseInt( in.nextLine() ); while( n-- > 0 ) { String pattern = in.nextLine(); String origin = in.nextLine(); System.out.println( kmp( pattern, origin ) ); } } }