1.BF算法
BF算法是普通的模式匹配算法,BF算法的思想就是将目标串s的第一个字符与模式串t的第一个字符进行匹配,若想等,则比较s的第二个字符和t的第二个字符;若不想等,则比较s的第二个字符和t的第一个字符。如此一直比较下去,直到得出最后的匹配结果。
在比较过程中每次匹配失败都要将标记s串当前比较位置的指针i回溯到最初开始比较的位置,然后后移一位,而目标串则要第一个字符开始。通过对比较过程进行分析可知,其中有些比较是多余的,可以避免。
2.KMP算法
KMP算法与BF算法的区别在于KMP算法巧妙的消除了指针i的回溯问题,指针i不需要移动,只需要移动模式串t中下次需要匹配的位置j即可,使问题的时间复杂度降低。
假定目标串为S[i],其中0 <= i <= n;模式串为T[j],其中0 <= j <= m。
假设当前匹配到如下位置:
S0,S1,S2…
Si-j,Si-j+1…………..Si-1,
Si,Si+1….Sn
T0,T1…………………Tj-1,
Tj……
其中S和T中的红色部分匹配成功,恰好到Si和Ti的时候匹配失败,如果要保持i不变,同时要让模式串T相对于原始串S右移的话,可以更新j的值,让Si和新的Tj进行匹配,假设新的j用next[j]表示,即让Si和Tnext[j]匹配,显然新的j值要小于之前的j值,模式串才会是右移的效果,也就是说应该有next[j] <= j -1。那新的j值也就是next[j]应该是多少呢?观察如下的匹配:
(1)如果模式串右移1位(从简单的思考起,移动一位会怎么样),即next[j] = j – 1, 即让绿色的Si和Tj-1匹配 (注:省略号为未匹配部分)
S0,S1,S2,…,
Si-j,Si-j+1……………,Si-1,
Si, Si+1,….,Sn
T0,T1,…………………,Tj-1,
Tj, ………. (T的划线部分和S划线部分相等【1】)
T0,T1,……………..Tj-2,
Tj-1, ……. (移动后的T的划线部分和S的划线部分相等【2】)
根据【1】【2】可以知道当next[j] =j -1,即模式串右移一位的时候,有T[0 ~ j-2] == T[1 ~ j-1],而这两部分恰好是字符串T[0 ~j-1]的前缀和后缀,也就是说next[j]的值取决于模式串T中j前面部分的前缀和后缀相等部分的长度(好好揣摩这两个关键字概念:前缀、后缀)。
(2)如果模式串右移2位,即next[j] = j – 2, 即让绿色的Si和Tj-2匹配
S0,S1,…,
Si-j,Si-j+1,Si-j+2……………,Si-1,
Si, Si+1,….,Sn
T0,T1,T2,…………………,Tj-1,
Tj, ……….(T的划线部分和S划线部分相等【3】)
T0,T1,……………,Tj-3,
Tj-2,………(移动后的T的划线部分和S的划线部分相等【4】)
同样根据【3】【4】可以知道当next[j] =j -2,即模式串右移两位的时候,有T[0 ~ j-3] == T[2 ~ j-1]。而这两部分也恰好是字符串T[0 ~j-1]的前缀和后缀,也就是说next[j]的值取决于模式串T中j前面部分的前缀和后缀相等部分的长度。
(3)依次类推,可以得到如下结论:当匹配失败的情况下,j的新值next[j]取决于模式串中T[0 ~ j-1]中前缀和后缀相等部分的长度, 并且next[j]恰好等于这个最大长度。因此,KMP算法的关键工作就是求得next[]数组,而且该数组只和模式串T有关,是模式串自身的特性。
3.Next数组
在KMP算法中,为了确定在匹配不成功时,下次匹配时j的位置,引入了next[]数组,next[j]的值表示P[0…j-1]中最长后缀的长度等于相同字符序列的前缀。对于next[]数组的定义如下:
(1) next[j] = -1,其中j = 0时。
(2) next[j] = max(k),其中0<k<j P[0…k-1]=P[j-k,j-1]
(3) next[j] = 0 其他情况
如:
P a b a b a
j 0 1 2 3 4
next -1 0 0 1 2
即next[j]=k>0时,表示P[0…k-1]=P[j-k,j-1]
KMP算法的思想就是:在匹配过程称,若发生不匹配的情况,如果next[j]>=0,则目标串的指针i不变,将模式串的指针j移动到 next[j]的位置继续进行匹配;若next[j]=-1,则将i右移1位,并将j置0,继续进行比较。
KMP算法的关键在于求算next[]数组的值,即求算模式串每个位置处的最长后缀与前缀相同的长度, 而求算next[]数组的值有两种思路,第一种思路是用递推的思想去求算,还有一种就是直接去求解。
(1)按照递推的思想求解next数组:
根据定义next[0]=-1,假设next[j]=k, 即P[0…k-1]==P[j-k,j-1]
1)若P[j]==P[k],则有P[0..k]==P[j-k,j],很显然,next[j+1]=next[j]+1=k+1;
2)若P[j]!=P[k],则可以把其看做模式匹配的问题,即匹配失败的时候,k值如何移动,显然k=next[k]。
(2)直接根据定义求解next数组
4.时间复杂度分析
BF算法的时间复杂度:在最坏的情况下,BF算法要将目标串的每一个字符同模式串进行比较一遍,假定目标串长度为m,模式串长度为n,总的时间复杂度为O(m*n)。而对于KMP算法,进行比较的时间复杂度为O(m+n),求next数组的时间复杂度为n,总体时间复杂度为O(m+2n)。
5.源代码
#include <iostream>
#include <string>
#include <cstdlib>
#include <sys/time.h>
using namespace std;
const int X = 30000, Y = 20;
void getNext_1(string t ,int next[]);
void getNext_2(string t ,int next[]);
bool equals(string str, int i, int j);
//1.进行模式匹配的BF算法
int BF_Match(string s, string t){
int slen = s.size();
int tlen = t.size();
if(slen >= tlen){
for(int k = 0; k <= slen - tlen; k++){
int i = k, j = 0;
while(i < slen && j < tlen && s[i] == t[j]){
i++;
j++;
}
if(j == tlen)
return k;
else
continue;
}
}
return -1;
}
//2.1进行模式匹配的KMP算法(调用getNext_1)
int KMP_Match_1(string s, string t){
int slen = s.size();
int tlen = t.size();
if(slen >= tlen){
int i = 0, j = 0;
int next[Y];
getNext_1(t, next);
while(i < slen){
if(j == -1 || s[i] == t[j]){
i++;
j++;
}else{
j = next[j];
}
if(j == tlen)
return i - tlen;
}
}
return -1;
}
//2.2进行模式匹配的KMP算法(调用getNext_2)
int KMP_Match_2(string s, string t){
int slen = s.size();
int tlen = t.size();
if(slen >= tlen){
int i = 0, j = 0;
int next[Y];
getNext_2(t, next);
while(i < slen){
if(j == -1 || s[i] == t[j]){
i++;
j++;
}else{
j = next[j];
}
if(j == tlen)
return i - tlen;
}
}
return -1;
}
//3.1用递推法求目标串对应的NEXT数组
void getNext_1(string t ,int next[]){
int j, k;
next[0] = -1;
j = 0;
k = -1;
while(j < t.size() - 1){
if(k == -1 || t[j] == t[k]){
j++;
k++;
next[j] = k;
}else{
k = next[k];
}
}
}
//3.2直接求解目标串对应的NEXT数组
void getNext_2(string t, int next[]){
int i, j, tmp;
next[0] = -1;
next[1] = 0;
for(i = 2; i < t.size(); i++){
tmp = i - 1;
for(j = tmp; j > 0; j--){
if(equals(t, i, j)){
next[i] = j;
break;
}
}
if(j == 0)
next[i] = 0;
}
}
bool equals(string str, int i, int j){
int k = 0;
int s = i - j;
for(k = 0; k <= j - 1 && s <= i - 1; k++, s++){
if(str[k] != str[s])
return false;
}
return true;
}
//封装的不同的find函数
int find_1(string x, string y){
return BF_Match(x, y);
}
int find_2(string x, string y){
return KMP_Match_1(x, y);
}
int find_3(string x, string y){
return KMP_Match_2(x, y);
}
char* rand_string(char *str, int len){
int i = 0;
srand((unsigned)time(NULL));
for(i = 0; i < len; i++)
str[i] = rand()%26 + 'a';
str[i] = '\0';
return str;
}
long getCurrentTime(){
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}
int main(){
string x = "afafafafafafaffadadadadad";
string y = "dadadadad";
/*
char str_1[X+1];
rand_string(str_1, X);
string x(str_1);
sleep(1);
*/
/*
char str_2[Y+1];
rand_string(str_2, Y);
string y(str_2);
*/
cout << "string x = " << x << endl;
cout << " 字符串长度为:" << x.length() << endl;
cout << "string y = " << y << endl;
cout << " 字符串长度为:" << y.length() << endl;
long time_1 = getCurrentTime();
int pos_1 = find_1(x, y);
long time_2 = getCurrentTime();
cout << "1.匹配结果" << pos_1;
cout << " 用时:";
cout << time_2 - time_1 << " ms;" << endl;
long time_3 = getCurrentTime();
int pos_2 = find_2(x, y);
long time_4 = getCurrentTime();
cout << "2.匹配结果" << pos_2;
cout << " 用时:";
cout << time_4 - time_3 << " ms;" << endl;
long time_5 = getCurrentTime();
int pos_3 = find_3(x, y);
long time_6 = getCurrentTime();
cout << "3.匹配结果" << pos_3;
cout << " 用时:";
cout << time_6 - time_5 << " ms;" << endl;
}