图解KMP算法

KMP算法是最常见的算法之一,通常用来解决字符串的匹配问题,KMP算法本人认为是比较难理解的,我对于这个算法的学习是通过边画图边理清思路,然后在理解的基础上再进行代码的编写,我认为这个方法对于我来说能够更好的掌握这个算法,因此通过这篇博客进行分享,希望能对大家有所帮助!

KMP算法的介绍

KMP算法是一个解决模式串在文本串是否出现过,如果出现过,求出最早出现的位置的经典算法;KMP算法是在暴力匹配算法的基础上进行改进的,利用之前判断过信息,通过一个前缀值数组,保存模式串中前后最长公共子序列的长度,每次回溯时,通过前缀值数组找到前面匹配过的位置,省去了大量的计算时间

使用KMP算法的步骤:
  • 先求出子串对应的前缀值表
  • 根据前缀值表进行匹配,暴力匹配是只要遇到不相等的字符,就要大量的回溯到之前已经判断过的字符,而kmp算法是根据前缀值表进行回溯,可以大大减少回溯的次数

问题:

  1. 有一个字符串 str1= “abaacababcac”,和一个子串 str2=“ababc”
  2. 现在要判断 str1 是否含有 str2, 如果存在,就返回第一次出现的位置, 如果没有,则返回-1
  1. 求出子串ababc对应的前缀值表:(前缀值表就是”前缀”和”后缀”的最长的共有元素的长度)
    在这里插入图片描述
    通过以上的图解分析,可以得到前缀值表为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OjE5y2lY-1587222788612)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200418210216590.png)]

前缀值表的改变:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-paIJL5hD-1587222788614)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200418210315764.png)]
2. 根据前缀值表,进行字符串的匹配

图解分析:
在这里插入图片描述
3. 关于求子串对应的前缀值表的代码实现:

我个人认为通过代码实现来求出子串对应的前缀值表是kmp算法的难点所在,因此下面我将自己理解的求前缀值的思路详细的列举出来,希望能在这块知识点上帮助到大家!

思路:

  1. 首先要定义一个数组prefix,用来存放前缀值,该数组的第一位一定是0,因为只有一个字符的情况下,对应的前缀值是0,无需参与后面的遍历操作
  2. 接着定义一个变量i,用来指向子串的每一个字符,从下标为1开始遍历
  3. 定义一个变量len,用来表示定位到的字符的前一个字符的前缀值,同时代表着子串数组的下标
  4. 当遍历到的字符等于子串数组对应len下标的字符,那么将上一个字符的前缀值加一赋给当前字符的前缀值,并且i++
  5. 如果遍历到的字符不等于子串数组对应len下标的字符,那么需要将子串数组对应len下标的字符的前一个字符的前缀值赋给len;但是要注意的是这步操作的代码是len=prefix[len-1];如果len=0的话,会出现数组越界异常,因此需要加一个判断,如果len=0的话,直接将0赋给当前字符的前缀值,并且i++。
  1. 代码实现:
public class kmpAlgorithm {

	public static void main(String[] args) {	
		String str1="abaacababcac";
		String str2="ababc";
		int index = kmpSearch(str1, str2);
		System.out.println("index="+index);
	}

	/**
	 * 得到子串的前缀值表
	 * @param s
	 * @return
	 */
	public static int[] prefixTable(String s){
		
		char []p=s.toCharArray();//将子串转化成字符数组
		int[] prefix=new int[s.length()];//创建一个存放前缀值的数组
		
		prefix[0]=0;//只有一个字符的前缀值为0
		int i=1;//用来遍历字符数组,从第二个字符开始遍历,因为第一个前缀值已经确定为0了
		int len=0;//用来代表当前遍历字符的前一个字符的前缀值,同时代表着子串数组的下标
		while(i<p.length){
			if(p[i]==p[len]){
				len++;//前缀值加一
				prefix[i]=len;
				i++;
			}else{
				if(len>0){
					//为了防止数组越界,必须加上此判断语句
					len=prefix[len-1];
				}else{
					prefix[i]=0;
					i++;
				}
			}
		}
		return prefix;
	}
	
	/**
	 * 为了方便后面kmp算法代码的实现,将前缀值表整体后移一位,第一位赋值为-1
	 */
	public static int[] move(int[] a){
		for(int i=a.length-1;i>0;i--){
			a[i]=a[i-1];
		}
		a[0]=-1;
		return a;
	}
   /**
	 * kmp搜索
	 * @param str1
	 * @param str2
	 * @return
	 */
	public static int kmpSearch(String str1,String str2){
		if(str1==null||str2==null){
			return -1;
		}
		char[] ch1=str1.toCharArray();
		char[] ch2=str2.toCharArray();
		int [] prefix=move(prefixTable(str2));

		int length1=ch1.length;
		int length2=ch2.length;
		
		int i=0,j=0;
		
		while(i<length1&&j<length2){
			if(ch1[i]==ch2[j]){
				i++;
				j++;
			}else{
				j=prefix[j];
				if(j==-1){
					i++;
					j++;
				}
			}
		}
		if(j==length2){
			return i-j;
		}else{
			return -1;
		}
	}
}
  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值