KMP算法
-
简单的模式匹配算法
- 定义:子串的定位操作通常称为串的模式匹配,他求的是子串在主串中的位置
- 过程
- 逐个字符比较
- 从主串指针 i 对应的字符和模式串指针 j 对应的字符开始,依次比较它们是否相等。
- 若相等,则同时移动 i 和 j 向右一位,继续比较下一个字符。
- 失配处理
- 若不相等,则表明当前子串不匹配,需要根据算法类型采取不同的策略:
- 朴素匹配:主串指针 i 回退一位(恢复到失配前的状态),模式串指针 j 重置为起始位置,然后继续比较。
- 模式匹配KMP:根据预先计算的模式串的部分匹配表,将模式串指针 j 直接移动到新位置(不回溯主串),继续比较。
- 逐个字符比较
-
核心思想
-
利用已知的匹配信息
在进行字符串匹配的过程中,当模式串与主串在某个位置发生失配时,KMP算法意识到已有的部分匹配并没有被完全浪费。这是因为,从模式串的起始位置到失配点,有一段子串已经在主串中成功匹配。这一段已匹配的子串提供了关于模式串结构的重要信息。
-
避免回溯主串
- 在朴素的字符串匹配算法中,一旦发生失配,需要将模式串回溯到下一个位置(通常是前一个字符),然后继续从主串的同一位置开始重新比较。这种回溯过程可能导致大量的重复比较,尤其是在模式串中有较长相同前缀的情况下
- KMP算法的核心创新在于,它通过预先计算的“部分匹配表”(LPS表)来确定模式串失配后的下一个起始比较位置,而不需要回溯主串。部分匹配表记录了模式串中每个前缀的最长相同真前缀(同时也是后缀)的长度。当失配发生时,可以根据当前失配点对应的部分匹配表值,直接将模式串滑动到主串中相应的新位置,继续比较,而不是回溯主串。
-
-
部分匹配表(LPS)
- 最长公共前后缀:
-
对A这个子串,前缀集合,后缀集合都为空
-
对AB这个子串,前缀集合{A},后缀集合{B}
-
对于ABA这个子串,前缀集合{A,AB}后缀集合{A,BA}
-
对于ABAB这个子串,前缀集合{A,AB,ABA}后缀集合{B,AB,BAB}
-
对于ABABC这个子串,前缀集合{A,AB,ABA,ABAB},后缀集合{C,BC,ABC,BABC}
-
从而得到他们的next数组,部分匹配表
A B A B C NEXT(最长公共前后缀长度) 0 0 1 2 0
-
- 最长公共前后缀:
-
代码实现
//获取KMP算法中的next数组public static int[] kmpNext(String dest){//创建next数组int[] next=new int[dest.length()];next[0]=0;//获取next数组for (int i=1,j=0;i<dest.length();i++){while (j>0&&dest.charAt(i)!=dest.charAt(j)){j=next[j-1];}if (dest.charAt(i)==dest.charAt(j)){j++;}next[i]=j;}return next;}//j变量的作用1.追踪最长相同真前缀:初始化时,j 被设置为 0,表示当前正在考虑的最长相同真前缀起始于模式串的第一个字符。在外层 for 循环中,随着 i 从 1 到 dest.length()-1 递增,j 用于追踪模式串中以第 i 个字符结尾的子串的最长相同真前缀的最后一个字符的索引。2.更新最长相同真前缀:当 dest.charAt(i) 与 dest.charAt(j) 相等时,说明当前字符可以延长最长相同真前缀,因此将 j 增加 1,继续追踪更长的相同真前缀。将 j 设置为 next[j-1],意味着将当前考虑的最长相同真前缀的起始位置回溯到这个较短的相同真前缀的起始位置。这样做可以避免重新检查那些已经确认匹配的字符,因为它们构成了相同的真前缀。
/**** @param str1 模式串* @param str2 子串* @param next next数组* @return 若没找到返回-1,找到就返回在模式串中的下标*/public static int kmpSearch(String str1,String str2,int[] next){for (int i=0,j=0;i<str1.length();i++){while ((str1.charAt(i)!=str2.charAt(j))&&j>0){//通过next数组找到前一个位置子串的最长公共前后缀的索引值j=next[j-1];}if (str1.charAt(i)==str2.charAt(j)){j++;//子串索引后移}if (j==str2.length()){return i-j+1;}}return -1;}