KMP算法图文演示+详细解读

KMP算法介绍

KMP算法俗称“看毛片算法”,是一个解决模式串是否在文本中出现过,若出现则返回其最早出现位置的经典算法。简单 的说就是字符串查找算法。

先看一个案例:
有一个字符串str1=“ABAACABABCAC”,和一个字串str2=“ABABC”;
现在需要判断str1是否包含str2,如果包含则返回第一次出现的角标,否则返回-1

对于这个案例大家最容易想到的就是用暴力匹配来判断,先演示一下暴力匹配

暴力匹配

1、首先判断str1第一个和str2第一个角标处的元素是否相同
KMP算法图文演示+详细解读_第1张图片
2、相同则匹配第二个角标的元素
KMP算法图文演示+详细解读_第2张图片
3、继续匹配第三个
KMP算法图文演示+详细解读_第3张图片
4、继续匹配第四个
KMP算法图文演示+详细解读_第4张图片
5、当发现第五个不匹配的时候,将str2向右平移一个单位,继续从str2第一个角标处重新匹配
KMP算法图文演示+详细解读_第5张图片
6、不匹配继续后移
KMP算法图文演示+详细解读_第6张图片

一直重复此操作直到匹配到对应元素或者将str1匹配完,显然这样的效率很慢,因为要对str1的元素重复匹配。那么有没有只对str1进行一遍扫描就可以判断出结果呢,答案是有的那就是KMP算法。

如果是我们人类来完成上面的事,我们肯定不会一个个的去比较,当遇到不匹配的元素时,我们一眼就知道str2该往后移动几个单位,不可能每次移动一个单位,但是如何让电脑也知道当出现不匹配的情况时,该往后移动几个单位呢?这就是KMP算法的核心思想了。
KMP算法分为两个步骤,第一步通过算法生成一个与str2长度相同的匹配表存到next数组中,这样当str2对应位置出现不匹配情况时,可以参考该表的指示向后移动若干个单位。第二部就是对应该表进行字符串的匹配。先来演示得到匹配表之后如何移动的问题,后面再讲如何生成该匹配表。
通过算法对str2进行计算可生成下表(具体生成算法在下面)

str2 A B A B C
next数组 0 0 1 2 0

当有了该表后,再来演示一下KMP算法的匹配过程

KMP算法的匹配

1、同样先匹配str1和str2的第一个角标
KMP算法图文演示+详细解读_第7张图片
2、相同继续匹配第二个
KMP算法图文演示+详细解读_第8张图片
3、依然相同继续匹配第三个
KMP算法图文演示+详细解读_第9张图片
4、仍然相同继续匹配第四个
KMP算法图文演示+详细解读_第10张图片
5、终于发现不匹配的元素了,这个时候正是KMP算法的核心步骤,首先会去查找str2第四个元素”B“的前一个元素”A“所对应的next数组中的数字,也就是数字1,查到该数字后会找该数字对应str2中的位置(1是角标为1),也就是第2个元素B的位置,然后右移str2使第2个元素B与当前str1中的元素A对齐继续进行判断(j=next[j-1] j 表示当前str2处的角标)
KMP算法图文演示+详细解读_第11张图片
6、此时我们发现移动之后仍然无法匹配,则继续执行上面的操作。先查找str2当前位置”B“的前一个元素”A“对应next数组中的元素,也就是数字0。则此时将str2中第一个元素A(角标为0)与str1中当前元素A对齐
KMP算法图文演示+详细解读_第12张图片
7、接着匹配下一个元素
KMP算法图文演示+详细解读_第13张图片
8、又遇到了不匹配的情况,同样执行上面操作,先查到当前元素”B“前一个元素”A“对应的数字是0,则将str2中第一个元素与str1当前元素C对齐
KMP算法图文演示+详细解读_第14张图片
9、仍然不匹配,而在这个时候str2前面已经没有元素,则将str2向后移动1个单位
KMP算法图文演示+详细解读_第15张图片
10、然后继续一一对应进行匹配,这时已经可以正确匹配上了,也就不在一一演示了。
观察上面步骤我们可以发现,str2再匹配过程中始终没有后退,一直处于勇往直前的状态。这就明显看到了KMP算法的优点了。

