0) 引论
这里主要想讨论一下字符串的模式匹配,主要是KMP算法。
假设我们有一个字符串S,称之为原始串;另一个字符串T,称之为模式串;字符串匹配是指找出原始串S中是否含有模式串T,如果含有,则返回S中第一个匹配项的位置;
例如S=“abcabcabdef”;T=“abcabd”;那么我们可以得到S中含有模式串T,我们返回S中第一个匹配项的位置,即3。
1) Brute-Force算法
暴力算法,应该算是最朴素的算法了,大概小学生也能想出这个解决方法;BF算法的思想是:从S的第一个字符与T的第一个字符开始比较,如果相等,则进行下一个字符的比较,直到遇到不相等的项;这时我们把T的第一个字符与S的第二个字符相比较,重复上面的过程,知道找到匹配的项为止,或者返回一个值,显示没有匹配的项。
对上面的例子:
abcabcabdef
abcabd;
可知匹配不成立,所以要把T向右移一位,即
abcabcabdef
abcabd;
还是不成立,继续右移一位
abcabcabdef
abcabd;
还是不成立,继续右移一位
abcabcabdef
abcabd;
这时我们得到了匹配,返回位置3。
由此可见,BF算法就是一个类似于穷搜的算法,这个算法的时间复杂度为O(strlen(S)*strlen(T))。空间复杂度为O(1)
代码实现:
# include<stdio.h> #include<string.h> #include<windows.h> int BruteForce(char *S,char *T) { unsigned int i=0,j=0; int k=0; while(i<strlen(S)&&j<strlen(T)) { if(S[i+k]==T[j]) { k++; j++; } else { i++; j=0; k=0; } if(S[i+strlen(T)-1]==T[strlen(T)-1]) { return i; break; } } } void main() { char *S,*T; S = "abcdabcabdef"; T = "abcabd"; int BF; BF = BruteForce(S,T); printf("%d\n",BF); system("pause"); }
上面介绍的Brute-Force算法很简单也相当好理解,但是作为一个受过多年高等教育的人来说,用上面的方法简直是一种侮辱,因此我们需要更加复杂,更有挑战的算法(纯属玩笑话)。其实最主要的原因是上面的算法耗时,做了一些无用的功。每次把模式串向右移动一步并不够,例如上面的例子我们可以很容易的得到应该把T向右移动3步比较合适,那么怎么让计算机能够做到的,那么我们就需要KMP算法。同时我们也可以看到上面的算法没有利用T中已经匹配了的字符的一些信息。
KMP算法由D.E.Knuth与V.R.Pratt和J.H.Morris同时发现的。KMP算法的与BF算法的最大的不同就在于“失配”的处理上,BF算法当遇到失配时,做法是把模式串向右移动一位;而KMP算法则是向右移动更多的位,这个由一个next数组控制,已达到加速的目的。
对上面的例子:
abcabcabdef
abcabd;
我们可以得到在位置5处,发生了失配,也就是S[5]!=T[5]。那么对于KMP算法,我们需要将T向右移位,也就是说要另S[5]==T[next[5]];更通用的表述是当S[i]!=T[j]时,令j=next[j],使S[i]==T[next[j]],也就是说相当于将T向右移动了j-next[j]>=1位。因此KMP算法的核心就是next数组。next数组只与模式串T的本身性质有关,而与原始串S无关。
下面我们来看next数组怎样求:
KMP算法中,如果当前字符匹配成功,即S[i]==T[j],令i++,j++,继续匹配下一个字符;如果匹配失败,即S[i] != T[j],需要保持i不变,并且让j = next[j],这里next[j] <=j -1,即模式串T相对于原始串S向右移动了至少1位(移动的实际位数j - next[j] >=1)。
因此要应用KMP算法,我们首先可以利用模式串T来得到相应的next数组。
a) next[0]=-1: 任何串的首字符的next值为-1;
b) next[j]=-1: 模式串T中下标为j的字符,如果与首字符相同,且j的前面的1—k个字符与开头的1—k个字符不等(或者相等但T[k]==T[j])(1≤k<j)。
c) next[j]=k: 模式串T中下标为j的字符,如果j的前面k个字符与开头的k个字符相等,且T[j] != T[k] (1≤k<j)
d) next[j]=0: 除了上面的情况外其他的情况。
next数组值的意义
设在字符串S中查找模式串T,若S[m]!=T[n],那么,取T[n]的模式函数值next[n],
a) next[n]= -1 表示S[m]和T[0]间接比较过了,不相等,下一次比较 S[m+1]和T[0]。
b) next[n]=0 表示比较过程中产生了不相等,下一次比较 S[m] 和T[0]。
c) next[n]= k >0 但k<n, 表示,S[m]的前k个字符与T中的开始k个字符已经间接比较相等了,下一次比较S[m]和T[k]是否相等。
int *Next(char *T,int *next) { int j = 0, k = -1; next[0] = -1; while ( T[j] != '\0' ) { if (k == -1 || T[j] == T[k]) { ++j; ++k; if (T[j]!=T[k]) next[j] = k; else next[j] = next[k]; } else k = next[k]; } }