- 浏览: 77590 次
- 性别:
- 来自: 上海
最新评论
-
rockythd:
视界这个概念终于搞清楚了,谢谢!
Java关于Scala的“视界(view bound)”的模拟 -
regular:
写了一个更通用的方法:ObjectUtils.cast。目的是 ...
Java关于Scala的“视界(view bound)”的模拟 -
lrztiancai:
谢谢分享。正在找这个!
Parsley+SpiceLib 2.4 Developer Manual -
kraft:
第二版什么时候出啊
Programming In Scala 翻译 -
justjavac:
xpf7622 写道haixu.huang@live @前的名 ...
Programming In Scala 翻译
从一个很长的字符串(或者数组)中,查找某个子串(模式串)是否存在,在算法上被称为是“模式匹配”。
模式匹配的经典算法包括KMP算法、BM算法等等。以下简要回顾这些经典算法的思想,并说明我对此的改进想法。
首先对模式串进行处理,获得当某个字符位置失配的时候,比较位置的指针应该指向的下一个位置的数组。举例如下:
经过对上面的模式串进行处理,我们可以做如下比较:
KMP的比较位数组计算方式遵循下列公式:
更详细的说明参见:
KMP算法详解
首先建立两个表:
坏字符表,包含所有可能出现的字符,0x00..0xFF,说明如果尾部字符是这个的话,模式串为了能匹配这个位置的这个字符,右移的最短距离。
好后缀表,说明如果模式串中若有与某个长度的后缀一致的子串,则可以向右移动的距离。
更详细的说明参见:
字符串匹配算法 boyer-moore算法
Boyer-Moore 经典单模式匹配算法
首先,KMP算法可能会比较一些显然不可能匹配的情况:
实际上,我们可以看到,上面例子里的第2,第3次比较根本没有必要。根本就可以直接跳到第4步继续下去。问题出现在什么地方?问题就出现在next的公式中。
这个公式只说明了自匹配的情况,没说明在自匹配的基础上还应该有一个尾字符的不匹配。
也就是说,若两个子串的下一个字符也一样,就没必要再匹配,因为只有源串里面比较的那个字符不一样才需要移动。
从Boyer-Moore Algorithm之中学习到,改良的BM算法中的好后缀表的建立也包含了同样的思想,而且更玄的是,好后缀表竟然能考虑到模式串的边界之外隐含的匹配信息,的确很强大。
另外,向BM算法致敬,若尾字符根本没出现在模式串中的话,所有的比较都根本无意义。所以这种情况下,相比上面调整的KMP算法的小挪移,可以实现大挪移。
修改之后的算法逻辑如下:
实际上,我在代码里创建的是跳转表,而不是指针索引位表。
步进值的计算方法如下:
最后,Java代码如下:
总体看来,还是BM算法更好一些,其效率比KMP算法更高。
我这里虽然借用了BM的坏字符思想并改进了KMP的步进值计算,但由于逐字符比较始终是从头开始,而不像BM算法是从尾部开始,所以小挪移的潜力没有BM的那么大。
若模式串相对较长的话,可以在模式串中找几个稀疏分布的点,比较的时候,首先比较这几个点的字符是否与源串相同,如果不同就没必要逐个字符比较了,肯定不一致,可以往后挪了。
模式匹配的经典算法包括KMP算法、BM算法等等。以下简要回顾这些经典算法的思想,并说明我对此的改进想法。
KMP算法
首先对模式串进行处理,获得当某个字符位置失配的时候,比较位置的指针应该指向的下一个位置的数组。举例如下:
模式串:A B A B C next :0 0 1 2 0
经过对上面的模式串进行处理,我们可以做如下比较:
索引位:_ _ ▼ ↓ 字符串:A B A A B A B D C A B A B A B C 模式串:A B A B C 比较位:_ _ ▲ ↑ 失配,指针指向 next[3] = 1 的位置 索引位:_ _ ▼ ↓ 字符串:A B A A B A B D C A B A B A B C 模式串:_ _ A B A B C 比较位: ▲ ↑ 仍然失配,next[1] = 0 的位置 索引位:_ _ ▼ ↓ 字符串:A B A A B A B D C A B A B A B C 模式串:_ _ _ A B A B C 比较位: ▲ ↑ OK了,继续向下走。 索引位:_ _ _ _ _ _ ▼ ↓ 字符串:A B A A B A B D C A B A B A B C 模式串:_ _ _ A B A B C 比较位: _ _ _ ▲ ↑ 失配,next[4] = 2 的位置。 索引位:_ _ _ _ _ _ ▼ ↓ 字符串:A B A A B A B D C A B A B A B C 模式串:_ _ _ _ _ A B A B C 比较位: _ ▲ ↑ 仍然失配,next[2] = 0 的位置。 索引位:_ _ _ _ _ _ ▼ ↓ 字符串:A B A A B A B D C A B A B A B C 模式串:_ _ _ _ _ _ _ A B A B C 比较位: ▲ ↑ 仍然失配且比较位已经是0了,只能索引,比较+1继续找。 索引位:_ _ _ _ _ _ _ ▼ ↓ 字符串:A B A A B A B D C A B A B A B C 模式串:_ _ _ _ _ _ _ _ A B A B C 比较位: ▲ ↑ 仍然失配且比较位已经是0了,只能索引,比较+1继续找。 索引位:_ _ _ _ _ _ _ _ ▼ ↓ 字符串:A B A A B A B D C A B A B A B C 模式串:_ _ _ _ _ _ _ _ _ A B A B C 比较位: ▲ ↑ OK,继续向下走。 索引位:_ _ _ _ _ _ _ _ _ _ _ _ ▼ ↓ 字符串:A B A A B A B D C A B A B A B C 模式串:_ _ _ _ _ _ _ _ _ A B A B C 比较位: _ _ _ ▲ ↑ 失配,next[4] = 2 的位置。 索引位:_ _ _ _ _ _ _ _ _ _ _ _ ▼ ↓ 字符串:A B A A B A B D C A B A B A B C 模式串:_ _ _ _ _ _ _ _ _ _ _ A B A B C 比较位: _ ▲ ↑ OK,任务达成。
KMP的比较位数组计算方式遵循下列公式:
next[i]= { 0 | p = 1 k | 1 <= k < i,且 P1..Pk-1 == Pi-k+1..Pi-1 1 | 其他情况 }
更详细的说明参见:
KMP算法详解
BM算法
首先建立两个表:
坏字符表,包含所有可能出现的字符,0x00..0xFF,说明如果尾部字符是这个的话,模式串为了能匹配这个位置的这个字符,右移的最短距离。
好后缀表,说明如果模式串中若有与某个长度的后缀一致的子串,则可以向右移动的距离。
A 对齐源串和模式串的头部; B 比较模式串尾字符与源串同位置字符是否一致; B.1 若不一致,则源串字符是否存在于模式串中; B.1.1 若不存在,则跳过模式串长度,转到B条目继续比较; B.1.2 若存在,则根据坏字符表右移模式串,转到B条目; B.2 若一致,反向比较倒数第二字符是否一致,直到倒数第n字符不一致,于是…… 检查坏字符表和好后缀表,选择可以移动的最大距离。
更详细的说明参见:
字符串匹配算法 boyer-moore算法
Boyer-Moore 经典单模式匹配算法
组合优化
首先,KMP算法可能会比较一些显然不可能匹配的情况:
模式串:A B A B A next:0 0 1 2 3 索引位:_ _ _ ▼ ↓ 字符串:A B A B D A B ... 模式串:A B A B A 比较位:_ _ _ ▲ ↑ 失配,next[4] = 2。 索引位:_ _ _ ▼ ↓ 字符串:A B A B D A B ... 模式串:_ _ A B A B A 比较位: _ ▲ ↑ 失配,next[2] = 0。 索引位:_ _ _ ▼ ↓ 字符串:A B A B D A B ... 模式串:_ _ _ _ A B A B A 比较位: ▲ ↑ 失配,比较位和索引位+1进行下一个比较。 索引位:_ _ _ _ ▼ ↓ 字符串:A B A B D A B ... 模式串:_ _ _ _ _ A B A B A 比较位: ▲ ↑ OK,继续之后的比较。
实际上,我们可以看到,上面例子里的第2,第3次比较根本没有必要。根本就可以直接跳到第4步继续下去。问题出现在什么地方?问题就出现在next的公式中。
next[i]= { 0 | p = 1 k | 1 <= k < i,且 P1..Pk-1 == Pi-k+1..Pi-1 1 | 其他情况 }
这个公式只说明了自匹配的情况,没说明在自匹配的基础上还应该有一个尾字符的不匹配。
next[i]= { 0 | p = 1 k | 1 <= k < i,且 P1..Pk-1 == Pi-k+1..Pi-1 & Pk != Pi 1 | 其他情况 }
也就是说,若两个子串的下一个字符也一样,就没必要再匹配,因为只有源串里面比较的那个字符不一样才需要移动。
从Boyer-Moore Algorithm之中学习到,改良的BM算法中的好后缀表的建立也包含了同样的思想,而且更玄的是,好后缀表竟然能考虑到模式串的边界之外隐含的匹配信息,的确很强大。
另外,向BM算法致敬,若尾字符根本没出现在模式串中的话,所有的比较都根本无意义。所以这种情况下,相比上面调整的KMP算法的小挪移,可以实现大挪移。
修改之后的算法逻辑如下:
模式串:A B A B A next:0 1 0 1 0 A 对齐源串和模式串的头部; B 比较模式串尾字符与源串同位置字符是否一致; B.1 若不一致,则源串字符是否存在于模式串中; B.1.1 若不存在,则跳过模式串长度,转到B条目继续比较; B.1.2 若存在,则根据坏字符表右移模式串,转到B条目; B.2 若一致,使用“改·KMP法”匹配,决定模式串的右移值,转到B条目继续比较。
实际上,我在代码里创建的是跳转表,而不是指针索引位表。
模式串:A B A B A 步进值:1 1 3 3 5 表示若此位失配,则模式串向右移动的位置。 索引位:index[n] = max(n - step[n], 1); 例子1: 索引位:_ _ _ ↓ 字符串:A B A D A A B ... 模式串:A B A B A 比较位:_ _ _ ↑ 失配,step[4] = 3,index = max(4 - step[4], 1) = 1。 索引位:_ _ _ ↓ 字符串:A B A D C A B ... 模式串:_ _ _ A B A B A 比较位: ↑ 例子2: 索引位:_ _ ↓ 字符串:A B C A B D A B ... 模式串:A B A B A 比较位:_ _ ↑ 失配,step[3] = 3,index = max(3 - step[3], 1) = 1。 索引位:_ _ _ ↓ 字符串:A B C A B D A B ... 模式串:_ _ _ A B A B A 比较位: ↑ 例子3(更为复杂的): 模式串:A B A B A C A B A B A D 步进值:1 1 3 3 5 2 7 7 9 9 11 6 索引值:1 1 1 1 1 4 1 1 1 1 1 6 索引位:_ _ _ _ _ ↓ 字符串:A B A B A D A B ... 模式串:A B A B A C A B A B A D 比较位:_ _ _ _ _ ↑ 失配,step[6] = 2,index = 4。 索引位:_ _ _ _ _ ↓ 字符串:A B A B A D A B... 模式串:_ _ A B A B A 比较位: _ _ _ ↑ 例子4: 索引位:_ _ _ _ _ _ _ _ _ _ _ ↓ 字符串:A B A B A C A B A B A C A B ... 模式串:A B A B A C A B A B A D 比较位:_ _ _ _ _ _ _ _ _ _ _ ↑ 失配,step[12] = 6,index = 6。 索引位:_ _ _ _ _ _ _ _ _ _ _ ↓ 字符串:A B A B A C A B A B A C A B ... 模式串:_ _ _ _ _ _ A B A B A C A B A B A D 比较位: _ _ _ _ _ ↑
步进值的计算方法如下:
模式串:A B A B A C A B A B A D 步进值:1 2 3 4 5 6 7 8 9 10 11 12 <- 设定初始值 第一轮:设定delta = 1 从头开始比较,直到发现不同,修改发现位置的步进值,设定为min(step, delta) 模式串:A B A B A C A B A B A D 比较位:↑__↑ 设定步进值: 步进值:1 (1) ... 第二轮:设定delta = 2 从头开始比较,直到发现不同,修改发现位置的步进值,设定为min(step, delta) 模式串:A B A B A C A B A B A D 比较位:↑_____↑ 比较位: ↑_____↑ 比较位: ↑_____↑ 比较位: ↑_____↑ 设定步进值: 步进值:1 1 3 4 5 (2) ... 直到delta = length - 1
样例代码
最后,Java代码如下:
/** * @version 1.0 * @author Regular * @date 2009-06-11 */ package cn.sh.huang; import java.io.File; import java.io.FileInputStream; import java.nio.charset.Charset; public class StringCmp { /** * Entrance method * * @param args */ public static void main(String[] args) throws Exception { String fileName = "C:\\Program Files\\Java\\jdk1.6.0_13\\LICENSE"; String pattern = "enef"; File file = new File(fileName); int fileLen = (int) file.length(); FileInputStream fis = new FileInputStream(file); byte[] buffer = new byte[fileLen]; fis.read(buffer); int i = indexOfData(buffer, 0, pattern); System.out.println(i); } private static int indexOfData(byte[] buffer, int index, String s) { byte[] pattern = s.getBytes(Charset.forName("US-ASCII")); int[] fast_shift = new int[256]; // 模式串尾字符比较结果的移动 for (int i = 0; i < 256; i++) { fast_shift[i] = pattern.length; } for (int i = pattern.length - 1, j = 0; i >= 0; i--, j++) { int x = 0xFF & pattern[i]; if (fast_shift[x] > j) { fast_shift[x] = j; } } int[] slow_shift = new int[pattern.length]; // 改·KMP算法的移动 getNextStep(pattern, slow_shift); int cursor = 0; outterLoop: while (index + pattern.length <= buffer.length) { // 首先检查index + pattern.length - 1位置的字符,决定快速移动距离 int x = 0xFF & buffer[index + pattern.length - 1]; int shift = fast_shift[x]; if (shift > 0) { index += shift; cursor = 0; continue; } // 若尾字符一致,使用改·KMP算法决定慢速移动距离 while (cursor < pattern.length - 1) { if (pattern[cursor] != buffer[index + cursor]) { index += slow_shift[cursor]; cursor = cursor - slow_shift[cursor]; if (cursor < 0) { cursor = 0; } continue outterLoop; } cursor++; } return index; } return -1; } /** * <pre> * idx = max(0, n - step) * a b a b a c a b a b a d step idx * X a b a b a c a b a b a d 1 0 * a X b a b a c a b a b a d 1 0 * a b X a b a b a c a b a b a d 3 0 * a b a X b a b a c a b a b a d 3 0 * a b a b X a b a b a c a b a b a d 5 0 * a b a b a X a c a b a b a d 2 3 * a b a b a c X a b a b a c a b a b a d 7 0 * a b a b a c a X b a b a c a b a b a d 7 0 * a b a b a c a b X a b a b a c a b a b a d 9 0 * a b a b a c a b a X b a b a c a b a b a d 9 0 * a b a b a c a b a b X a b a b a c a b a b a d 11 0 * a b a b a c a b a b a X a b a b a d 6 5 * </pre> * @param pattern * @param next */ private static void getNextStep(byte[] pattern, int[] next) { for (int i = 0; i < pattern.length; i++) { next[i] = i + 1; } // 卷积 for (int delta = 1; delta < pattern.length; delta++) { int i = 0; int j = i + delta; while (pattern.length > j && pattern[i] == pattern[j]) { i++; j++; } if (pattern.length > j) { if (next[j] > delta) { next[j] = delta; } } } } }
总体看来,还是BM算法更好一些,其效率比KMP算法更高。
我这里虽然借用了BM的坏字符思想并改进了KMP的步进值计算,但由于逐字符比较始终是从头开始,而不像BM算法是从尾部开始,所以小挪移的潜力没有BM的那么大。
后续设想
若模式串相对较长的话,可以在模式串中找几个稀疏分布的点,比较的时候,首先比较这几个点的字符是否与源串相同,如果不同就没必要逐个字符比较了,肯定不一致,可以往后挪了。
发表评论
-
把Spring容器中的bean绑定到通过代码创建的对象
2012-04-26 16:17 2267Spring提供了对配置中创建对象的字段实例注入。但如果是通过 ... -
动态注册消息类型及处理函数
2011-10-01 15:56 992内容略。参见代码演示。 -
代码实例
2011-02-14 17:17 1007代码实例文件 -
如何在类外部调用被子类覆盖的父类方法
2011-01-20 14:46 1888题目比较绕。以下用一个简单的例子说明: public cl ... -
SWT应用的开发实例:没有使用到OSGi
2011-01-14 11:27 1518添加音效,以及中奖名单回看功能。 SWT应用一枚。具体方法见 ... -
运行期代码问题检查技术的研究
2010-11-29 13:30 1136以下用我之前代码中的一个bug作为说明,解释如何实现代码在运行 ... -
代码潜在故障的动态分析
2010-11-16 12:24 1499引子 大家都听说过FindBugs的大名。这是一款静态代码分析 ... -
健壮的、便捷的、异步的SocketChannel实现
2010-04-27 10:34 8373Socket通信比较常见的问题有如下几种: 1、设置收发超时; ... -
打算研究学习一下OSGi和Equinox
2010-02-10 11:26 1138看到一本很直接讨论这个题目的书,不过要等到3月1日才出来。 ... -
关键应用服务的集群技术模拟
2010-01-08 14:41 1076集群技术,也就是俗称的Cluster,是为了保证某种应用不间断 ... -
JarSpur 检查引用包归属的小工具
2009-12-25 17:31 1076图形化的界面,允许你导入任意多的在项目中可能需要的Jar包。 ... -
class.getResourceAsStream()与ClassLoader.getResourceAsStream()的区别
2009-11-11 17:33 2045在jar包里获得流形式的资源有两种方法,一个是Class.ge ... -
MultiKeyedMap方案的实现
2009-11-10 11:55 3751方案背景 所谓“MultiKey ... -
Java2D: 硬件加速 - 第二部分 - 缓冲策略:Buffer Strategies
2009-11-02 12:52 2934原文地址:Java2D: Hardware ... -
Java2D: 硬件加速 - 第一部分 - 非恒定图像类:Volatile Image
2009-10-30 16:19 4291原文地址:Java2D: Hareware Accelerat ... -
自建的MiniChart库,目前实现了点图、折线图、柱状图和饼图
2009-07-15 11:08 1228花了大约一个星期时间做的MiniChart库。 由于现在的免费 ... -
BM方案模式匹配的Java代码实现
2009-06-17 13:47 1565速度还算快,例子里比较的文件一共371个,3,293,472字 ... -
读写进程的互斥锁
2009-03-16 15:27 1588以下的代码完成了对某个资源的读写互斥锁,具体说明如下:1. 若 ... -
Object数组到泛型数组转换的伪解决方案
2009-01-23 10:44 4324闲来无事,想要用目前的Java技术模拟一个对象数据库。最初只是 ...
相关推荐
在Java编程中,模式匹配是一种强大的工具,常用于解析、简化或操作复杂的对象结构,如在解析表达式或处理树形结构时。本篇将详细解释如何在Java中实现模式匹配,以及它如何帮助提高代码的性能和可读性。 在给定的...
KMP(Knuth-Morris-Pratt)算法是一种字符串匹配算法,主要用于在主串中查找子串出现的位置。这个算法避免了在遇到不匹配字符时的回溯,提高了搜索效率。KMP算法的核心是构造一个部分匹配表(也叫失配表),它记录了...
串联重复序列识别的关键在于模式匹配算法,尤其是循环串的匹配算法。该算法可以通过对传统的字符串匹配算法进行改动来实现,从而定位特定基因在循环串中的位置。通过这种案例的引入,学生不仅能够理解生物信息学中所...
首先,项目采用了一种基于单词前缀树(Trie)的多串模式匹配算法。前缀树,也被称为字典树,是一种数据结构,用于存储一个字符串集合。在英文拼写检错中,它能快速地查找和比较单词。当输入的单词与字典中的单词进行...
- **扩展性**:通过工厂模式,当需要添加新产品时,只需修改工厂的实现而不需改动客户端代码,增加了系统的灵活性。 - **缺点**: - **复杂度**:虽然工厂模式提高了代码的可维护性和扩展性,但也可能导致系统...
Rete 算法是一种高效的模式匹配算法,特别适用于处理大量的规则和事实。 在传统的编程方式中,业务逻辑通常嵌入在应用程序代码中,当业务规则发生变化时,需要重新编译和部署整个应用。而使用规则引擎如 Drools,...
利用SVM算法,根据移动台在通话过程中接收到的来自服务小区和邻小区**号特征,预测其所在的网格分区号,实现高精度的模式匹配定位。 综上所述,基于SVM的GSM网络定位技术是一种高效、实用的定位方法,通过分析移动...
1. 规则表示语言(Rete算法):Rete是一种高效的模式匹配算法,用于快速找出满足条件的规则。 2. Drools:这是一个开源的Java规则引擎,支持Java Rule Language (JRL) 和 Decision Table 格式的规则定义。 3. BRMS...
然而,在关系数据库中,由于其基于记录的结构,需要额外的关系-对象服务来解决对象和表格之间的转换问题,这可能导致一些挑战,比如数据不匹配和额外的编程复杂性。 为了解决这些问题,出现了持久化框架。这种框架...
为了提高程序的可扩展性和适应性,设计时应考虑到可能的改动,如增加新的游戏模式、调整地图大小或元素数量等。类和方法的结构应保持清晰,便于理解和修改。 以上是连连看2游戏算法的主要实现步骤,实际开发中还...
这意味着开发者可以自由地查看、分析和学习其中的代码结构、算法和设计模式,但不应该直接将其应用于盈利性产品中,以免引发版权问题。 【标签】"泡泡龙" 和 "c++" 指明了这个项目的主要内容。"泡泡龙" 指的是游戏...
Rete 算法是一种高效的模式匹配算法,用于匹配事实(Facts)和规则。它通过构建 RETE 网络来加速规则的评估,当新的事实被插入到 Working Memory 中时,算法能够迅速找到匹配的规则并执行相应的动作。 **Drools ...
当需要修改某一部分功能时,只需要在对应层进行改动,而不会影响到其他层,这对于大型项目的长期维护至关重要。 总的来说,"新闻管理系统model2模式"是一个典型的分层架构的应用实例,它通过合理划分职责,提高了...
它可以是简单的字符串匹配,也可以涉及到正则表达式,以便进行更复杂的模式匹配。 - 替换功能:配合搜索,替换功能可以将找到的文本或模式替换为新的内容。这对于大规模修改代码中的某些元素或更新文档中的特定信息...
1. **智能匹配系统**:游戏中的自动匹配算法确保了每局游戏的挑战性和公平性,让玩家在消除图案时能感受到策略与运气的完美结合。 2. **多种游戏模式**:除了传统的单人模式,可能还包括多人对战模式、计时赛模式等...
该工具通过优化拷贝算法,能够智能地识别需要更新或新建的文件,避免了对未改动文件的无谓处理,从而节省了时间和系统资源。 “智能匹配”是这个工具的另一个关键特性。它可能包含以下方面:文件的哈希对比,用于...
正则表达式(Regex)是编程语言中用于处理字符串的强大工具,它允许我们用简洁的模式来匹配、查找、替换或提取文本中的特定字符序列。GNU regex库提供了对正则表达式的支持,使得程序员能够在自己的应用程序中轻松...