字符串模式匹配是一个经常用到的功能,比如当我们在一个文档中Ctrl+F 查找一个字符串时,就用到了字符串模式匹配的知识:其实现算法是怎样的呢?
假设主字符串为S:BBC ABCDAB ABCDABCDABDE
模式字符串为T: ABCDABD
1)朴素模式匹配算法(暴力模式匹配)
在暴力字符串匹配过程中,我们会从S[0] 跟 T[0] 匹配,如果相等则匹配下一个字符,直到出现不相等的情况,此时我们会简单的丢弃前面的匹配信息,然后从S[1] 跟 T[0]匹配,循环进行,直到主串结束,或者出现匹配的情况。这种简单的丢弃前面的匹配信息,造成了极大的浪费和低下的匹配效率。
/*
* 朴素模式匹配算法
*/
int match(char* s,char* t)
{
int j=0;
int i=0;
int *next=getNext(t);
while(t[i]!=' '&&s[j]!=' ')
{
if(s[j]==t[i])
{
i++;j++;
}
else {
j=j-i;
i=0;
}
}
if(t[i]==' ')
{ return j-i;}
else
{ return -1;}
}
2)KMP模式匹配算法
在KMP算法中,对于每一个模式串我们会事先计算出模式串的内部匹配信息,在匹配失败时最大的移动模式串,以减少匹配次数。
已知空格与D不匹配时,前面六个字符"ABCDAB"是匹配的,个数为6个。查表可知,字符D对应的部分匹配值"为2,因此按照下面的公式算出向后移动的位数:
移动位数 = 已匹配的字符数 - 对应的部分匹配值
因为 6 - 2 等于4,所以将搜索词向后移动4位。
10、
因为空格与C不匹配,搜索词还要继续往后移。这时,已匹配的字符数为2("AB"),C 对应的"部分匹配值"为0。所以,移动位数 = 2 - 0,结果为 2,于是将搜索词向后移2位。
11、
因为空格与A不匹配,已匹配的字符数为0,A对应的"部分匹配值"为-1,移动位数=0-(-1),结果为1,继续后移一位,,这也解释了第1,2步骤中往后移动1位的原因。
12、
逐位比较,直到发现C与D不匹配。于是,移动位数 = 6 - 2,继续将搜索词向后移动4位。
13、
逐位比较,直到搜索词的最后一位,发现完全匹配,于是搜索完成。如果还要继续搜索(即找出全部匹配),移动位数 = 7 - 0,再将搜索词向后移动7位,这里就不再重复了。
下面介绍《部分匹配表》是如何产生的。
索引 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
搜索词 | A | B | C | D | A | B | D |
next[j] | -1 | 0 | 0 | 0 | 0 | 1 | 2 |
移动的位数 | 1 | 1 | 2 | 3 | 4 | 4 | 4 |
求next函数的过程是一个递推的过程:
1.首先由定义得next[0]=-1,next[1]=0;
2.假设已知next[j]=k,又T[j] = T[k],则显然有next[j+1]=k+1;
3.如果T[j]!= T[k],则令k=next[k],直至T[j]等于T[k]为止。
注:
1.虽然next定义中没有明确指出next[1]=0,但由0<k<j的条件很容易判断出next[1]只能等于0;
2.next[j]=k表明在T串中的字符T[k]之前存在一个长度最大的子串"tj-ktj-k+1…tj-1"和T串中的子串 "t0t1…tk-1" 相等,而现在又知道了tj=tk,这就是说,在字符T[k+1]之前存在着一个长度最大的子串使得等式 "t0t1…tk"="tj-ktj-k+1…tj"成立,则根据next函数值的定义不就得到next[j+1]=k+1了吗?
3.由于tj!=tk,则等式"t0t1…tk"="tj-ktj-k+1…tj" 不成立,也就是说,在字符T[k+1]之前不存在一个子串" tj-ktj-k+1…tj"和子串"t0t1…tk"相等,那么是否可能存在另一个值p<k,使等式"t0t1…tp"=" tj-ptj-p+1…tj" 成立,这个p显然应该是 next[k+1],因为这相当于一个"利用next函数值进行T串和T串的匹配"问题。
编程实现求解next数组的代码:
/*
* KMP求解next数组的方法
*/
int* getNext(char * t)
{
//模式字符串长度
int len =strlen(t);
int *next=new int[len];
/*将数组元素预置为0*/
int i=0;
while(t[i]!=' ')
{
next[i]=0;
i++;
}
/*将数组元素预置为0*/
int j=0;int k=-1;
next[0]=-1;
while(t[j+1]!=' ')
{
if(k==-1||t[j]==t[k])
{
k++;j++;
next[j]=k;
}
else
{
j++;
k= next[k+1];
}
}
return next;
}
因此在朴素模式匹配算法的基础上我们通过新的移位方法可以改进得到KMP算法的实现:
/*
* KMP模式匹配算法
*/
int kmpMatch(char* s,char* t)
{
int j=0;
int i=0;
int *next=getNext(t);
while(t[i]!=' '&&s[j]!=' ')
{
if(s[j]==t[i])
{
i++;j++;
}
else {
j=j-next[i];
i=0;
}
}
if(t[i]==' ')
{ return j-i;}
else
{ return -1;}
}
终于弄清楚KMP算法的精髓啦!