你可能需要了解的KMP算法(Java语言)+Leetcode例题

KMP算法应该是经典算法中的经典算法了,背景我也不介绍了,当然背景我也不怎么了解。

那它解决的问题是什么呢?
在我粗浅的理解中,主要解决的是包含问题,如经典的字符串包含问题,如解决字符串中abccccc中是否包含abc这样一个问题。

好了,你也许会说,这还不简单吗,直接两边循环嵌套,暴力碾压过去。当然,这是可以做到的,这就是传统的BF算法。但是,毫无疑问,这不优雅美观。丑陋且恐怖的O(n*m)复杂度相信你不会喜欢的,有极客精神的程序员都努力避免这样高复杂度算法的产生。

KMP的优雅之处就是可以将时间复杂度压缩到O(n)

设基础问题为求str1是否包含str2

首先,我们需要有一个基础概念:我们可以求一个字符串中,一个字符之前的字符串,最长前缀和最长后缀的匹配长度(且前缀不能包含最后一个字符,后缀不能包含第一个字符)
这是什么意思呢,有点拗口,举个例子:
abaabac中,我们若要求c的最长前缀和最长后缀的匹配长度。
显然前缀a后缀a匹配,但不是最长。
前缀aba后缀aba匹配,是最长。所以匹配长度为3

根据此规则,我们可以将str2中每个字符计算匹配长度从而得出一个数组。就暂且称为next数组吧。

next数组怎么能快速高效的求出来呢?毕竟我们不可能每个字符位置都重新去计算一遍,这样舍本逐末还不如直接使用BF算法呢。

下图为求next数组的方法:
在这里插入图片描述
求第i位用的是数学归纳法,证明第i位的求法可以用反证法。

没理解的话可以看看这篇文章:http://www.ruanyifeng.com/blog/2013/05/Knuth–Morris–Pratt_algorithm.html

那有什么用呢?

继续往下看

我们接下来开始比对str1中是否包含str2,在传统的BF算法中,只是单纯的循环比对。在KMP算法中,我们来看看是如何加速这个过程的:
我们假设下图是比对字符串的某一过程,str1在i位置(前面或许还有已匹配失败的字符串段)开始与str2初始位置o匹配,直到X位置与Y位置不匹配(注意:X与Y位置后面或许还有剩余字符串)

由于我们已经知道了str2字符串任意位置的最长前缀与最长后缀(即上述的next数组),在X与Y匹配失败后,我们可以直接跳过到最长后缀对应的第一个位置:str中的J位置(因为前提是最长前缀最长后缀)。这是第一个加速过程。
又因为最长前缀
最长后缀,所以就有理所应当的第二个加速过程:假设Y位置的最长前缀长度为N,那我们可以直接从J+N位置继续匹配。
在这里插入图片描述

证明过程:即大致是围绕证明最长前缀和最长后缀的关系当前仅当相等且最长时,后续观点成立。(即否定跳过字符串中不可能包含str2等一系列操作)

LeetCode中也有比较直白的使用KMP算法的题,如第28题实现strStr()。

BF算法实现版:

class Solution {
    public int strStr(String haystack, String needle) {
		if (needle==null||haystack==null||needle=="") {
			return 0;
		}
		char[] haychar=haystack.toCharArray();
		char[] needchar=needle.toCharArray();
		int hayindex=0,tempindex;
		boolean finish=true;
		while (hayindex<=(haychar.length-needchar.length)) {
			tempindex=hayindex;
			for (int i = 0; i < needchar.length; i++) {
				if (needchar[i]!=haychar[tempindex++]) {
					finish=false;
					break;
				}
			}
			if (finish) {
				return hayindex;
			}else{
				finish=true;
				hayindex++;
			}
		}
		return -1;
	}
}

在这里插入图片描述

KMP实现版:

class Solution {
    public int strStr(String haystack, String needle) {
		if (needle==null||haystack==null||needle.length()<=0) {
			return 0;
		}
		char[] haychar = haystack.toCharArray();
		char[] needchar = needle.toCharArray();
		int hayindex = 0;
		int needindex = 0;
		int[] next = getNextArray(needchar);
		while (hayindex < haychar.length && needindex < needchar.length) {
			if (haychar[hayindex] == needchar[needindex]) {
				hayindex++;
				needindex++;
			} else if (next[needindex] == -1) {
				hayindex++;
			} else {
				needindex = next[needindex];
			}
		}
		return needindex == needchar.length ? hayindex - needindex : -1;
	}

	public static int[] getNextArray(char[] ms) {
		if (ms.length == 1) {
			return new int[] { -1 };
		}
		int[] next = new int[ms.length];
		next[0] = -1;
		next[1] = 0;
		int pos = 2;
		int cn = 0;
		while (pos < next.length) {
			if (ms[pos - 1] == ms[cn]) {
				next[pos++] = ++cn;
			} else if (cn > 0) {
				cn = next[cn];
			} else {
				next[pos++] = 0;
			}
		}
		return next;
	}
}

在这里插入图片描述

可以看到KMP算法减少了运行时间(当然,OJ上的运行时间其实很容易产生较大波动,说服力不够)

若是用在超长字符串上,就可以感受到两种算法的明显差距了。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
KMP算法是一种字符串匹配算法,用于在一个文本串S内查找一个模式串P的出现位置。它的时间复杂度为O(n+m),其中n为文本串的长度,m为模式串的长度。 KMP算法的核心思想是利用已知信息来避免不必要的字符比较。具体来说,它维护一个next数组,其中next[i]表示当第i个字符匹配失败时,下一次匹配应该从模式串的第next[i]个字符开始。 我们可以通过一个简单的例子来理解KMP算法的思想。假设文本串为S="ababababca",模式串为P="abababca",我们想要在S中查找P的出现位置。 首先,我们可以将P的每个前缀和后缀进行比较,得到next数组: | i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | --- | - | - | - | - | - | - | - | - | | P | a | b | a | b | a | b | c | a | | next| 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 | 接下来,我们从S的第一个字符开始匹配P。当S的第七个字符和P的第七个字符匹配失败时,我们可以利用next[6]=4,将P向右移动4个字符,使得P的第五个字符与S的第七个字符对齐。此时,我们可以发现P的前五个字符和S的前五个字符已经匹配成功了。因此,我们可以继续从S的第六个字符开始匹配P。 当S的第十个字符和P的第八个字符匹配失败时,我们可以利用next[7]=1,将P向右移动一个字符,使得P的第一个字符和S的第十个字符对齐。此时,我们可以发现P的前一个字符和S的第十个字符已经匹配成功了。因此,我们可以继续从S的第十一个字符开始匹配P。 最终,我们可以发现P出现在S的第二个位置。 下面是KMP算法的C++代码实现:

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值