您的位置:首页 > 其它

KMP算法--字符串模式匹配算法

2011-07-25 22:29 375 查看
  今天看到第四章《》了,其中我觉得花的时间多一点的值得我写篇随笔的知识点就是:4.3 串的模式匹配算法;书上介绍了两种字符串匹配算法,一种是最简单最容易想到的逐个字符匹配算法(时间复杂度在好的情况下为O(n+m),[n为原串,m为匹配串],在最坏的情况下为O(n*m)),这个在我的代码中有了,就不赘述了。另外一种就是KMP算法,话说是三个人同时想到的算法,这仨名字各取开头的字符就是这个算法的名称了。这个算法是模式匹配的改进算法;(时间复杂度为O(m))。

逐个字符匹配算法

int indexOf(string s,string t,int pos){//逐个字符匹配算法,从s串的pos位置开始,返回t出现的首位置
if(t.empty()){//字串不要为空
return -1;
}
int i = pos;
int temp = 0;
for(;i<s.size();i++){
temp = i;
for(int j = 0;j<t.size();j++){
if(s[temp] == t[j]){//如果相等,就继续循环下去
temp++;
if(j == (t.size()-1)){
return i;//返回出现的首位置
}
}else{//不相等
break;
}
}
}
}


  

  而这个KMP算法在大体思想就是这样:

注意: s为原串,t为模式串,即小一点的那个,后面都是一样。↑↓

↓i=0 从这儿开始比较

1.s : a c a b a a b a a b c a c a a b c

t : a b a a b c a c (红色为t在s中第一次出现)

   ↑j=0 next[0]=-1 (next[]数组的作用等会儿说哈)

↓i=1

2.s : a c a b a a b a a b c a c a a b c

