数据结构上老师也没讲这个,平常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. gulp学习笔记1

    gulp系列学习笔记: 1.gulp学习笔记1 2.gulp学习笔记2 3.gulp学习笔记3 4.gulp学习笔记4 1.安装gulp 首先我们需要node环境,nodejs安装这里就不说了,不懂的 ...

  2. bzoj 3399: [Usaco2009 Mar]Sand Castle城堡

    3399: [Usaco2009 Mar]Sand Castle城堡 Time Limit: 3 Sec  Memory Limit: 128 MB Description 约翰用沙子建了一座城堡.正 ...

  3. python多线程生成缩略图

    在img目录下7张图片 分别是 11.jpg 22.jpg 33.jpg 44.jpg 55.jpg 66.jpg 77.jpg #encoding=utf-8 import os import ti ...

  4. Libjingle库简介

    原文链接 国内现在很多语音聊天工具都是基于TURN方式实现的,包括YY.AK等等,这种方式对于服务器的性能要求很高,而且在用户量增大的时候,服务器压力也会越来越大,用户的语音质量也会受到很大影响.而基 ...

  5. InnoSetup能够实现“安装细节描述”界面吗?

    QUOTE( Example_Test.iss ) // 脚本使用了 增强版脚本编辑器 build 091218:Beta2// 编译器版本为 5.3.6.ee1 [Setup]AppName=My ...

  6. RMAN duplciate 准备时,需要检查的target数据库参数内容

    SQL> show parameter audit_file_dest; NAME                                 TYPE        VALUE------ ...

  7. 用于主题检测的临时日志(b2d5c7b3-e3f6-4b0f-bfa4-a08e923eda9b - 3bfe001a-32de-4114-a6b4-4005b770f6d7)

    这是一个未删除的临时日志.请手动删除它.(1c773d57-4f35-40cf-ad62-bd757d5fcfae - 3bfe001a-32de-4114-a6b4-4005b770f6d7)

  8. netfilter分析

    转自:http://blog.sina.com.cn/s/blog_a31ff26901013n07.html 一.概述 1. Netfilter/IPTables框架简介 Netfilter/IPT ...

  9. java目录

    1. 在jsp文件或Servlet中,可以通过getServletContext().getRealPath("/")来获取项目根目录的绝对路径. 2. Java桌面程序中,可以通 ...

  10. 进入做Mvc项目的时候 返现某个文件夹下面css js png等静态文件都访问不了

    原来是我在该文件夹下面添加了一个web.config 里面 静止了所有的文件 直接访问 <system.web>    <httpHandlers>      <add ...