BF模式匹配算法,又称朴素模式匹配算法,简单模式匹配算法,暴力匹配算法。对于字符串,现有一个主串和一个子串,那么子串在主串中的定位操作通常称为串的模式匹配。下面我们来了解一下BF匹配算法。
假设有一主串S = “goodgoogle”,子串T = “google”,那么找到子串在主串中的位置,我们直接想到的方法就是如下:
我们设主串S和子串T的下标都从0开始,i=0;j=0。
当T[j]=S[i]时,i和j均加一,也就是均往右移动一个单位,继续进行比较。
当比较到i=j=3时,T[j] != S[i]。那么子串T中j就要回溯到子串的开头,也就是j=0的位置,而主串S中的i需要回溯到比较的下一位,也就是i=i-j+1。
如果匹配成功后,返回匹配成功处串的第一个位置,如果不成功就是返回-1.
以此类推,代码如下:
int BF(char S[],char T[]) //BF算法--模式匹配
{
int i = 0;
int j = 0;
while (S[i] != '\0' && T[j] != '\0') //只要两个串不到串的末尾就一直循环
{
if(S[i] == T[j]){
i++;
j++;
}
else{
i = i-j+1; //i回溯
j = 0; //j回溯
}
}
if (S[i] == '\0'){
return -1;
}
else{
return i-j;
}
}
那么我们可以看到,在BF算法中,子串‘google’和主串‘goodgoogle’前三个字符是相同的,在i=j=3的时候匹配失败,需要回溯,因为前三个字符相同,并且子串中‘g’不等于后面的’o‘,所以此时子串中的’g‘一定不等于主串中的’o‘,但是BF算法照样进行了一次比较,这就造成了效率的下降。所以我们应该对其进行优化,就有了下面的KMP算法。同时,理解KMP算法最好从BF算法的缺陷中入手,通过何种方式进行优化。
KMP算法就是为了解决BF算法的低效问题。
上面我们说过子串‘google’和主串‘goodgoogle’前三个字符是相同的,在i=j=3的时候匹配失败,需要回溯。
但是我们知道,在子串’google‘中,’g‘不等于其后面的两个’0‘,所以上图两个比较判断其实是多余的,这也是为什么BF算法低效的原因,也是BF算法的缺陷。
我们可以直接省略上图两步,将’g‘与’d‘进行比较。
我们再讨论另一种情况:
假设S = ’abcdefgab‘,T = ’abcdex‘。我们第一次比较前五位主串字符和子串字符相等。
因为’a‘不等于’b’,‘c’,‘d’,‘e’,所以后面的四步可以省略。
有人可能会问,为什么我们要在i=5处重新进行比较呢,我们不是已经知道了’a’不等于’x’吗?首先T[5] != S[5],再者,我们虽然知道T[0] != T[5],但是我们却不知道T[0]和S[5]是否相等,所以要在i=5处比较。注意此处不同于上面讲的’goodgoodle‘,’google‘。在匹配失败处,子串中T[0] = T[3],而在
S = ’abcdefgab‘,T = ’abcdex‘中,匹配失败处,T[0] != T[5]。
由上面分析我们可以发现,主串i不进行回溯,只有子串中的j进行回溯。那么我们就要知道j应该回溯到那个位置呢?
我们需要对子串进行分析:
下面有一个数学描述:
如果想要知道证明过程,请参考[ 数据结构(C语言版)严蔚敏 吴伟民 清华大学出版社]
下图是本人查阅参考书籍进行的一些总结,若想弄清楚数学上的证明过程,则可看下图,希望对读者有一点启发。若不想,即可跳过下图,并直接学习如何根据定义来求next数组。
由上述next[j]定义可以求出子串的next数组的值。
那么对于T=‘abcabx’
求解next数组代码:
void getNext(char *T, int *next) //获得next数组的值
{
int i,j;
j = 0;
i = 1;
next[1] = 0;
while(i < T.length){
if(j == 0 || T[i] == T[j]){ //只针对子串T的比较
i++;
j++;
next[i] = j;
}
else{
j = next[j];
}
}
}
求解next数组的代码其实就是上述数学表述(证明)的代码实现
在我们知道next数组如何求后,KMP算法代码如下:
int KMP(char S[],char T[],int pos) //KMP算法
{
int i,j;
i = pos; /*i为主串S当前元素下标值若i不为1,则从pos值开始*/
j = 1; //j为子串T当前元素下标值
//若i=j=1.则在主串和子串的第一个元素比较,也即串的元素下标均从1开始
int next[255];
getNext(T,next); //获取next数组
lenS = strlen(S);
lenT = strlen(T);
while(i < lenS && j < lenT){
if(S[i] == T[i] || j = 0){
i++;
j++;
}
else{
j = next[j]
}
}
if(S[i] == '\0'){
return -1
}
else
return i-lenT
}
其中j=0的情况就是子串T中的第一个元素和主串S中对应比较的元素不相等。
并且next数组的第一个元素均为0,注意是next[1]=0。
参考书目:
数据结构 (C语言版) 严蔚敏 吴伟民 清华大学出版社
大话数据结构 程杰 清华大学出版社