接下来解决如何生成匹配表的问题

该表也叫前缀表,里面的数字表示当前个数该字符串的最大共有前后缀的长度
KMP算法图文演示+详细解读_第16张图片
1、next表中第一个元素表示当str2字符串长度为1时,即str2=“A”时最大相同前后缀长度,注意前后缀的长度不能超过字符串的长度 。由于不能超过字符串的长度,所以前缀和后缀的长度必须小于1,则很明显第一个空格应该是数字0。

KMP算法图文演示+详细解读_第17张图片
2、next表中第二个元素是当str2字符串长度为2时,即str2=“A B”时最大相同前后缀的长度,此时前缀只有A,后缀只有B,且A!=B则相同前后缀的长度为0。
KMP算法图文演示+详细解读_第18张图片
3、next表中第三个元素是当str2字符串长度为3时,即str2="A B A"时最大相同前后缀的长度,此时前缀有“A”和“A B”;后缀有“A”和“B A”;而最大相同前后缀只有“A”则该位置的数字应该是1;
KMP算法图文演示+详细解读_第19张图片
4、next表中第四个元素是当str2字符串长度为4时,即str2=“A B A B”时最大相同前后缀的长度,此时前缀有“A”,“A B”,“A B A”;后缀有“B”,“A B”,“B AB”;最大共有前缀是“A B”则此位置的数字应该是2;
KMP算法图文演示+详细解读_第20张图片
5、同上此时str2=”A B A B C“;前缀有"A",“A B”,“A B A”,“A B A B”;后缀有"C",“B C”,“A B C”,“B A B C”;然而并没有相同前后缀,则此位置是数字也应是0;
KMP算法图文演示+详细解读_第21张图片

再来解释一下为什么匹配过程中要参考该表,该表的意义是什么

假如有两个字符串str1和str2
KMP算法图文演示+详细解读_第22张图片
两个字符串蓝紫色部分是相等的,而str1角标i处和str2脚本j处的元素不相等,会查前缀表j-1处的数字。
KMP算法图文演示+详细解读_第23张图片
假如数字为4,则说明str2长度为j时有相同前后缀且长度为4;即①②③④处的字符是完全相等的,这样str2就可以将②和str1中的③对齐进行后面的匹配。
KMP算法图文演示+详细解读_第24张图片
此时继续判断i和j位置的字符
KMP算法图文演示+详细解读_第25张图片
现在已经清楚了KMP算法的原理,接下来就是代码实现了

next数组算法

vpublic static int[] Next(String str2) { //naxt数组生成代码
		int [] next = new int[str2.length()]; //创建等长的next数组
		next[0]=0;
		for(int i=1,j=0;i<str2.length();i++) { //遍历str2寻找共同前后缀
			while(j>0&&str2.charAt(i)!=str2.charAt(j)) {
				j=next[j-1];                //这里就是KMP算法最骚的操作了 ,如果这步不太懂可参考下图
			}
			if(str2.charAt(i)==str2.charAt(j)) {
				j++;
			}
			next[i]=j;
		}
		return next;
	}

KMP算法图文演示+详细解读_第26张图片

根据next表进行的匹配算法:

public static int kmp(String str1,String str2,int[] next) {
		for(int i = 0,j=0;i<str1.length();i++) {
			
			while(j>0&&str1.charAt(i)!=str2.charAt(j)) {//这里可结合上面图片匹配过程进行理解
				j=next[j-1];
			}
			if(str1.charAt(i)==str2.charAt(j)) {
				j++;
			}
			if(j == str2.length()) {
				return i-j+1;
			}
		}
		return -1;
	}

你可能感兴趣的:(算法)