数据结构上老师也没讲这个,平常ACM比赛时我也没怎么理解,只是背会了代码……前天在博客园上看见了一篇介绍KMP的,不经意间就勾起了我的回忆,写下来吧,记得更牢。

一.理论准备

        KMP算法为什么比传统的字符串匹配算法快?KMP算法是通过分析模式串,预先计算每个位置发生不匹配的时候,可以省去重新匹配的的字符个数。整理出来发到一个next数组, 然后进行比较,这样可以避免字串的回溯,模式串中部分结果还可以复用,减少了循环次数,提高匹配效率。通俗的说就是KMP算法主要利用模式串某些字符与模式串开头位置的字符一样避免这些位置的重复比较的。例如 主串: abcabcabcabed ,模式串:abcabed。当比较到模式串'e'字符时不同的时候完全没有必要从模式串开始位置开始比较直接从模式串的'c'字符开始比较就可以了。并且主串也不用回溯了。

        传统的匹配算法没有利用匹配过的信息(模式串是知道的,那么部分匹配主串也是知道的),每次都从头开始比较,速度很慢。

        先介绍前缀数组(我自己这么叫的,不知道对不对)是如何产生的。首先,要了解两个概念:"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。

        来看一个例子:chi表示模式串的前i个字符组成的前缀, next[i] = j表示chi中的开始j个字符和末尾j个字符是一样的(注意下标是字符数目),而且对于前缀chi来说,这样的j是最大值。next[i] = j的另外一个定义是:有一个含有j个字符的串,它既是chi的真前缀,又是chi的真后缀。
         规定:next[1] = next[0] = 0,这个规定不像0!=1那样,而是确实是这样子,不懂得看上面的前后缀概念。注意:next数组里并不是首尾回文串,而是前缀等于后缀,理解这个对于递推求next数组很重要哟。next[i]就是前缀数组,下面通过1个例子来看如何构造前缀数组。
         例:cacca有5个前缀,求出其对应的next数组。前缀2为ca,显然首尾没有相同的字符,next[2] = 0,前缀3为cac,显然首尾有共同的字符c,故next[3] = 1,前缀4为cacc,首尾有共同的字符c,故next[4] = 1,前缀5为cacca,首尾有共同的字符ca,故next[5] = 2。如果仔细观察,可以发现构造next[i]的时候,可以利用next[i-1]的结果。比如abcdabc,模式已求得next[7] = 3,为求next[8],可以直接比较第4个字符和第8个字符,如果它们相等,则next[8] = next[7]+1 = 4,这是因为next[7] = 3保证了前缀ch7的末尾4个字符的前3个字符是一样的。但如果这两个字符不想等呢?那就继续迭代,利用(k=3)k = next[k]的值来求,直到k=0(next[8] = 0)或者字符相等(next[8] = k+1)。

        相信大家一定有所了解了……

        部分资料来自网络,作者佚名,原文有删改。

二.算法实现

import java.util.ArrayList;

public class KMP {

	//主串
	static String str = "1kk23789456789hahha";
	//模式串
	static String ch = "789";
	static int next[] = new int[20];

	public static void main(String[] args) {
		setNext();
		ArrayList<Integer> arr = getKmp();
		if(arr.size()!=0) {
			for(int i=0; i<arr.size(); i++) {
				System.out.println("匹配发生在:"+arr.get(i));
			}
		}else {
			System.out.println("匹配不成功");
		}
	}
	private static void setNext() {
		// TODO Auto-generated method stub
		int lenCh = ch.length();
		next[0] = 0;
		next[1] = 1;
		//k表示next[i-1]的值
		int k = 0;
		for(int i=2; i<=lenCh; i++) {
			k = next[k];
			/*
			 * 这个while循环的作用找个例子看看就好理解了
			 * 我认为是每次找最长,一旦成功就停止,保证找到的是当前最长
			 */
			while(k!=0 && ch.charAt(i-1)!=ch.charAt(k)) {
				k = next[k];
			}
			if(ch.charAt(i-1)==ch.charAt(k)) {
				k++;
			}//else就是k=0
			//不是next[k] = k,i表示有几个字符的前缀
			next[i] = k;
		}
	}
	private static ArrayList<Integer> getKmp() {
		// TODO Auto-generated method stub
		ArrayList<Integer> arr = new ArrayList<Integer>();
		int lenStr = str.length();
		int lenCh = ch.length();
		//主串开始的匹配位置
		int pos = 0;
		//模式串每次匹配位置
		int k = 0;
		//循环条件不是k<lenCh,这样的话可能死循环(没有匹配发生)
		while(pos<lenStr) {
			/*
			 * 首次进入没什么大作用,做要是为提高以后的匹配效率
			 * 写在最后一行也行
			 */
			k = next[k];
			while(k<lenCh && str.charAt(pos)==ch.charAt(k)) {
				pos++;
				k++;
			}
			if(lenCh==k) {
				arr.add(pos-k);
			}else if(0==k) {
				/*
				 * 不加这一句死循环
				 * 因为next[0] = 0
				 * 比如abcd和abce,到de不匹配,此时执行k = next[k](k=3),
				 * k变为0,发现d和a不匹配,此时k还是0,重复执行以上步骤,那么死循环了
				 */
				pos++;
			}//实际上else就是k = next[k],所以才说k = next[k]写在最后一行也行
		}
		return arr;
	}

}

