理解与实现KMP模式匹配算法
来源:互联网 发布:matlab cell数组 编辑:程序博客网 时间:2024/05/18 01:19
看了差不多一天,终于理解了KMP算法到底是怎么的一回事,核心难点是求出前缀与后缀的公共部分的最大长度。
首先先了解一下字符串的前缀与后缀:
- "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。
前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。
当一个字符串前缀与后缀出现相同的情况下,当后缀匹配相同时,那么前缀一定会匹配相同,反之相当。而出现这种情况下时,字符串必定含有相同的字符,相同字符出现在前缀与后缀中,这样我们可以把字符串分成三个部分:
【a】【b】【a】
【a】是相同的部分,【b】是不同的部分
KMP算法的匹配过程如下:
位置 : 1 2 3 4 5 6
目标字符串s1: 【a】【b】【a】【b】【x】【。】
要匹配出字符串s2:【a】【b】【a】【x】【。】
当s1匹配4k时,由于4位置不满足,所以这次匹配失败,重新移动位置进行匹配,而对于s2而已就不需要,因为它的1 与3是相同的,所以它不需要再和再和s2的3相匹配,因为它和2位置是不相同的,所以它也不需要再和s1的2位置再匹配了,因为【b】与【b】已经匹配相同了,所以可以移动如下:
位置 : 1 2 3 4 5 6
目标字符串s1: 【a】【b】【a】【b】【x】【a】【b】【a】【x】【。】
要匹配出字符串s2: 【a】【b】【a】【x】【。】
【a】与【b】不相同,移动1,匹配5:
位置 : 1 2 3 4 5 6 7 8 9 10
目标字符串s1: 【a】【b】【a】【b】【x】【a】【b】【a】【x】【g】【a】【b】【a】【x】【。】
要匹配出字符串s2: 【a】【b】【a】【x】【。】
【a】与【b】不相同,移动1,匹配6:
位置 : 1 2 3 4 5 6 7 8 9 10
目标字符串s1: 【a】【b】【a】【b】【x】【a】【b】【a】【x】【g】【a】【b】【a】【x】【。】
要匹配出字符串s2: 【a】【b】【a】【x】【。】
【a】与【b】不相同,移动1,匹配到10:
位置 : 1 2 3 4 5 6 7 8 9 10
目标字符串s1: 【a】【b】【a】【b】【x】【a】【b】【a】【x】【g】【a】【b】【a】【x】【。】
要匹配出字符串s2: 【a】【b】【a】【x】【。】
位置 : 1 2 3 4 5 6 7 8 9 10
目标字符串s1: 【a】【b】【a】【b】【x】【a】【b】【a】【x】【g】【a】【b】【a】【x】【。】
要匹配出字符串s2: 【a】【b】【a】【x】【。】
11不同,移动1:
位置 : 1 2 3 4 5 6 7 8 9 10
目标字符串s1: 【a】【b】【a】【b】【x】【a】【b】【a】【x】【g】【a】【b】【a】【x】【。】
要匹配出字符串s2: 【a】【b】【a】【x】【。】
所以可以看到,核心的地方是要知道s2到底要移动多少位置。
移动位置大小=已经匹配的字符数-匹配字符位置(包括该位置)之前的字符串的前缀与后缀的公共部分的最大长度(next(i))。
如:
位置 : 1 2 3 4 5 6 7 8 9 10
目标字符串s1: 【a】【b】【a】【b】【x】【a】【b】【a】【x】【g】【a】【b】【a】【x】【。】
要匹配出字符串s2: 【a】【b】【a】【x】【。】
匹配到【。】 事,该【a】【b】【a】【x】【。】的前缀与后缀的公共部分最大长度为0,已经匹配了4个字符,故移动4-0个位置:
位置 : 1 2 3 4 5 6 7 8 9 10
目标字符串s1: 【a】【b】【a】【b】【x】【a】【b】【a】【x】【g】【a】【b】【a】【x】【。】
要匹配出字符串s2: 【a】【b】【a】【x】【。】
所以,匹配转化为了求每个字符对应的next(i)的值:
我们知道,前缀一定是包含了前面的字符,后缀是包含了后面的字符,我们知道位置为i 的字符的next(i),所以前面next(i)个字符与后面的next(i)个字符是相同的
【0】。。。【next(i)-1】【next(i)】 。。。【i-next(i)】。。【i】【i+1】
假如【next(i)】与【i+1】相同,那么,next(i+1)=next(i)+1;
假如不相同,就判断【0】。。。【next(i)-1】【next(i)】,通过next(next(i))就判断前面有多少与后面相同的,如果【next(next(i))】与【i+1】相同,那么next(i+1)=next(next(i))+1.....一直判断下去。
下面给出一个我实现的Java的获取next的方法,就是利用了递归:
<span style="color:#cc0000;"> </span>public static int[] getnext(String target) { int[] next=new int[target.length()]; char[] cs=target.toCharArray(); for(int i=0;i<next.length;i++) { next[i]=getnum(i, cs,next, cs[i]); } return next; } public static int getnum(int id,char[] cs,int[] next,char targetc) { if(id==0) return 0; int prenxet=next[id-1]; if(cs[prenxet]==targetc) { return prenxet+1; } else{ return getnum(prenxet,cs,next,targetc); } }
实现了获取next(i),接下来实现匹配就简单的很了,看我实现的代码如下:
/** * 2015年10月27日上午11:21:22 * @param matchStr 想要找到是否含有targetstr的字符串 * @param targetStr 要匹配的字符串 * @return */ public static int KmpMatch(String matchStr,String targetStr) { char[] matchChars=matchStr.toCharArray(); char[] targetChars=targetStr.toCharArray(); int[] next=getNext(targetStr); int matchIndex=0;//matchStr 游标 int targetIndex=0;//target游标 int movelength=0; int mathlength=0; while(matchIndex<matchChars.length) { if(matchChars[matchIndex]==targetChars[targetIndex]) { matchIndex++; targetIndex++; mathlength++; } else { if(mathlength>0) { movelength=mathlength-next[targetIndex];//要移动的长度为 已匹配长度减去 next(i) targetIndex=targetIndex-movelength;//移动位置 } else { matchIndex++; targetIndex=0; } } if(targetIndex==targetChars.length) return matchIndex-targetIndex; else if(targetIndex<0)//若移动已经超出目标,返回-1 return -1; } return -1;//没有匹配到返回-1 }
看一下测试代码:
public static void main(String[] args) {String matchstr ="aass啊wwww"; String targetstr="啊"; System.out.println("--:"+KmpMatch(matchstr, targetstr)); // KMP匹配字符串 }
输出如下:--:4
这只是实现了匹配第一个字符串,接下来实现匹配所有的字符串,将匹配的下标位置通过集合保存起来:
实现代码如下:
/** * 2015年10月27日上午11:21:22 * * @param matchStr * 想要找到是否含有targetstr的字符串 * @param targetStr * 要匹配的字符串 * @return */public static java.util.List<Integer> KmpMatch(String matchStr,String targetStr) {char[] matchChars = matchStr.toCharArray();char[] targetChars = targetStr.toCharArray();int[] next = getNext(targetStr);int matchIndex = 0;// matchStr 游标int targetIndex = 0;// target游标int movelength = 0;int mathlength = 0;java.util.List<Integer> matchIndexs = new ArrayList<Integer>();while (matchIndex < matchChars.length) {if (matchChars[matchIndex] == targetChars[targetIndex]) {matchIndex++;targetIndex++;mathlength++;} else {if (mathlength > 0) {movelength = mathlength - next[targetIndex];// 要移动的长度为// 已匹配长度减去// next(i)targetIndex = targetIndex - movelength;// 移动位置} else {matchIndex++;targetIndex = 0;}}if (targetIndex == targetChars.length) {matchIndexs.add(matchIndex - targetIndex);movelength = 0;mathlength = 0;targetIndex++;targetIndex = 0;} else if (targetIndex < 0)// 若移动已经超出目标,返回return matchIndexs;}return matchIndexs;}
测试,通过一个文件读取要搜索匹配的字符串:
public static void main(String[] args) {File f = new File("E:/test/test.txt");if (f.exists()) {FileReader reader = null;try {reader = new FileReader(f);} catch (FileNotFoundException e) {e.printStackTrace();}String targetstr = "的";StringBuffer matcherstr=new StringBuffer();char[] readchar = new char[30];try {while ( reader.read(readchar) >0) {matcherstr.append(readchar);}} catch (IOException e) {e.printStackTrace();}java.util.List<Integer> indexs = KmpMatch(matcherstr.toString(),targetstr);for (int i = 0, size = indexs.size(); i < size; i++)System.out.println("匹配成功的第:"+i+1+"个,位置为:" + indexs.get(i)); // KMP匹配字符串}}
输出如下:
匹配成功的第:01个,位置为:8匹配成功的第:11个,位置为:106匹配成功的第:21个,位置为:158匹配成功的第:31个,位置为:181匹配成功的第:41个,位置为:218匹配成功的第:51个,位置为:230匹配成功的第:61个,位置为:277匹配成功的第:71个,位置为:332匹配成功的第:81个,位置为:380匹配成功的第:91个,位置为:391
匹配正确,以上只是实现的思路,给出的代码算法也是不是最优的,提供一种思路而已,读取大量字符串时也需要考虑很多问题,暂时不再描述。
- 理解与实现KMP模式匹配算法
- KMP模式匹配算法分析与实现
- 模式匹配 -- KMP 算法原理与实现
- 理解KMP模式匹配算法
- KMP模式匹配算法实现
- 模式匹配KMP算法实现
- KMP模式匹配算法实现
- 模式匹配与KMP算法
- KMP模式匹配算法的一些理解
- KMP模式匹配算法的两点理解
- 【模式匹配】KMP算法的简明理解
- 自我理解的KMP 算法 模式匹配
- 算法:KMP算法实现的模式匹配
- KMP模式匹配算法 C++实现
- KMP字符串模式匹配算法实现(php)
- 模式匹配 KMP C语言算法实现
- C++实现KMP模式匹配算法
- KMP模式匹配算法C++实现
- HashMap遍历性能对比
- lua string 分割字符串
- db file scattered read
- win7下配置Apache本地虚拟主机
- Xcode7,消失的pin菜单(Editor->pin)
- 理解与实现KMP模式匹配算法
- CSS3 @font-face
- setTimeout()执行时序
- error Code:410 Error Message:appid and openid not match 威富通技术支持,兴业银行微信支付接入支持
- 替换index和show中select_tag的方法
- chown与chmod的简单比较
- 关于UIView的autoresizingMask属性的研究
- 基于Google Json工具包解析数据
- Litepal 混淆注意