KMP算法是一种用于优化字符串匹配的算法。
文章目录
一、KMP算法是什么?
KMP算法是一种字符串匹配算法,可以在 O(n+m) 的时间复杂度内实现两个字符串的匹配。其主要思想是当出现字符串不匹配时,通过之前记录的已经匹配的文本信息,直接跳过不可能的字符串匹配,避免重新匹配。
二、前缀表与Next数组
1.前缀表
前缀表的作用是跳过不可能成功的字符串匹配,并进行回退,其回退的位置是通过Next数组来操作的。
2.Next数组
Next数组就是一个前缀表,在字符串匹配问题中,我们的目的是寻找在主串 s 中是否存在模式串 t 若存在的话,其出现的位置在哪?而Next 数组就是用于模式串 t的,其元素 next[i] 表示 t[0] ~ t[i] 这一个子串中,最长的公共前后缀的长度。
例如在模式串baabaa中,Next数组的长度为模式串的长度,初始化Next [ 0 ] = 0,其余元素如下图所示,红色代表最长公共前后缀。
下方箭头表示 i ,初始化为1,上方箭头 j 表示后缀的结尾位置,上下箭头索引对应的字符不相同,由图看出从i到j的子串中无公共前后缀,所以Next [1] = 0,上箭头 j 不移动;
下箭头 i 向右移动,此时情况同上,故Next [2] =0,上标不移动;
下箭头继续向右,上下箭头索引对应的字符相同,此时存在公共前后缀 b,因此 Next [3] =j +1 =1,上标将会右移;
此时下箭头移动后,上下箭头索引对应的字符相同,存在的最长公共前后缀为ba,因此Next [4]继续+1=2,上箭头继续右移;
此时下箭头移动后,上下箭头索引对应的字符相同,存在的最长公共前后缀为baa,因此Next [5]继续+1=3,上箭头继续右移;此时上箭头索引为3,下箭头索引为6,当不相等时,上箭头会持续左移,直到上下箭头索引对应的字符相同,最终next [6]=3
Next[] ={0,0,0,1,2,3,3}
3.通过Next数组进行字符串匹配
如图所示,当匹配到模板串的尾字母a时,出现了不匹配的情况,此时将通过Next数组中尾字母a对应的索引,从该索引处重新匹配(该索引之前的字符无需重新匹配);
重新匹配的情况如图所示,此时情况同上出现了尾字母不匹配的情况,继续根据Next数组进行回退,
此时进行了成功匹配。
4.代码实现
Next数组的实现,其Java代码如下所示:
private void getNext(int[] next , String s ){ int j = 0; next[0] = 0; for (int i = 0; i < s.length(); i++){ while ( j > 0 && s.charAt(i) != s.charAt(j)) j = next[j-1]; if (s.charAt(j) == s.charAt(i)) j++; next[i] = j; } }
字符串匹配的整体代码实现:
其中 s 表示主串 ,t表示模板串,当成功匹配时,将返回匹配成功开始时的下标,否则将返回false,表示s中不存在t。
class Solution { private void getNext(int[] next , String s ){ int j = 0; next[0] = 0; for (int i = 0; i < s.length(); i++){ while ( j > 0 && s.charAt(i) != s.charAt(j)) j = next[j-1]; if (s.charAt(j) == s.charAt(i)) j++; next[i] = j; } } public int strStr(String s, String t) { if (t.length() == 0) return 0; int[] next = new int[t.length()]; getNext(next, t); int j = 0; for (int i = 0; i < s.length(); i++){ while (j > 0 && t.charAt(j) != s.charAt(i)) j = next[j-1]; if (t.charAt(j) == s.charAt(i)) j++; if (j == t.length()) return i-t.length()+1; } return -1; } }
总结
Next数组计算的形式有很多,有的会对数组进行整体左移,有的则会进行减1操作,但其根本的思想是一致的,对于字符串匹配问题,引入KMP算法能大大降低问题的时间复杂度。