三.问题扩展

        KMP算法的高效性往往是在模式串比较长的时候才能体现出来(看next数组的推导过程),而实际上模式串往往很短,回想自己使用办公套件时查找的字符串长度,所以实践上大多使用BM算法来实现,感兴趣的读者可以自己查阅相关资料,或许可以再看看多模匹配(在主串中一次查找多个模式串)的AC自动机、dictmatch算法。

初探KMP算法的更多相关文章

  1. KMP算法初探

    [edit by xingoo] kmp算法其实就是一种改进的字符串匹配算法.复杂度可以达到O(n+m),n是参考字符串长度,m是匹配字符串长度. 传统的算法,就是匹配字符串与参考字符串挨个比较,如果 ...

  2. 简单有效的kmp算法

    以前看过kmp算法,当时接触后总感觉好深奥啊,抱着数据结构的数啃了一中午,最终才大致看懂,后来提起kmp也只剩下“奥,它是做模式匹配的”这点干货.最近有空,翻出来算法导论看看,原来就是这么简单(先不说 ...

  3. KMP算法

    KMP算法是字符串模式匹配当中最经典的算法,原来大二学数据结构的有讲,但是当时只是记住了原理,但不知道代码实现,今天终于是完成了KMP的代码实现.原理KMP的原理其实很简单,给定一个字符串和一个模式串 ...

  4. 萌新笔记——用KMP算法与Trie字典树实现屏蔽敏感词(UTF-8编码)

    前几天写好了字典,又刚好重温了KMP算法,恰逢遇到朋友吐槽最近被和谐的词越来越多了,于是突发奇想,想要自己实现一下敏感词屏蔽. 基本敏感词的屏蔽说起来很简单,只要把字符串中的敏感词替换成"* ...

  5. KMP算法实现

    链接:http://blog.csdn.net/joylnwang/article/details/6778316 KMP算法是一种很经典的字符串匹配算法,链接中的讲解已经是很明确得了,自己按照其讲解 ...

  6. 数据结构与算法JavaScript (五) 串(经典KMP算法)

    KMP算法和BM算法 KMP是前缀匹配和BM后缀匹配的经典算法,看得出来前缀匹配和后缀匹配的区别就仅仅在于比较的顺序不同 前缀匹配是指:模式串和母串的比较从左到右,模式串的移动也是从 左到右 后缀匹配 ...

  7. 扩展KMP算法

    一 问题定义 给定母串S和子串T,定义n为母串S的长度,m为子串T的长度,suffix[i]为第i个字符开始的母串S的后缀子串,extend[i]为suffix[i]与字串T的最长公共前缀长度.求出所 ...

  8. 字符串模式匹配之KMP算法图解与 next 数组原理和实现方案

    之前说到,朴素的匹配,每趟比较,都要回溯主串的指针,费事.则 KMP 就是对朴素匹配的一种改进.正好复习一下. KMP 算法其改进思想在于: 每当一趟匹配过程中出现字符比较不相等时,不需要回溯主串的 ...

  9. 算法:KMP算法

    算法:KMP排序 算法分析 KMP算法是一种快速的模式匹配算法.KMP是三位大师:D.E.Knuth.J.H.Morris和V.R.Pratt同时发现的,所以取首字母组成KMP. 少部分图片来自孤~影 ...

随机推荐

  1. Java null String

    In Java, the String will have different usage. Example: public class Test { public static void main( ...

  2. android: open failed: EACCES (Permission denied)

    1.  问题描述:在Android中,用程序访问Sdcard时,有时出现“java.io.IOException: open failed: EACCES (Permission denied)&qu ...

  3. GO語言視頻教程

    第1课:https://github.com/Unknwon/go-fundamental-programming/blob/master/lectures/lecture1.md Go开发环境搭建h ...

  4. AngularJS中的http拦截

    $http服务允许我们与服务端交互,有时候我们希望在发出请求之前以及收到响应之后做些事情.即http拦截. $httpProvider包含了一个interceptors的数组. 我们这样创建一个int ...

  5. C#中将结构类型数据存储到二进制文件中方法

    以往在vb6,vc6中都有现成的方法将结构类型数据写入和读取到二进制文件中,但是在c#中却没有现成的方法来实现,因此我查阅了一些资料,借鉴了网上一些同学的做法,自己写了个类似的例子来读写结构类型数据到 ...

  6. Python一点注意

    1. pickle pickle模块中的两个主要函数是dump()和load().dump()函数接受一个文件句柄和一个数据对象作为参数,把数据对象以特定的格式保存到给定的文件中.当我们使用load( ...

  7. CentOS 6.5 EasyPR环境搭建

    EasyPR是一款开源的中文车牌识别系统,项目地址. 在搭建的过程中,主要的问题是注意版本的兼容性,这里面的版本包括:opencv版本,g++版本以及cmake版本. 我使用的EasyPr版本信息如下 ...

  8. 使用Eclipse进行远程调试【转】

    今天决定做件有意义的事,写篇图文并茂的blog,为什么要图文并茂?因为很多事可能用语言也说不明白,从以前我发表的一篇文章可以看得出来,http://blog.csdn.net/sunyujia/arc ...

  9. Spring源码追踪3——AOP机制

    研究代码: spring配置文件 <cache:annotation-driven /> Java代码 @Cacheable(value = "test", key = ...

  10. 使用git提交内容到网盘

    1.创建版本库(注意勾选纯版本库的选项) 2.客户端获取版本库代码 3.提交及获取 master是git默认的主要分支(主干),适合单人独自开发.多人开发时可以给每个人创建一个分支 参考资料: htt ...