声明:先看一下阮一峰的网络日志关于字符串的KMP算法的讲解。本文图片均引用于这篇日志。
在先前的笔试中遇到了关于字符串匹配的问题,一时脑袋卡壳没写好算法。现在就来分析分析
暴力算法和KMP算法各自原理,以及代码实现,之间差异,并且总结一下好算法的一般思路。
===========================================================================
各自原理:
暴力算法:
1.
我们把长的字符串做为一个文本字符串,命名为strText,把短的字符串称为目标串,命名为strTarget。
文本串"BBC ABCDAB ABCDABCDABDE"的第一个字符‘B’与目标串"ABCDABD"的第一个字符‘A’
比较不产生匹配,在整个过程中,我们假设红色虚线是固定的。因此,文本串向左边移动一个字符。
2.
字符‘B’与‘A’不产生匹配,文本串继续左移。
3.
直到这里,此时出现第一个匹配,程序中将文本串和目标串都向左移动,并且记录下此时文本串出来比较
的那个元素(就是字符‘A’)的位置。
4.
继续比较,又是一个匹配,继续移动。
5.
此时出现不匹配,比较重新归位,根据前面记录的‘A’的位置文本串进入下一个字符‘B’,而目标串的下标重新开始,
继续比较。
6.
这个就是暴力算法的大致原理分析过程。
KMP算法:
7.
一个基本事实是,当空格与D不匹配时,你其实知道前面六个字符是"ABCDAB"。KMP算法的想法是,设法利用这个已知信息,
不要把"搜索位置"移回已经比较过的位置,继续把它向后移,这样就提高了效率。
8.
怎么做到这一点呢?可以针对目标串,算出一张《部分匹配表》(Partial Match Table)。这张表是如何产生的,后面再介绍,
这里只要会用就可以了。
9.
已知空格与D不匹配时,前面六个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符B对应的"部分匹配值"为2,因此按照
下面的公式算出向后移动的位数:
移动位数 = 已匹配的字符数 - 对应的部分匹配值
6 - 2 = 4;所以移动 4 个字符。
10.
移动后就来到了这里,空格与字符‘C’不匹配,再次利用《部分匹配表》查询移动位数。2 - 0 = 2;因此移动2个字符。
11.
再次移动后来到了这里,空格与字符‘A’不匹配,再次利用《部分匹配表》查询移动位数。1 - 0 = 1;因此移动1个字符。
12.
一一对比完后到了D字符,发现文本串的字符‘C’与目标串字符‘D’不匹配,查询移动位数。6 - 2 = 4;因此移动4个字符。
13.
移动后就来到这了,一一比较完后发现全部匹配。目标串匹配成功!
14.
关于《部分匹配表》
下面介绍《部分匹配表》是如何产生的。
首先,要了解两个概念:"前缀"和"后缀"。
"前缀"指除了最后一个字符以外,一个字符串的全部头部组合;
"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。如上图所示。
15.
"部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例,
16.
"部分匹配"的实质是,有时候,字符串头部和尾部会有重复。比如,"ABCDAB"之中有两个"AB",那么它的"部分匹配值"就是2("AB"的长度)。
搜索词移动的时候,第一个"AB"向后移动4位(字符串长度-部分匹配值),就可以来到第二个"AB"的位置。
分析了这么详细,正如那句话,Talk is cheap, show me the code.来写一写代码吧!
代码实现:
暴力算法:
void SViolence( const char strText[ ], const char strSearch[ ] ) { int lengthOfstrText, lengthOfstrSearch; int i, j, ii; lengthOfstrText = strlen( strText ); lengthOfstrSearch = strlen( strSearch ); /*for( i = 0, j = 0, ii = 0; i < lengthOfstrText && j < lengthOfstrSearch; )*/ for( i = 0, j = 0, ii = 0; j < lengthOfstrSearch; ) { if( strText[ i ] == strSearch[ j ] ) { j++; i++; continue; } else { i = ii; i++; ii = i; j = 0;/* make a clear */ continue; } } if( j == lengthOfstrSearch ) printf( "Existence!" ); else printf( "No Existence!" ); }
在程序中的ii变量就是记录位置的。理解这个算法的运行过程可以很清楚的知道在算法中做了很多重复的比较工作。
时间复杂度分析:咋一看这个程序感觉很(hen)快(man)啊,只有一个for循环而已嘛。呵呵,其实你有所不知,这个for循环与众不同,它的结束不只是依赖于for循环条件而已,而是有循环条件和循环体的i,j,ii,等变量共同决定的。我们记lengthOfText = m,lengthOfSearch = n;所以这个算法的时间复杂度大概为:T(
n )= O( m * n )这个级别已经是我们所唾弃的了。