1、kmp算法讲解本文介绍了字符串匹配算法中的BF算法和KMP算法。本文中KMP算法介绍部分是关于KMP算法相关文章中最简洁的一篇文章之一。下一篇将继续介绍Horspool算法和BM算法。现在我们用的大部分软件都含有查找/替换的功能,要完成查找替换功能就需要用到字符串匹配算法。字符串匹配的算法有很多,最著名的字符串匹配算法有:KMP算法,Boyer-Moore(BM)算法。如果要我们自己去实现字符串匹配功能,我们会怎样去做呢?当然,我们最容易想到的方法就是人们常说的蛮力匹配法。术语:模式串:即你要查找或替换的字符串。源串/匹配串:你要从哪里查找或者替换哪里的字符串。比如你想在test.txt中查找
2、是否含有linux-code这个单词,那么模式串即为linux-code,源串/匹配串即为test.txt内的字符串。现在我们就来谈谈如何从源串中匹配模式串吧!算法一:Brute Force算法,即蛮力匹配法。判断一个字符串是否为另一个字符串的子串,最简单的方法就是将模式串与源串一个个字符比较,如果不相等则将模式串后移一位,继续比较。如此,直到子串完全匹配或者到达源字符串的末尾。代码也很简洁,几行就搞定。当然,其效率也是很低下的。intbf_match(char*src,char*pattern)if(src=NULL| pattern =NULL) return 0;intlen1=strl
3、en(src);intlen2=strlen(pattern);inti,j;for(i=0;ilen1;+i)for(j=0;jlen2;+j) if(srci+j!=patternj)break;if(j=len2-1)reurni; return-1; /没有匹配成功,返回-1当然还有很多蛮力算法的改进算法,我们这里不做进一步讨论。算法二:KMP算法曾经,KMP算法很让人头痛!是三个牛X哄哄的人提出来的。因此,我们第一眼看去,该算法并不好理解。关于KMP算法的阮一峰的这篇文章,是我看到过的写得最精炼简洁的一篇。原文摘录如下(原作者:阮一峰)。来听听,KMP算法是怎么实现的吧!举例来说,有
4、一个字符串BBC ABCDAB ABCDABCDABDE,我想知道,里面是否包含另一个字符串ABCDABD?1.首先,字符串BBC ABCDAB ABCDABCDABDE的第一个字符与搜索词ABCDABD的第一个字符,进行比较。因为B与A不匹配,所以搜索词后移一位。2.因为B与A不匹配,搜索词再往后移。3.就这样,直到字符串有一个字符,与搜索词的第一个字符相同为止。4.接着比较字符串和搜索词的下一个字符,还是相同。5.直到字符串有一个字符,与搜索词对应的字符不相同为止。6.这时,最自然的反应是,将搜索词整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把搜索位置移到已经比较过
5、的位置,重比一遍。7.一个基本事实是,当空格与D不匹配时,你其实知道前面六个字符是ABCDAB。KMP算法的想法是,设法利用这个已知信息,不要把搜索位置移回已经比较过的位置,继续把它向后移,这样就提高了效率。8.怎么做到这一点呢?可以针对搜索词,算出一张部分匹配表(Partial Match Table)。这张表是如何产生的,后面再介绍,这里只要会用就可以了。9.已知空格与D不匹配时,前面六个字符ABCDAB是匹配的。查表可知,最后一个匹配字符B对应的部分匹配值为2,因此按照下面的公式算出向后移动的位数:移动位数 = 已匹配的字符数 - 对应的部分匹配值因为 6 - 2 等于4,所以将搜索词向
6、后移动4位。10.因为空格与不匹配,搜索词还要继续往后移。这时,已匹配的字符数为2(AB),对应的部分匹配值为0。所以,移动位数 = 2 - 0,结果为 2,于是将搜索词向后移2位。11.因为空格与A不匹配,继续后移一位。12.逐位比较,直到发现C与D不匹配。于是,移动位数 = 6 - 2,继续将搜索词向后移动4位。13.逐位比较,直到搜索词的最后一位,发现完全匹配,于是搜索完成。如果还要继续搜索(即找出全部匹配),移动位数 = 7 - 0,再将搜索词向后移动7位,这里就不再重复了。14.下面介绍部分匹配表是如何产生的。首先,要了解两个概念:前缀和后缀。 前缀指除了最后一个字符以外,一个字符串
7、的全部头部组合;后缀指除了第一个字符以外,一个字符串的全部尾部组合。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, C
8、DAB, DAB, AB, B,共有元素为AB,长度为2; ABCDABD的前缀为A, AB, ABC, ABCD, ABCDA, ABCDAB,后缀为BCDABD, CDABD, DABD, ABD, BD, D,共有元素的长度为0。16.部分匹配的实质是,有时候,字符串头部和尾部会有重复。比如,ABCDAB之中有两个AB,那么它的部分匹配值就是2(AB的长度)。搜索词移动的时候,第一个AB向后移动4位(字符串长度-部分匹配值),就可以来到第二个AB的位置。从上面的讲解可以看出,KMP算法的核心是如何得到在字符失配时的移动步长。也就是如何得到部分匹配表。OK,现在KMP算法基本介绍完了。来二
9、两代码吧!KMP算法的一个关键部分是得到部分匹配表。那么部分匹配表如何得到呢?上文已经有详细的介绍。部分匹配值就是前缀和后缀的最长的共有元素的长度。以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, ABCD
10、A,后缀为BCDAB, CDAB, DAB, AB, B,共有元素为AB,长度为2; ABCDABD的前缀为A, AB, ABC, ABCD, ABCDA, ABCDAB,后缀为BCDABD, CDABD, DABD, ABD, BD, D,共有元素的长度为0。据此,我们可以编码如下:/返回值为一个指针。其指向的地址块连续存放了部分匹配表。int* get_pmt(char* pattern)if(pattern=NULL)returnNULL;intlen=strlen(pattern);int* ppmt=(int*)malloc(len+1)*sizeof(int);/分配内存,用于存放
11、部分匹配表。 memset(ppmt,0,sizeof(len+1)*sizeof(int);/将分配的内存初始化为0inti,j,k;for(i=1;ilen;+i)for(j=0;ji;+j)for(k=0;k=ppmti) ppmti=k+1; returnppmt;好了,有了get_pmt函数后,我们就可以轻松的写出kmp算法了。全部代码如下,如果你看不明白,那去仔细看看正文吧。#include #include #include int* get_pmt(char* pattern)if(pattern=NULL)returnNULL;intlen=strlen(pattern);i
12、nt* ppmt=(int*)malloc(len+1)*sizeof(int);/分配内存,用于存放部分匹配表。 memset(ppmt,0,sizeof(len+1)*sizeof(int);/将分配的内存初始化为0inti,j,k;for(i=1;ilen;+i)for(j=0;ji;+j)for(k=0;k=ppmti) ppmti=k+1; returnppmt;intkmp_match(char*src,char*pattern)if(src=NULL| pattern=NULL)return-1;intlen1=strlen(src);intlen2=strlen(pattern
13、);int*pmt=get_pmt(pattern);printf(src len is:%dn pattern len is:%dn,len1,len2);inti,j;for(i=0;ilen1-len2;)for(j=0;j1 ? (j-pmtj):1;printf(i is %dn,i);/为了观察中间结果 break; if(j=len2-1)if(pmt)free(pmt);returni; if(pmt) free(pmt);return-1;intmain()charsrc32=teslinuxlitforlinuxlinuetestfor;charpattern10=linuxlinu;printf(kmp_match result:%dn,kmp_match(src,pattern);当然,上述代码并不是最优的代码,get_pmt函数的实现可以进行进一步优化,这里就不涉及了。作者:JJDiaries(阿呆) 微信公众号:linux-code
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1