BF算法和KMP算法的简介
2017-09-13 20:51
316 查看
BF算法和KMP算法是两种比较著名的模式匹配算法(个人觉得可以说为字符串匹配)。本文对这两种算法进行介绍。
如果S.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),但其时间复杂度比较高。
看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”
从上述公式便可以知道,想要
4000
求出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起和子串重新开始匹配。
通过上述便可获得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。
这里以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想等。
PS:需要留意的是算法介绍里串的初始字符是1开始计数的,但在C中,其计数是以0开始的,所以0值最好改为-1。
1、 BF算法
这是最简单直观的模式匹配算法。子串(T)从主字符串(S)的指定位置(假定为开头)开始对比,假设i和j分别为主串(S)和子串(T)中下一个位置。如果S.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(0Sson_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”
从上述公式便可以知道,想要
4000
求出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算法:开始对主串和子串进行匹配,当匹配过程中产生“失配”时,指针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 |
第一遍: 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[i]
PS:需要留意的是算法介绍里串的初始字符是1开始计数的,但在C中,其计数是以0开始的,所以0值最好改为-1。
相关文章推荐
- 字符串匹配算法--BF算法(暴力破解法)+KMP算法
- 【数据结构与算法】模式匹配——从BF算法到KMP算法(附完整源码)
- 字符串模式匹配(BF算法和KMP算法)
- 串匹配问题的BF算法和KMP算法
- 模式匹配——从BF算法到KMP算法(附完整源码)转
- (八)实现了串比较里的BF算法和KMP算法
- KMP算法简介
- string 之 strchr函数 和 strstr函数(BF算法和KMP算法的应用)
- 字符串查找KMP算法和BF算法
- BF算法 + KMP算法
- KMP算法简介及代码实现
- 第十三篇:模式匹配——从BF算法到KMP算法(附完整源码)
- 字符串模式匹配的BF算法与KMP算法
- BF算法及KMP算法总结
- BF算法与KMP算法
- 字符串匹配BF算法和KMP算法
- BF算法与KMP算法
- 【数据结构与算法】模式匹配——从BF算法到KMP算法(附完整源码)
- BF算法和KMP算法
- 数据结构之字符串匹配算法(BF算法和KMP算法)