`
mabusyao
  • 浏览: 247298 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

字符串搜索算法总结

阅读更多
因为在网上搜寻hash算法的知识,无意中又找到一些字符串搜索算法。 由于之前已经学习过一些搜索算法,觉得应该可以归为一类。因此就写一篇文章来记录下学习的过程。

问题:

在一长字符串中找出其是否包含某子字符串。

首先当然还是简单算法,通过遍历来检索所有的可能:
	public static int naiveSearch(String content, String sub) {
		for(int i = 0; i < (content.length() - sub.length() + 1); i++) {
			boolean found = true;
			for(int j = 0 ; j < sub.length(); j++) {
				if(content.charAt(i + j) != sub.charAt(j)) {
					found = false;
					break;
				}
			}
			
			if(found) return i;
		}
		
		return -1;
	}

时间复杂度为 Θ((n-m+1) m)

Rabin–Karp,即hash检索法:
	public static int rabinKarp(String content, String sub) {
		long hcontent = rshash(content.substring(0, sub.length()));
		long hsub = rshash(sub);
		
		for(int i = 0; i < (content.length() - sub.length()); i++) {
			//hcontent = rshash(content.substring(i, sub.length() + i));

			if(hsub == hcontent) {
				if(sub.equals(content.substring(i, i + sub.length()))) {
					return i;
				}
			}
			
			hcontent = newhash(content, hcontent, i + 1, sub.length());
		}
		
		return -1;
	}
	
	private static long rshash(String str)  
	{
	   int a = 63689;
	   long hash = 0;
	   
	   for(int i = 0; i < str.length(); i++)
	   {
	      hash += a * str.charAt(i);
	   } 
	   return hash;
	}
	
	private static long newhash(String str, long previous, int i, int length)  
	{
	   int a = 63689;
	   
	   long minHash = str.charAt(i - 1) * a;
	   
	   long plusHash = str.charAt(i + length - 1) * a;
	   
	   return (previous - minHash + plusHash);
	}

这个算法的核心思想是,通过hash值,我们可以一次匹配一整条字串,速度上要快很多。
关键: 选择这样一种hash算法,使得从前一个hash值到后一个hash值仅需要常量的步骤。

我这里实现的hash算法可以做到这点,但是有效性并不高,应该还有其他的hash算法可以更好了减少冲突的发生。

KMP算法
KMP算法说简单也不简单,说复杂也不复杂。只要你理解了它的核心思想,代码量其实非常少。可是想要解释它的思想,却也不是一件容易的事情。

考虑再三,还是觉得自己无法胜任这个解释工作,于是找了一篇自己认为解释KMP算法比较透彻的文章,翻译出来,看看大家有没有更哈的建议。

KMP algorithm
http://en.wikipedia.org/wiki/Knuth-Morris-Pratt_algorithm

实例
为了解释该算法的细节,我们首先利用一个实例来把算法的步骤过一遍。在这个过程中的任意时间点,该算法的状态都由两个变量来决定, m和i。 m代表在S中,某个对于W(pattern)匹配的起始位置。 i代表在W中的当前正在进行匹配工作的位置。我们来描述一下当算法开始时的状态:
             1         2 
m: 01234567890123456789012
S: ABC ABCDAB ABCDABCDABDE
W: ABCDABD
i: 0123456

我们首先从0位开始匹配W和S中平行的字符串,如果匹配,则前进到下一位。然而当我们到第四步的时候,我们发现S[3]是空格而W[3]=‘D’,出现了第一个不匹配。在传统的模式匹配算法中,接下来我们应该从S[1]的位置重新开始匹配。然而,如果我们仔细观察,在S的0到3位中(也就是我们刚刚进行过匹配成功的位),除了0位,其它都没有‘A‘出现过。而假设某字符串中有和W匹配的子串,那么这个子串必须是以’A‘开头的。因此,我们可以确定,S的0到3位中,都不可能存在这样的子串,于是我们决定从S的4位中重新找起。也就是说,m=4, i=0.

             1         2 
m: 01234567890123456789012
S: ABC ABCDAB ABCDABCDABDE
W:     ABCDABD
i:     0123456


此时,我们获得了一个几乎匹配的“ABCDAB”,然后在W[6](S[10])这个位置上,我们又出现了一个不匹配。于是,我们应该继续扩大m的值去寻找下一个可能的匹配。那么m的下一个值应该设置为多少呢?

整个算法的核心就在于此,我们可以从不匹配的位置开始,即m=10,然后这并不是一个正确的选择,我们发先在S的4到10位中,第8位也是‘A’。因此,如果我们从第十位开始继续找起的话,有可能就错过了某个匹配。

S中第八位的‘A’和第九位的‘B’,分别跟W的第0第1位相匹配。KMP算法对此的处理是,新的m=8(即首位匹配的值),新的i=2(因为前两位根据统计,已经是匹配好的了)。然后我们从S的m+i开始匹配W的i位。


             1         2 
m: 01234567890123456789012
S: ABC ABCDAB ABCDABCDABDE
W:         ABCDABD
i:         0123456

跟第一步类似,我们在i=2就出错了,下一步我们应该跳转到哪里呢?当然是m=11,i=0:

             1         2 
m: 01234567890123456789012
S: ABC ABCDAB ABCDABCDABDE
W:            ABCDABD
i:            0123456

此时,我们又在m=17的位置上出错了,根据第二步的解释,我们这次跳到m=15而i=2:

             1         2 
m: 01234567890123456789012
S: ABC ABCDAB ABCDABCDABDE
W:                ABCDABD
i:                0123456

找到该模式,算法结束,返回m的值15.

部分匹配表
如果我们刚才的分析那样,整个匹配算法的核心就在于,当某次匹配过程出现不匹配的值时,如何寻找下一个做匹配的位置(这里的位置包括两个概念,即起始位置和我们应该从哪个值开始做匹配)。这样的值当然是越大越好,因为选择的下一个匹配位置越大,我们跳过的值就越多,整个算法就越快。

如果单看我们之前的分析,好像这个确定下一个匹配位置的工作关系到S和W两张表。其实不是这样的,我们只需要对W进行一个预处理,就可以做到这点

还是来看,当W是“ABCDABD”这样一个字串时。我们会发现除了起始位置是‘A’以外,4位的值也是‘A’,那么如果我们在某次匹配时,匹配到了W的第4位,那么下一次做匹配查找时,就应当从W第4位对应的那个字符开始:
m     123456
S ... ABCDAX...
W     ABCDABD
i     0123456
因为我们已经知道S[5]和W[4]是匹配的了,那么其实就不需要再匹配一次了。因此匹配的起始位置是m=5,但是应当从i=1那里开始进行匹配。

如果是这样的一个情况呢?
m     1234567
S ... ABCDABX..
W     ABCDABD
i     0123456
同样的道理,匹配的起始位置依然是m=5,但是应当从i=2开始匹配。

下面给出求出当从任一位出现不匹配是,应该从哪里开始从新匹配的算法:
	private static void next(char[] input, int[] table) {
		int pos = 2;
		int cnd = 0;
		
		table[0] = -1;
		table[1] = 0;
		
		while(pos < input.length) {
			if(input[pos - 1] == input[cnd]) {
				table[pos] = cnd + 1;
				pos++;
				cnd++;
			} else if(cnd > 0) {
				cnd = table[cnd];
			} else {
				table[pos] = 0;
				pos++;
			}
		}
	}



既然已经得到了这样一个表,那么写出整个KMP算法也不是什么难事了:

	private static void next(char[] input, int[] table) {
		int pos = 2;
		int cnd = 0;
		
		table[0] = -1;
		table[1] = 0;
		
		while(pos < input.length) {
			if(input[pos - 1] == input[cnd]) {
				table[pos] = cnd + 1;
				pos++;
				cnd++;
			} else if(cnd > 0) {
				cnd = table[cnd];
			} else {
				table[pos] = 0;
				pos++;
			}
		}
	}


最后两个算法分别是BM算法和有限自动机算法。昨天我花了一天的时间研究BM算法,对算法的本质有了一定的了解,但是对于如何编码还是有点困惑。

决定把这两个算法先放一下,在字符搜索算法上停留的时间有点长。还是等以后再继续学习。
分享到:
评论

相关推荐

    c语言数据结构字符串模式匹配算法.zip

    KMP字符串模式匹配通俗点说就是一种在一个字符串中定位另一个串的高效算法。简单匹配算法的时间复杂度为O(m*n);KMP匹配算法。可以证明它的时间复杂度为O(m+n).。 一.简单匹配算法 先来看一个简单匹配算法的函数: ...

    Python中的字符串查找操作方法总结

    Python 查找字符串使用 变量.find(“要查找的内容”[,开始位置,结束位置]),开始位置和结束位置,表示要查找的范围,为空则表示查找所有。查找到后会返回位置,位置从0开始算,如果每找到则返回-1。 str = 'a,...

    经典数据结构和算法总结

    包含了各种经典的数据结构和算法的总结(描述 + C++代码),例如:链表,字符串,二叉树,哈夫曼树,图,查找,排序等。

    从字符串中找出每个字符出现的次数java代码

    从字符串中找出每个字符出项的次数java代码,这是总结了前人的很多方法自己总结的,很容易懂,算法也比较巧妙,和大家分享下

    算法-第4版-完整版

    5.3.3 Knuth-Morris-Pratt子字符串查找算法 496 5.3.4 Boyer-Moore字符串查找算法 502 5.3.5 Rabin-Karp指纹字符串查找算法 505 5.3.6 总结 509 5.4 正则表达式 514 5.4.1 使用正则表达式描述模式 ...

    算法 第4版 高清中文版

    5.3.3 Knuth-Morris-Pratt子字符串查找算法 496 5.3.4 Boyer-Moore字符串查找算法 502 5.3.5 Rabin-Karp指纹字符串查找算法 505 5.3.6 总结 509 5.4 正则表达式 514 5.4.1 使用正则表达式描述模式 514 5.4.2 ...

    《算法》中文版,Robert Sedgewick,塞奇威克

    5.3.3 Knuth-Morris-Pratt子字符串查找算法 5.3.4 Boyer-Moore字符串查找算法 5.3.5 Rabin-Karp指纹字符串查找算法 5.3.6 总结 5.4 正则表达式 5.4.1 使用正则表达式描述模式 5.4.2 缩略写法 5.4.3 正则...

    算法 第4版-谢路云译-带完整书签

    5.3.3 Knuth-Morris-Pratt子字符串查找算法 496 5.3.4 Boyer-Moore字符串查找算法 502 5.3.5 Rabin-Karp指纹字符串查找算法 505 5.3.6 总结 509 5.4 正则表达式 514 5.4.1 使用正则表达式描述模式 514 ...

    算法,4th,塞奇威克 (Robert Sedgewick)韦恩 (Kevin Wayne), 谢路云 译.azw3

    5.3.3 Knuth—Morris—Pratt子字符串查找算法 5.3.4 Boyer—Moore字符串查找算法 5.3.5 Rabin—Karp指纹字符串查找算法 5.3.6 总结 5.4 正则表达式 5.4.1 使用正则表达式描述模式 5.4.2 缩略写法 5.4.3 正则...

    算法文档,来看看吧

    [原网页] 程序员编程艺术第三十~三十一章:字符串转换成整数,通配符字符串匹配 [原网页] 程序员编程艺术第二十八~二十九章:最大连续乘积子串、字符串编辑距离 [原网页] 数据挖掘中所需的概率论与数理统计知识、...

    静态查找法实现管道铺设中的最小生成树问

    good)——图 15. 利用深度或广度优先搜索求图的近似最小生成树 16. **(选做)利用VB实现栈或队列的基本操作(如:初始化、入栈出栈、入队出队等) 17.线性表不同存储结构...22.哈希表在字符串操作中的应用(字符串

    常用算法代码

    | 最短公共祖先(多个短字符串) 33 Geometry 计算几何 34 | GRAHAM 求凸包 O(N * LOGN) 34 | 判断线段相交 34 | 求多边形重心 34 | 三角形几个重要的点 34 | 平面最近点对 O(N * LOGN) 34 | LIUCTIC 的...

    找工作面试算法笔记

    个人找工作期间总结的相关面试...有清晰的归类,包括:排序、链表、字符串、队列和栈、二叉树、二叉搜索树等。【注】:个人总结、每个算法都是本人编写、编译、调试通过的。找完工作,贡献出来,以便帮助更多小伙伴!

    leetcode中文版-leetcode:算法笔记总结。包括《剑指offer》《程序员笔试面试指南》《Leetcode》相关题目

    字符串系列 链表系列 二叉树系列 排序系列 动态规划系列 设计系列 数学系列 其它系列 算法题型分类总结 1.冒泡排序: 原地排序,稳定排序(相邻元素大小相等时不交换),最好O(N),最坏O(N^2),平均O(N^2) 2.插入排序...

    小甲鱼_数据结构与算法(98集全)

    四36_字符串.mp4 二37_ KMP算法. mp4 四71斐波那契查找(黄金分割法查找).38_ KMP算法2. mp4 立39_ KMP算法之NEXT数组代码原理分析. mp4二40_ KMP算法之实现及优化. mp4二41树. mp4 四42_树的存储结构. mp443_...

    LeetCode解题总结

    13.3 字符串的所有子回文字符串 13.4 最长公共子序列问题 13.5 字符串的编辑距离 13.6 不同路径之和 13.6.1 无障碍13.6.2 有障碍 13.7 最大矩形面积 13.8 字符串交叉组合 13.9 旋转字符串 13.10 最小路径和 13.11 ...

    ACM算法模板和pku代码

    字符串hash 堆 二维树状数组 Trie树 二叉查找树 线段树 RMQ LCA+RMQ SB-Tree 数论 生成紧凑素数表 分解质因子 最大公约数 a^b mod n 扩张欧几里德算法 素数表质因子分解 Stirling公式 中国剩余定理...

    算法实验-串匹配问题-采用分治法求解最大连续子序列和问题-用分治策略求众数问题-最近点对问题

    给定一段文本,在该文本中查找并定位任意给定字符串。 要求: (1)实现BF算法; (2) 实现BF算法的改进算法:KMP算法 2.采用分治法求解最大连续子序列和问题 给定一个有n(n≥1)个整数的序列,要求求出其中...

    计算机算法与程序设计(python)-计算机PPT模板.pptx

    第八章二叉搜索树 01 8.1二叉搜索树的定义与查找 02 8.2二叉搜索树的实现 03 8.3有序数组构造二叉搜索树 04 8.4二叉搜索树的区间查找 05 8.5二叉搜索树的插入 06 8.6trie树 计算机算法与程序设计(python)-计算机PPT...

Global site tag (gtag.js) - Google Analytics