通俗理解KMP算法

前言

     因为最近在刷剑指,看到有大神用自动机,然后就研究了下,进而看了看KMP算法,但是很多资料感觉太啰嗦,看起来着实有点费劲,所以,结合自己的理解,想写一篇简易通俗的KMP。

    

☆☆☆ 算法目录导航页,包含基础算法、高级算法、机器学习算法等☆☆☆

    

1.KMP的定义

    KMP其实就是一种改进的字符串匹配(查找)算法,为什么叫KMP呢,是因为其发现者Knuth-Morris-Pratt 这3位大神的名字第一个字母是KMP,并没其他特殊含义,所以关于定义请死记硬背吧O(∩_∩)O~。
     KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)。

2.算法过程

   其实上面的解释对于初学者来说不好理解,那我们举个例子:
   假设已有字符串称为a串, BBC ABCDAB ABCDABCDABDE,求是否含有子串称之为b串ABCDABD。
     ① 开始匹配

在这里插入图片描述

     我们发现a串前4个字符都跟b匹配不上,所以一步一步来到了第5个字符a[4] = A处,此时匹配成功就一直匹配,直到a[10]处匹配失败:

在这里插入图片描述

     接下来,我们看下暴力求解和KMP如何进行匹配:
     ①暴力求解:b串向后移动一步进行匹配,即匹配失败时,b串只能一步一步的往后移动。时间复杂度为O(m * n)。

在这里插入图片描述

     ② KMP求解:

在这里插入图片描述

     ③ 分析:暴力求解是不是傻,正常人眼一看,匹配失败时,肯定是要跳到a[8]处再进行匹配的,这样效率肯定高啊。所以,KMP其实就是正常人的一种做法,前面的信息已经比较过了,是有记忆依赖的,而不是相互独立的。难道正常人前一秒看见的东西下一秒就会忘了?那是真的傻。这个地方的思路跟动态规划和lstm等算法还是有异曲同工之妙的。
     ④ 总结:所以,KMP就是让我们的算法有记忆功能,提高算法效率。这种类型算法的实现无非就是空间换时间。所以,我们需要建立一张记忆表,用于查询之前的记忆内容,KMP中叫next数组,它是由b串得来,存储的是b串的前缀后缀中最长的公共字串的长度再整体往后移动一位的值,下面会举例说明。

3.b串移动公式

     公式就意味着是一种规律,前人总结,后人即用即可。
     只要移动b串,说明已经失配。
     移动公式:移动位数 = 已匹配字符数 - 失配字符的上一位字符所对应的最大长度值

4.最大长度表求解过程

     最大长度指的是前缀后缀中最长公共字串的长度。举例说明:

在这里插入图片描述

5.b串移动公式 换种形式

     b串位移公式变为:移动位数 = 失配字符所在位置 - 失配字符对应的next 值

6.next表求解过程

     为了编程好实现,又定义了next表,其实就是最大长度表整体右移一位:

在这里插入图片描述

Java实现 next表
/**
	 * 
	 * @param b_str  需要查找或匹配的字符串
	 * @param next   由最长的相同前缀后缀的子串长度表 而 构建的next表
	 * 
	 * 	 0  0  0  0  1  2  0      前缀后缀最长子串长度表
	 *  -1, 0, 0, 0, 0, 1, 2   getNext得到(前面长度表右移1)
	 * 	 
	 */
	static void getNext(char [] b_str,int [] next)
	{
		int bLen = b_str.length;
		next[0] = -1;//因为最大长度表右移一位后,第一位补位 -1
		int i = -1;
		int j = 0; //b串起始索引
		while (j < bLen - 1)//遍历b串
		{
			//b_str[i]表示前缀的第一个字符,b_str[j]表示后缀的第一个字符
			if (i == -1 || b_str[i] == b_str[j]) 
			{
				i++;
				j++;
				next[j] = i;//当前比较字符,即后缀的第一个字符更新其next值
			}
			else 
			{
				i = next[i];
			}
		}
	}

7.根据移动公式求b串是否为a的子串

Java 完整实现
/**
 * kmp
 * 
 * 字符串匹配查找算法
 * 
 * @author wxq
 *
 */
public class KMP {
	
	public static void main(String[] args) {
		String a_str = "BBC ABCDAB ABCDABDE";
		String b_str = "ABCDABD";
		int [] next = new int [b_str.length()];
		getNext(b_str.toCharArray(),next);
		System.out.println(KmpSearch(a_str.toCharArray(),b_str.toCharArray(), next));
//		System.out.println(Arrays.toString(next));
	}
	
	/**
	 * kmp搜索匹配字符串
	 * @param a_str
	 * @param b_str
	 * @param next
	 * @return
	 */
	static int KmpSearch(char [] a_str, char [] b_str,int [] next)
	{
		int i = 0;
		int j = 0;
		int aLen = a_str.length;
		int bLen = b_str.length;
		while (i < aLen && j < bLen)
		{
			//①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++    
			if (j == -1 || a_str[i] == b_str[j])
			{
				i++;
				j++;
			}
			else
			{
				//②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]    
				//next[j]即为j所对应的next值      
				j = next[j];
			}
		}
		if (j == bLen)
			return i - j;
		else
			return -1;
	}
	
	/**
	 * 
	 * @param b_str  需要查找或匹配的字符串
	 * @param next   由最长的相同前缀后缀的子串长度表 而 构建的next表
	 * 
	 * 	 0  0  0  0  1  2  0      前缀后缀最长子串长度表
	 *  -1, 0, 0, 0, 0, 1, 2   getNext得到(前面长度表右移1)
	 * 	 
	 */
	static void getNext(char [] b_str,int [] next)
	{
		int bLen = b_str.length;
		next[0] = -1;//因为最大长度表右移一位后,第一位补位 -1
		int i = -1;
		int j = 0; //b串起始索引
		while (j < bLen - 1)//遍历b串
		{
			//b_str[i]表示前缀的第一个字符,b_str[j]表示后缀的第一个字符
			if (i == -1 || b_str[i] == b_str[j]) 
			{
				i++;
				j++;
				next[j] = i;//当前比较字符,即后缀的第一个字符更新其next值
			}
			else 
			{
				i = next[i];
			}
		}
	}

百度百科
参考 — 1
参考 — 2
参考 — 3

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值