1、 BF算法
这是最简单直观的模式匹配算法。子串(T)从主字符串(S)的指定位置(假定为开头)开始对比,假设i和j分别为主串(S)和子串(T)中下一个位置。
如果S[i].ch和T[j].ch相等,则两个字符串里的i和j都加1,也就是指示串中的下一个位置,并继续进行比较;
如果S[i].ch和T[j].ch不相等,则指针直接退回主串的下一个字符(i=i-j+2)再重新和子串的第一个字符(j=1)比较。
上述解释中比较难理解的应是i=i-j+2,i表示主串的移动距离,j表示子串的移动距离,在未出现S[i].ch和T[j].ch不相等时,i与j应该相等,所以i-j也就是退回到i开始的第0个字符,而加上2也就等价于将i移动到i最初开始的第2个字符,因为文中是以1表示i开始的字符,而第2个字符也就等价于i开始字符之后的字符。
比如说主串为abcdefghi,子串为abcdf,则当i=j=5时,S[5].ch和T[5].ch不相等,则将i退回开始字符的之后字符(即第2个字符)。
主串 子串 主串 子串
a b c de f g h I a b c d f a b a b c a b c a c b a b a b c a c
1 2 3 4 5 67 8 9 1 2 3 4 5 1 2 3 4 5 6 7 8 9 10 11 1213 1 2 3 4 5
第一次匹配: a b c d e f g h I a b a b c a b c a c b a b
i=5 i=3
a b c d f a b c a c
j=5 j=3
第二次匹配: a b c d e f g h I a b a b c a b c a c b a b
i=2 i=2
a b c d f a b c a c
j=1 j=1
第三次匹配: a b c d e f g h I a b a b c a b c a c b a b
i=3 i=7
a b c d f a b c a c
j=1 j=5
. .
. .
. .
第九次匹配: a b c d e f g h I 第6次匹配:a b a b c a b c a c b a b
i=9 i=11
a b c d f a b c a c
j=1 j=6
可以说,BF算法比较粗暴直观,它直接从第1字符开始一个一个字符地比较主串和子串的字符,如果不一样就回溯到开始的第2个字符(也就是i-j+2)开始比较,而子串是回到其第1个字符(即j=1),但其时间复杂度比较高。
# include
# include
int Wf_Bf(char *Smain,char * Sson, int pos,int Smain_len, int Sson_len)
{
int i = 0;
int j = 0;
if(0==Smain_len || 0==Sson_len){
#if 0
int Smain_len = strlen(Smain);
int Sson_len = strlen(Sson);
#endif
Smain_len = strlen(Smain);
Sson_len = strlen(Sson);
}
if(NULL==Smain || NULL == Sson )
return -1;
if(0
Sson_len){
return i - Sson_len;
}
else {
return 0;
}
#endif
}
int main()
{
#if 1
char *Smain = "ababcabcacbab";
char *Sson = "abcac";
#else
char *Smain = "abcdefghI";
char *Sson = "abcdf";
#endif
int flag = Wf_Bf(Smain,Sson,0,0,0);
if(-1==flag)
printf("fail to match\n");
else
printf("match successfully, the address is %c\n", Smain[flag]);
return 0;
}
2.KMP算法
KMP算法是由Knuth、Morris和Pratt同时设计实现,故简称为KMP算法,但我觉得其可简称为 看毛片算法 ,让人印象更加深刻。
看BF算法,可以发现其主串的i指针总是反复地回溯到开始的地址,这会花费很多的时间,而KMP便不会回溯i指针。
现介绍一下算法的思想:假设主串为:“s1s2...sn”,子串为“t1t2...tm”,为了避免主串i的重复回溯,KMP便得解决当主串第i个字符与子串第j个字符不相等时,主串中第i个字符应与子串中哪个字符进行比较?
假设此时应与子串中第k(k<j)个字符继续比较,则子串中前k-1个字符的子串必须满足下式,且不可能存在k’>k满足下式:
“t1t2...tk-1”= “si-k+1si-k+1...si-1”
当对比到第k个字符时,子串中的前k-1个字符应与主串中从i往前数k个字符是一样的,而已经得到的“部分匹配”的结果是
“tj-k+1t j-k+2...tj-1”= “si-k+1si-k+1...si-1”
而从子串的j-1处可以知道子串中的前k-1个字符应与主串中从i往前数k个字符是一样的,所以可以得出:
“t1t2...tk-1”= “tj-k+1t j-k+2...tj-1”
从上述公式便可以知道,想要求出k的具体数值,便是求出子串的对应长度(k)内符合上述公式的字符长度。因此可以导出下述公式:
根据上述公式可以求得next[j]的数值,用文字描述如下:
假设以指针i和j分别指示主串和子串中正待比较的字符,令i的初值为pos,j的初值为1。若在匹配工程中si=tj,则i和j分别增1,否则i不变,j退到next[j]的位置再比较,若相等,则指针各自增1,否则j再退到下一个next值的位置再比较,依次类推,直至下列两种可能:
一种是j退到next值(next[next[...next[j]...]])时字符比较相等,则指针各自增1,继续进行匹配;
另一种是j退至值为0(即模式的第一个字符“失配”),则此时需将子串继续向右滑动一个位置,即从主串si+1起和子串重新开始匹配。
j | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
子串 | a | b | a | a | b | c | a | c |
next[j] | 0 | 1 | 1 | 2 | 2 | 3 | 1 | 2 |
通过上述便可获得KMP。
然后便可进行KMP算法:开始对主串和子串进行匹配,当匹配过程中产生“失配”时,指针i不变,指针j退回至next[j]所指示位置重新进行比较,如果不匹配,则指针退回至next[next[j]]进行对比...直到指针j退至0时,指针i和指针j便需同时增1,也就是说如果主串的第i个字符和子串的第1个字符不等,则从主串的第i+1个字符起重新匹配。
综上所述,可以发现KMP的关键点是next[j]的获取。
由定义可知,
当j为1时,next[1] = 0
假设next[j]= k,则说明子串中存在
“t1t2...tk-1”= “tj-k+1t j-k+2...tj-1”
其中1<k<j,且不可能存在k’>k,而求next[j+1]可分为两个步骤:
(1) 如果 tk = tj,则表示存在
“t1t2...tk-1 tk” = “tj-k+1tj-k+2...tj-1 tj”
因此可求得
next[j+1] = k+1,即next[j+1] = next[j] +1
(2)如果tk!= tj,便需要把求next值的问题看成是一个子串匹配的问题,整个子串既是主串又是子串,而在匹配过程中,t1= tj-k+1, t2= tj-k+2,,。。。, tk-1 = tj-1,则当时将子串向右滑动直到子串中第next[k]个字符和主串中第j个字符相比较。直到有一个next[k] = k’,且tk’= tj则说明在主串中第j+1个字符前存在一长度为k’(即next[k])的最长子串,和子串中从首字符起长度为k’的子串相等,即
“t1t2...tk’-1”= “tj-k’+1t j-k’+2...tj-1” (1<k’<k<j)
也就等价于next[j+1]=k+1,即
Next[j+1]= next[k] +1
与之类似的,如果tk’!= tj,则继续将子串右滑动与第next[next[k’]]字符进行匹配,若相等则+1处理,若不等继续下去直到next[...]的值为0,那时next的值便是1。
j | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 |
子串 | a | b | a | a | b | c | a | c |
next[j] | 0 | 1 | 1 | 2 | 2 | 3 | 1 | 2 |
这里以next[7]的值获取为例子对next[j]的求取进行说明。
第一遍: 1 2 3 4 5 6 7 8 j
a b a a b c a c 子串
a b a 子串的子串
获取next[7]便需对比t6和tnext[6]的数值,因为a!=c,所以子串向右滑动值next[3](也就是next[next[6]])
第二遍:1 2 3 4 5 6 7 8 j
a b a a b c a c 子串
a 子串的子串
因为t6!=t1,其中1的值为next[next[6]] = 1,而next[1] =0,所以next[7] = 1
小结:KMP算法就是主串的指针i只增不减,当i所指的字符与子串指针j所指的字符不等时,就将子串指针j的值改为next[j](也就是将指针向左移,将字符串整体向右移),再进行对比,直到i所指的值与j所指的值相等或者是next[j]的值为0,然后i+1。
因此,KMP的关键点是next[j]值的获取,而next[j]值便是通过对子串进行再一次的KMP算法的处理,即子串既是主串又是子串,
简单说:求next[j]时,先对比tj-1和tk的值(其中k=next[j-1]),
如果相等,则next[j] = k +1=next[j-1] +1;
如果不等,则再对比tj-1和tk的值(此时k=next[next[j-1]])
如果相等,则next[j] = k+1
如果不等,则再对比tj-1和tk的值(此时k=next[ next[ next[j-1] ] ])
.....................................................................
如此重复,直到next[...]的值为0或者存在一个k,使得tj-1和tk想等。
#include
#include
#include
#include
/*实现get—next*/
#if 0
int * Wf_Kmp_next(char *Sson,int Sson_len)
{
int i = 0;
int j = -1;
if(0==Sson_len){
Sson_len = strlen(Sson);
}
int * next = (int*)calloc(sizeof(int),Sson_len);
next[0] = -1;
while(i<(Sson_len-1)){
if(-1==j || Sson[i]==Sson[j]){
j++;
i++;
next[i] = j;
}
else
j = next[j];
}
return next;
}
#else
/*实现get_nextval*/
int * Wf_Kmp_next(char *Sson,int Sson_len)
{
int i = 0;
int j = -1;
if(0==Sson_len){
Sson_len = strlen(Sson);
}
int * next = (int*)calloc(sizeof(int),Sson_len);
next[0] = -1;
while(i<(Sson_len-1)){
if(-1==j || Sson[i]==Sson[j]){
j++;
i++;
if(Sson[i]!=Sson[j])
next[i] = j;
else next[i] = next[j];
}
else
j = next[j];
}
return next;
}
#endif
int Wf_Kmp(char *Smain, char *Sson,int pos, int Smain_len, int Sson_len)
{
int i = 0;
int j = 0;
int * next = Wf_Kmp_next(Sson,Sson_len);
if(NULL==Smain || NULL==Sson)
return -1;
if(0==Smain_len || 0==Sson_len){
Smain_len = strlen(Smain);
Sson_len = strlen(Sson);
}
if(0<=pos && pos <= Smain_len){
i = pos;
j = 0;
}
else
return -1;
// while(i
(Sson_len-1)) return i-j; else return 0; } int main(void) { char *Smain = "acabaabaabcacaabc"; char *Sson = "abaabcac"; #if 1 int flag = 0; flag = Wf_Kmp(Smain,Sson,0,0,0); printf("the flag is %d, the c is %c\n",flag,Smain[flag]); #else int * next = Wf_Kmp_next(Sson,0); int i = strlen(Sson); for(int j=0;j
PS:需要留意的是算法介绍里串的初始字符是1开始计数的,但在C中,其计数是以0开始的,所以0值最好改为-1。