字符串匹配算法-KMP
举例来说,有一个字符串"BBC ABCDAB ABCDABCDABDE",我想知道,里面是否包含另一个字符串"ABCDABD"?
在上面这个例子中,字符串"BBC ABCDAB ABCDABCDABDE"称为主串,字符串"ABCDABD"称为模式串
许多算法可以完成这个任务,Knuth-Morris-Pratt算法(简称KMP)是最常用的之一。下面,我用自己的语言,解释KMP算法。
1、首先,主串"BBC ABCDAB ABCDABCDABDE"的第一个字符与模式串"ABCDABD"的第一个字符,进行比较。因为B与A不匹配,所以模式串后移一位
2、因为B与A不匹配,模式串再往后移
3、就这样,直到主串有一个字符,与模式串的第一个字符相同为止
4、接着比较主串和模式串的下一个字符,还是相同
5、直到主串有一个字符,与模式串对应的字符不相同为止
6、这时,最自然的反应是,将模式串整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把"搜索位置"移到已经比较过的位置,重比一遍
7、一个基本事实是,当空格与D不匹配时,你其实知道前面六个字符是"ABCDAB"。KMP算法的想法是,设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,继续把它向后移,这样就提高了效率
怎么做到这一点呢?可以针对模式串,算出一张《部分匹配表》(Partial Match Table)。这张表是如何产生的,后面再介绍,这里只要会用就可以了
8、已知空格与D不匹配时,前面六个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符B对应的"部分匹配值"为2,因此按照下面的公式算出向后移动的位数:
移动位数 = 已匹配的字符数 - 对应的部分匹配值(最后一个匹配字符)
因为 6 - 2 等于4,所以将模式串向后移动4位。
9、因为空格与C不匹配,模式串还要继续往后移。这时,已匹配的字符数为2("AB"),对应的"部分匹配值"为0。所以,移动位数 = 2 - 0,结果为 2,于是将模式串向后移2位。
10、因为空格与A不匹配,继续后移一位。
11、发现主串中的A与模式串中的A匹配,接着比较主串和模式串的下一个字符,还是相同,直到发现C与D不匹配。
12、因为C与D不匹配,所以移动位数= 6 - 2,继续将模式串向后移动4位
13、然后再逐位比较,直到模式串的最后一位,发现完全匹配,于是搜索完成。如果还要继续搜索(即找出全部匹配),移动位数 = 7 - 0,再将模式串向后移动7位,这里就不再重复了
14、下面介绍《部分匹配表》是如何产生的。
首先,要了解两个概念:"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合
15、"部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例
-"A"的前缀和后缀都为空集,共有元素的长度为0;
-"AB"的前缀为[A],后缀为[B],共有元素的长度为0;
-"ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
-"ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
-"ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;
-"ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;
-"ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。
Java实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | package com.buaa; import java.util.Arrays; import java.util.Random; /** * @ProjectName PatternMatchAlgorithm * @PackageName com.buaa * @ClassName KMP * @Description TODO * @Author 刘吉超 * @Date 2016-05-08 09:41:01 */ public class KMP { /** * KMP匹配字符串 * * @param originString 主串 * @param moduleString 模式串 * @param partialmatchArray 部分匹配表 * @return 若匹配成功,返回下标,否则返回-1 */ public static int match(String originString,String moduleString, int [] partialmatchArray) { // 主串 if (originString == null || originString.length() <= 0 ) { return - 1 ; } // 模式串 if (moduleString == null || moduleString.length() <= 0 ) { return - 1 ; } // 如果模式串的长度大于主串的长度,那么一定不匹配 if (originString.length() < moduleString.length()) { return - 1 ; } int origin_index = 0 ; int module_index = 0 ; boolean startMatch = false ; for (;origin_index < originString.length() && module_index < moduleString.length(); origin_index++){ // 主串中字符 char char_origin = originString.charAt(origin_index); // 模式串中的字符 char char_module = moduleString.charAt(module_index); if (char_origin == char_module){ startMatch = true ; module_index++; } else if (startMatch){ // 向前移动"已匹配的字符数 - 最后一个匹配字符对应的部分匹配值",换句话说,module_index的值就是最后一个匹配字符对应的部分匹配值 module_index = partialmatchArray[module_index]; startMatch = false ; origin_index--; } } if (module_index == moduleString.length()){ return origin_index - module_index; } return - 1 ; } /** * 计算每个元素对应的"部分匹配值" */ public static int [] matchStr(String moduleString) { if (moduleString == null || moduleString.length() == 0 ) { return new int [ 0 ]; } int [] matchArray = new int [moduleString.length()]; /* * 对matchArray数组进行初始化 * 虽然int数组中每个元素的默认值就是0,但在开发时,建议在使用变量之前,先对变量进行初始化 */ Arrays.fill(matchArray, 0 ); for ( int i = 0 ; i < moduleString.length(); i++) { for ( int j = 0 ; j < i; j++) { String prefixStr = moduleString.substring( 0 , j + 1 ); String suffixStr = moduleString.substring(i - j, i + 1 ); if (prefixStr.equals(suffixStr)) { matchArray[i] = prefixStr.length(); break ; } } } return matchArray; } /** * 随机生成字符串 * * @param length 表示生成字符串的长度 * @return String */ public static String generateString( int length) { String baseString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" ; StringBuilder result = new StringBuilder(); Random random = new Random(); for ( int i = 0 ; i < length; i++) { result.append(baseString.charAt(random.nextInt(baseString.length()))); } return result.toString(); } public static void main(String[] args) { // 主串 // String originString = generateString(10); String originString = "BBCABCDABBBCDABCDABDE" ; // 模式串 // String moduleString = generateString(4); String moduleString = "ABCDABD" ; // 部分匹配表 int [] partialmatchArray = matchStr(moduleString); System.out.println( "主串:" + originString); System.out.println( "模式串:" + moduleString); System.out.println( "部分匹配表:" + Arrays.toString(partialmatchArray)); int index = match(originString,moduleString,partialmatchArray); System.out.println( "匹配的下标:" + index); } } |
如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】。
如果,您希望更容易地发现我的新博客,不妨点击一下左下角的【关注我】。
如果,您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是【刘超★ljc】。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构