数据结构上老师也没讲这个,平常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基类方法wait、notify、notifyAll

    wait(),notify()和notifyAll()都是Java基类java.lang.Object的方法. 通俗解释wait():在当前线程等待其它线程唤醒.notify(): 唤醒一个线程正在等 ...

  2. WindowsPhone-GameBoy模拟器开发五--使用XNA初略实现Gameboy显示系统

    开篇前,最近弄了个空间,大家不嫌弃的话可以上去讨论讨论J http://www.lihengzhe.cn 这一次,就来简单地实现gameboy的实现机制.先说一下本次内容涉及到的技术,其实也就一项—X ...

  3. MongoDB副本集配置系列十:MongoDB local库详解和数据同步原理

    1:local库是MongoDB的系统库,记录着时间戳和索引和复制集等信息 gechongrepl:PRIMARY> use local switched to db local gechong ...

  4. IntelliJ IDEA + Maven创建Java Web项目

    1. Maven简介 相对于传统的项目,Maven 下管理和构建的项目真的非常好用和简单,所以这里也强调下,尽量使用此类工具进行项目构建, 它可以管理项目的整个生命周期. 可以通过其命令做所有相关的工 ...

  5. Delphi中设置条件断点

    写了这么长时间的代码,一直认为调试程序比写程序要重要,上次有人问俺,如何调试一个循环中某个循环条件位置下断点.本来想来在Delphi的断点设置中应该是有一个类似条件断点的东西的,不过我也一直不知道怎么 ...

  6. eclipse连接远程hadoop集群开发时权限不足问题解决方案

    转自:http://blog.csdn.net/shan9liang/article/details/9734693 eclipse连接远程hadoop集群开发时报错   Exception in t ...

  7. [CoreOS 转载] CoreOS实践指南(四):集群的指挥所Fleet

    转载:http://www.csdn.net/article/2015-01-14/2823554/2 摘要:CoreOS是采用了高度精简的系统内核及外围定制的操作系统.ThoughtWorks的软件 ...

  8. Updating Protobuf and GRPC in Golang

    转自: http://russmatney.com/techsposure/update-protobuf-golang-grpc/ TL;DR: When protobuf updates, all ...

  9. 【转】APP被苹果App Store拒绝的N个原因(持续补充)

    作为iOS开发者,估计有很多都遇到过APP提交到App Store被拒,然后这些被拒的原因多种多样,今天dApps收集了常见的被拒的原因,以便更多开发者了解. 1.程序有重大bug,程序不能启动,或者 ...

  10. 360随身WiFi驱动下载

    一场不算太好的体验,但还是解决问题了 360随身WiFi驱动下载地址 事情经过: 某天在家里组装起PC,才发现当时没有在这屋里预留网线接口,走明线穿堂过户肯定是不合适的,还是买个无线网卡吧 自然还是要 ...