字符串的模式匹配算法
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
在前面的图文中,我们讲了“串”这种数据结构,其中有求“子串在主串中的位置”(字符串的模式匹配)这样的算法。解决这类问题,通常我们的方法是枚举从A串(主串)的什么位置起开始与B串(子串)匹配,然后验证是否匹配。假设A串长度为n,B串长度为m,那么这种方法的复杂度是O(m*n)的。虽然很多时候复杂度达不到m*n(验证时只看头一两个字母就发现不匹配了),但是我们有许多“最坏情况”,比如:
A=“aaaaaaaaaaaaaaaaaaaaaaaaab”,B=“aaaaaaaab”。
大家可以忍受朴素模式匹配算法(前缀暴力匹配算法)的低效吗?也许可以,也许无所谓。
有三位前辈D.E.Knuth、J.H.Morris、V.R.Pratt发表一个模式匹配算法,最坏情况下是O(m+n),可以大大避免重复遍历的情况,我们把它称之为克努特-莫里斯-普拉特算法,简称KMP算法。
假如,A=“abababaababacb”,B=“ababacb”,我们来看看KMP是怎样工作的。我们用两个指针i和j分别表示,。也就是说,i是不断增加的,随着i 的增加j相应地变化,且j满足以A[i]结尾的长度为j的字符串正好匹配B串的前j个字符(j当然越大越好),现在需要检验A[i+1]和B[j+1]的关系。
例子:
S=“abcdefgab”
T=“abcdex”
对于要匹配的子串T来说,“abcdex”首字符“a”与后面的串“bcdex”中任意一个字符都不相等。也就是说,既然“a”不与自己后面的子串中任何一字符相等,那么对于主串S来说,前5位字符分别相等,意味着子串T的首字符“a”不可能与S串的第2到第5位的字符相等。朴素算法步骤2,3,4,5的判断都是多余,下次的起始位置就是第6个字符。
例子:
S=“abcabcabc”
T=“abcabx”
如果T串后面也含有首字符“a”。对于开始的判断,前5个字符完全相等,第6个字符不等,此时,根据刚才的经验,T的首字符“a”与T的第二位字符“b”、第三位字符“c”均不等,所以不需要做判断,朴素算法步骤2,3都是多余。
因为T的首位“a”与T第四位“a”相等,第二位的“b”与第五位的“b”相等。而第四位的“a”与第五位的“b”已经与主串S中的相应位置比较过了,是相等的,因此可以断定,T的首字符“a”、第二位的字符“b”与S的第四位字符和第五位字符也不需要比较了,下次的起始位置就是6,T的起始位置就是第3个字符。
对比这两个例子,可知两种情况下主串当前位置的都是第6个字符。要考虑的变化就是子串的下标j值了。通过观察也可发现,我们屡屡提到了T串的首字符与自身后面字符的比较,发现如果有相等字符,j值的变化就会不相同。也就是说,这个j值的变化与主串其实没什么关系,关键就取决于T串的结构中是否有重复的问题。
下面按照程序的方式来介绍,字符串第一个字符的下标为0。
由于T=“abcde x”,当中没有任何重复的字符,所以j就由5变成了0。
由于T=“abcab x”,前缀的“ab”与后面“x”之前串的后缀“ab”是相等的。因此j就由5变成了2。因此,我们可以得出规律,j值的多少取决于当前字符之前的串的前,后缀的相似度。
我们把T串各个位置的j值的变化定义为一个数组next,那么next的长度就是T串的长度。于是我们可以得到下面的定义:
起始字符 next[0] = -1;
如果后面没有与起始字符相同的字符则next[i] = 0;
如果后面有与起始字符相同的字符则next[i] = -1;与前缀进行匹配,匹配完毕的后一位的j值记录匹配字符的个数。
其他next[i] = 0;
即next[i]的值表示,第i位不同时,j的取值。具体构造方法查看后面代码。
例子:
T=“abcdex”
Next = -100000
例子:
T=“abcabx”
Next = -100-102
例子:
T= “aaaaaaaab”
Next = -1-1-1-1-1-1-1-17
例子:
T =“ababacb”
Next = -10-10-130
讲完原理之后,我们利用策略模式作为解决相同问题利用不同算法的解决方案。
namespace StringMatching
{
public abstract class StringMatchingStrategy
{
public abstractint StringMatchingAlgorithm(string source, string substr);
}
}
namespace StringMatching
{
public class KmpMatching: StringMatchingStrategy {
privateint[] GetNext(string substr)
{
if(substr == null)
thrownew ArgumentException("subs tr");
int i =0, j = -1;
int[]nextVal = new int[substr.Length];
nextVal[0] = -1;
while(i < substr.Length - 1)
{
if(j == -1 || substr[i] ==
substr[j])
{
i++;
j++;
if(substr[i] != substr[j])
nextVal[i] = j;
else
nextVal[i]
=nextVal[j];
}
else
{
j = nextVal[j];
}
}
return nextVal;
}
public override int StringMatchingAlgorithm(stri ng source, string substr)
{
if(source == null)
thrownew ArgumentException("sour ce");
if(substr == null)
thrownew ArgumentException("subs tr");
int i = 0, j = 0, v;
int[]nextVal = GetNext(substr);
while(i < source.Length && j <
substr.Length)
{