1、KMP算法的介绍:
KMP算法的提出是为了解决字符串匹配问题,我们当然可以使用暴力方法来判断一个文本字符串中是否包含给定的字符串,但是使用暴力方法会存在大量的回溯,时间效率特别低。KMP算法是一种改进的字符串匹配算法,KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。(网上大量介绍什么是KMP的算法的文章,我就不罗嗦了!)
说明:KMP算法的理解是一个老大难,我在网上找了好多资料废了九牛二虎之力搞懂了,这里写一篇博客是为了便于自己以后的回顾,但是算法分析的过程我借鉴了一个大神的分析,并且拿到自己的博客里使用了,所以这里做一个说明。
2、Kmp算法的思想分析
2.1、KMP算法就是解决字符串匹配问题的,在讲述之前,我们先摆出两个概念:
前缀:指的是字符串的子串中从原串最前面开始的子串,如abcdef的前缀有:a,ab,abc,abcd,abcde
后缀:指的是字符串的子串中在原串结尾处结尾的子串,如abcdef的后缀有:f,ef,def,cdef,bcdefKMP算法引入了一个next数组,next[i]表示的是前i的字符组成的这个子串最长的相同前缀后缀的长度!
怎么理解呢?
例如字符串aababaaba的相同前缀后缀有a和aaba,那么其中最长的就是aaba。
2.2、next数组的使用
那么现在假设我们已经得到了F的所有值,我们如何利用F数组求解呢?
我们还是先给出一个例子(笔者用了好长时间才构造出这一个比较典型的例子啊):
A="abaabaabbabaaabaabbabaab"
B="abaabbabaab"
B="a b a a b b a b a a b "
next= 0 0 1 1 2 0 1 2 3 4 5
我们再用i表示当前A串要匹配的位置(即还未匹配),j表示当前B串匹配的位置(同样也是还未匹配),补充一下,若i>0则说明i-1是已经匹配的啦(j同理)。
首先我们还是从0开始匹配:
此时,我们发现,A的第5位和B的第5位不匹配(注意从0开始编号),此时i=5,j=5,那么我们看F[j-1]的值:
F[5-1]=2;
这说明我们接下来的匹配只要从B串第2位开始(也就是第3个字符)匹配,因为前两位已经是匹配的啦,具体请看图:
然后再接着匹配:
我们又发现,A串的第13位和B串的第10位不匹配,此时i=13,j=10,那么我们看F[j-1]的值:
F[10-1]=4
这说明B串的0~3位是与当前(i-4)~(i-1)是匹配的,我们就不需要重新再匹配这部分了,把B串向后移,从B串的第4位开始匹配:
这时我们发现A串的第13位和B串的第4位依然不匹配
此时i=13,j=4,那么我们看F[j-1]的值:
F[4-1]=1
这说明B串的第0位是与当前i-1位匹配的,所以我们直接从B串的第1位继续匹配:
但此时B串的第1位与A串的第13位依然不匹配
此时,i=13,j=1,所以我们看一看F[j-1]的值:
F[1-1]=0
好吧,这说明已经没有相同的前后缀了,直接把B串向后移一位,直到发现B串的第0位与A串的第i位可以匹配(在这个例子中,i=13)
再重复上面的匹配过程,我们发现,匹配成功了!
这就是KMP算法的过程。
另外强调一点,当我们将B串向后移的过程其实就是i++,而当我们不动B,而是匹配的时候,就是i++,j++,这在后面的代码中会出现,这里先做一个说明。
3、KMP算法的代码实现
1 package com.baozi.kmp; 2 3 /** 4 * @author BaoZi 5 * @create 2019-07-07-9:39 6 */ 7 public class KmpAlgorithm { 8 public static void main(String[] args) { 9 String str1 = "BBC ABCDAB ABCDABCDABDE"; 10 String str2 = "ABCDABD"; 11 int[] next = kmpNext(str2); 12 int index = kmpsearch(str1, str2, next); 13 System.out.println(index); 14 15 } 16 17 //写出我们的KMP算法, 18 19 /** 20 * @param str1 原字符串 21 * @param str2 子串 22 * @param next 字串的部分匹配表 23 * @return 返回-1表示没有匹配到,否则返回第一个匹配的位置 24 */ 25 public static int kmpsearch(String str1, String str2, int[] next) { 26 //遍历 27 for (int i = 0, j = 0; i < str1.length(); i++) { 28 while (j > 0 && str1.charAt(i) != str2.charAt(j)) { 29 j = next[j - 1]; 30 } 31 if (str1.charAt(i) == str2.charAt(j)) { 32 j++; 33 } 34 if (j == str2.length()) { 35 return i - j + 1; 36 } 37 } 38 return -1; 39 } 40 41 /** 42 * @param dest 需要查找部分匹配值表的字符串 43 * @return 返回的是该字符串的部分匹配值表数组 44 */ 45 public static int[] kmpNext(String dest) { 46 //创建一个next数组保存部分匹配值 47 int[] next = new int[dest.length()]; 48 next[0] = 0; 49 for (int i = 1, j = 0; i < dest.length(); i++) { 50 //这里是KMP算法的核心 51 while (j > 0 && dest.charAt(i) != dest.charAt(j)) { 52 j = next[j - 1]; 53 } 54 if (dest.charAt(i) == dest.charAt(j)) { 55 j++; 56 } 57 next[i] = j; 58 } 59 return next; 60 } 61 }