t : a b a a b c a c

   ↑j=1 next[1]=0 (发现c!=b,所以t串又要从头来比较,后面的代码就如下,j = next[j]; if(j==-1){j=0;)

↓i=1

3.s : a c a b a a b a a b c a c a a b c

t : a b a a b c a c

   ↑j=1 next[1]=0 (发现c!=a,所以s串又要后退点了)

↓i=2

4.s : a c a b a a b a a b c a c a a b c

t : a b a a b c a c

   ↑j=0 next[0]=-1 (相等,好的继续)

↓i=7

5.s : a c a b a a b a a b c a c a a b c

t : a b a a b c a c

   ↑j=5 next[5]=2 (发现c!=a了,这时候就是体现算法的时候了,此时i会暂停向后移动,而t串应该从哪里开始比较呢?next数组告诉了我们答案,从t[2]开始和s串i位置开始比较,如下图)

↓i=7

6.s : a c a b a a b a a b c a c a a b c

t : a b a a b c a c

   ↑j=2 next[2]=0 (后面的移动方式我没必要画了,很明显已经移到合适的地方了,乍一看很巧,细细一想还是很有智慧的)

好,我来说说上面出现的每个参数的作用吧:s为原串,t为模式串,i为s串的移动指针,j为t串的移动指针,next数组的作用是指明t串指针j的位置的,也就是j回溯多少,当S串中的第i个字符与t串中的第j个字符不等时,s串的第i个字符(i指针永远不会往回走,最多只是暂停下)应该与模式中的哪个字符再比较的。针对每个t串,都会生成相对应的next数组,而next如何生成?

请看next特征数组构造:

模式串t开头的任意个字符,把它称为前缀子串,如t0t1t2…tm-1。在t的第i位置的左边,取出k个字符,称为i位置的左子串,即ti-k+1... ti-2 ti-1 ti。求出最长的(最大的k)使得前缀子串与左子串相匹配称为,在第i位的最长前缀串。第i位的最长前缀串的长度k就是模板串P在位置i上的特征数n[i]特征数组成的向量称为该模式串的特征向量。
可以证明对于任意的模式串t = t0t1…tm-1,确实存在一个由模式串本身唯一确定的与目标串无关的数组next,计算方法为:
(1) 求t0…ti-1中最大相同的前缀和后缀的长度k;
(2) next[i] = k;

作为特殊情况,当i=0时,令next[i] = -1;显然,对于任意i(0≤i<m),有next[i] < i;假定已经计算得到next[i], 那么next[i+1] = ? 特征数ni ( -1≤ ni ≤ i )是递归定义的,定义如下:
(1) n[0] = -1,对于i > 0的n[i] ,假定已知前一位置的特征数 n[i-1]= k ;
(2) 如果ti = tk ,则n[i] = k+1 ;
(3) 当ti ≠ tk 且k≠0时,则令k = n [k -1] ; 让(3)循环直到条件不满足;
(4) 当qi ≠ qk 且k = 0时,则ni = 0;

根据以上分析,可以得到next特征数组的计算方法,算法代码如下:

void getNext(string t,int* next){//求子字串t的next[]函数之并存入数组中
int temp = 0;
int zm = 0;
for(int i = 0; i < t.size();i++){//从0开始循环
temp = i-1;
if(i == 0){//第一个的时候,为-1
next[i] = -1;
}else if(temp > 0){
for(int j = temp;j>0;j--){
if(equals(t,i,j)){
next[i] = j;
break;
}else{
next[i] = 0;//视为其他情况为0
continue;
}

}
}else{
next[i] = 0;//其他情况为0
}

}

}

bool equals(string t,int i,int j){//比较字符串t[0]~t[j-1]同t[i-j]~t[j]是否相等
int temp = i-j;
//if((j-1)==(2*j-i)){//如果长度相同
for(int k = 0; k < j; k++,temp++){
if(t[k]!=t[temp]){//如果有不相同的,返回false
return false;
}
}
return true;//运行到这儿说明字符串t[0]~t[j-1]同t[i-j]~t[j]相等
}


  到这里了,解决了一个点了,还有一个点就是,i串既然不回溯,还是有暂停的,至于暂停总是有这样规律的:第一不匹配--停,第二次不匹配--走...针对i串的同一个字符没有第三次哦,为什么?你懂的。

说到这儿,我想KMP算法的思想应该讲完了,看起来KMP算法是要好些,通常来说,模式串t的长度比S串的小的多,对于整个匹配算法来讲,确实要改进不少,虽然第一种算法的时间复杂度是O(n*m),但是在一般的情况下,其实际执行的时间按近似于O(n+m),所以至今很多地方也有采用;

总结:KMP算法在模式串t与主串s存在许多“部分匹配”的情况下才显得速度很快,这个算法的最大特点就是,主串的指针不需要回溯,从头扫一遍就行了,这对处理庞大的数据文件很有效,也可以边读入便匹配,无需回头再重读。

忘记贴代码了,呵呵,补上:

/*
2011-7-25 zhangming
第四章,串 4.3节-串的模式匹配算法
总的来说有两种算法,
1,逐个字符匹配算法;(此匹配算法最简单,时间复杂度在好的情况下为O(n+m),[n为原串,m为匹配串],在最坏的情况下为O(n*m))
2,KMP算法;模式匹配的改进算法;(时间复杂度为O(m))
*/
#include <iostream>
#include <string>
using namespace std;
int indexOf(string s,string t,int pos);//逐个字符匹配算法,从s串的pos位置开始,返回t出现的首位置
int indexOfKMP(string s,string t,int pos);//KMP算法;模式匹配的改进算法;
void getNext(string t,int* next);//求子字串t的next函数之并存入数组中
bool equals(string t,int i,int j);//比较字符串t[0]~t[j-1]同t[i-j]~t[j]是否相等
void main(){
string s = "acabaabaabcacaabc";
string t = "abaabcac";
cout<<t<<"出现在"<<s<<"的第"<<indexOf(s,t,2)+1<<"位置。\n";
cout<<t<<"出现在"<<s<<"的第"<<indexOfKMP(s,t,2)+1<<"位置。\n";
system("pause");
}

int indexOf(string s,string t,int pos){//逐个字符匹配算法,从s串的pos位置开始,返回t出现的首位置
if(t.empty()){//字串不要为空
return -1;
}
int i = pos;
int temp = 0;
for(;i<s.size();i++){
temp = i;
for(int j = 0;j<t.size();j++){
if(s[temp] == t[j]){//如果相等,就继续循环下去
temp++;
if(j == (t.size()-1)){
return i;//返回出现的首位置
}
}else{//不相等
break;
}
}
}
}
int indexOfKMP(string s,string t,int pos){//KMP算法;模式匹配的改进算法;
if(t.empty()){//t字串不要为空
return -1;
}
int* next = new int[t.size()];
int pause = 0;//控制是否暂停的
getNext(t,next);
int i = 0,j = 0;
while(i!=s.size()){
if(s[i]==t[j]){
i++;
j++;
}else{
if(pause==1){//如果不需要暂停
pause=0;
i++;
}else{//如果需要暂停
pause=1;
}
j = next[j];
if(j==-1){
j=0;
}
}
if(j==t.size()){
return i-t.size();
}
}
return -1;
}
void getNext(string t,int* next){//求子字串t的next[]函数之并存入数组中
int temp = 0;
int zm = 0;
for(int i = 0; i < t.size();i++){//从0开始循环
temp = i-1;
if(i == 0){//第一个的时候,为-1
next[i] = -1;
}else if(temp > 0){
for(int j = temp;j>0;j--){
if(equals(t,i,j)){
next[i] = j;
break;
}else{
next[i] = 0;//视为其他情况为0
continue;
}

}
}else{
next[i] = 0;//其他情况为0
}

}

}
bool equals(string t,int i,int j){//比较字符串t[0]~t[j-1]同t[i-j]~t[j]是否相等
int temp = i-j;
//if((j-1)==(2*j-i)){//如果长度相同
for(int k = 0; k < j; k++,temp++){
if(t[k]!=t[temp]){//如果有不相同的,返回false
return false;
}
}
return true;//运行到这儿说明字符串t[0]~t[j-1]同t[i-j]~t[j]相等
}


  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: