码迷,mamicode.com
首页 > 编程语言 > 详细

串的应用--模式匹配算法

时间:2016-07-12 12:20:30      阅读:519      评论:0      收藏:0      [点我收藏+]

标签:

朴素的模式匹配算法

子串的定位操作通常称为串的模式匹配,是串中最重要的操作之一。

假设要从下面的主串S=”goodgoogle”中,找到T=”google”这个子串的位置,通常通过以下几个步骤:

  1. 主串S第一位开始,S和T前三个字母都匹配成功,但S第四个字母是d而T的是g。第一位匹配失败。
  2. 主串S第二位开始,主串S首字母是o,要匹配的T首字母是g,匹配失败。
  3. 主串S第三位开始,主串首字母是o,要匹配的T首字母是g,匹配失败。
  4. 主串S第四位开始,主串S首字母是d,要匹配的T首字母是g,匹配失败。
  5. 主串S第五位开始,S与T,6个字母全匹配,匹配成功。

简单来说,就是对主串的每个字符作为子串开头,与要匹配的字符串进行匹配。对主串做大循环,每个字符开头做T的长度的小循环,知道匹配成功或全部遍历完成为止。之前使用串其他操作实现模式匹配(Index方法),现不用串其他操作,使用基本的数组实现,假设主串S和要匹配的子串T的长度均保存在S[0]和T[0]中,具体代码如下:

/*返回子串T和主串S中第pos个字符之后的位置。若不存在,则函数返回值为0*/
/*T非空,1<=pos<=StrLength(S)*/
int Index(String S,String T,int pos){
   int i=pos; //i-主串S中当前位置下标,若pos不为1,从pos位置开始匹配
   int j=1; //j用于子串T中当前位置的下标值
   while(i <= S[0] && j <= T[0]){ //i小于S长度且j小于T长度时循环
      if(S[i]==S[j]){ //两字母相同时,继续循环,向后移动
         ++i;
         ++j;
      }else{ //俩字母不同时,i退回到上次匹配首位的下一位,j重置为1
         i=i-j+2;
         j=1;
      }
   }
   if(j>T[0]) return i-T[0];  //若j比T的长度大时,返回首位,否则返回0
   else return 0;
}

小结:最好情况的时间复杂度为 O(n+m) ,最坏情况的时间复杂度为 O[(n?m+1)?m] 。所以,这个算法太低效了。

KMP模式匹配算法

算法原理

对于所有在子串中有与首字符相等的字符,是可以省略一部分不必要的判断步骤。朴素的模式匹配中有很多重复的遍历步骤,主串的i是需要不断回溯来完成,而分析发现,这种回溯可以是不需要的,而KMP模式匹配算法就是避免这种不必要的回溯发生。

既然i值不回溯,也就是不能变小,要考虑的变化就是j值,观察发现,T串的首字符与自身后面字符比较,发现如果有相等字符,j值的变化会不同,即,j值的变化与主串没什么关系,关键取决于T串的结构是否有重复的问题。

j值得多少取决于当前字符之前的串的前后缀的相似度。

T串各位置的j值变化定义为一个数组next,那么next的长度就是T串的长度,函数定义如下:

next[j]=0,当 j=1 时;
next[j]=Max {k|1<k<jp1...pk?1=pj?k+1...pj?1} 当此集合不为空时;(有重复子串)
next[j]=1,其他情况

next数组值推导

举一些具体的例子:

eg1:T=”abcdex“

j 123456
模式串T abcdex
next[j] 011111

1)当j=1时,next[1]=0;
2)当j=2时,j由1到j-1就只有字符’a’,属于其他情况 next[2]=1;
3)当j=3时,j由1到j-1串是’ab’,显然’a’与’b’不相等,属于其他情况,next[3]=1;
4)以后同理,所以最终T串的next[j]为011111。

eg2:T=”abcabx“

j 123456
模式串T abcabx
next[j] 011123

1)当j=1时,next[1]=0;当j=2时,同上例说明, next[2]=1;当j=3时,同上,next[3]=1;当j=4时,同上,next[4]=1;
2)当j=5时,此时j由1到j-1的串是’abca’,前缀字符’a’与后缀字符’a’相等,所以k值为2,因此next[5]=2;
3)当j=6时,此时j由1到j-1的串是’abcab’,前缀字符’ab’与后缀字符’ab’相等,所以next[6]=3。

eg3:T=”ababaaaba“

j 123456789
模式串T ababaaaba
next[j] 011123

1)当j=1时,next[1]=0;当j=2时,同上next[2]=1;当j=3时,同上,next[3]=1;
2)当j=4时,j由1到j-1的串是’aba’,前缀字符’a’与后缀字符’a’相等,所以next[4]=2;
3)当j=5时,此时j由1到j-1的串是’abab’,前缀字符’ab’与后缀字符’ab’相等,所以k值为3,因此next[5]=3;
4)当j=6时,此时j由1到j-1的串是’ababa’,前缀字符’aba’与后缀字符’aba’相等,所以next[6]=4;
5)当j=7时,此时j由1到j-1的串是’ababaa’,前缀字符’a’与后缀字符’a’相等,所以next[7]=2;
6)当j=8时,此时j由1到j-1的串是’ababaaa’,前缀字符’a’与后缀字符’a’相等,所以next[8]=2;
7)当j=9时,此时j由1到j-1的串是’ababaaab’,前缀字符’ab’与后缀字符’ab’相等,所以next[9]=3。

KMP模式匹配算法实现

具体代码如下:

/*通过计算返回子串Tnext数组,计算出当前要匹配的串Tnext数组*/
void get_next(String T,int *next){
   int i=1,j=0;
   next[1]=0;
   while(i<T[0]){ /*此处T[0]表示串T的长度,遍历子串T*/
      if(j==0 ||T[i]==T[j]){  
         /*T[i]表示后缀的单个字符;T[j]表示前缀的单个字符*/
         ++i;++j;next[i]=j;
      }else  j=next[j];  //若字符不相同,则j值回溯
   }
}

/*返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回值为0*/
/*T非空,1<=pos<=StrLength(S)*/
int Index_KMP(String S,String T,int pos){
   int i=pos;  //i用于指示主串S当前位置下标,若pos不为1,从pos位置开始
   int j=1;  //用于指示子串T中当前位置下标
   int next[255];  //定义next数组
   get_next(T,next); //对串T进行分析,得到next数组
   while(i<=S[0] && j<=T[0]){ //i小于S长度,j小于T长度
      if(j==0 || S[i]==T[j]){
         //相对于朴素算法增加j=0判断,俩字母相等继续
         ++i;++j;
      }else{  //匹配失败,指针退回,重新匹配,i值不变,j值由next[j]确定
         j=next[j];  //j退回合适位置,i值不变
      }
   }
   if(j>T[0]) return j-T[0];
   else return 0;
}

相比较于朴素匹配算法,改动不算大,关键在于去掉i值回溯部分。因为只涉及简单的单循环,时间复杂度为 O(m) ,由于i值不回溯,KMP效率得到提升,相比较朴素模式匹配是要好的。

注意:KMP算法仅在模式与主串之间存在很多”部分匹配”的情况下才有优势,否则量算法差异不明显。

KMP模式匹配算法改进

原理

例如当主串S=”aaaabcde”,子串T=”aaaaax”,其next数组值分别为012345。开始时,当i=5,j=5时,b与a不相等,为步骤1;因此j=next[5]=4,i=5,为步骤2;此时b与a依旧不等,j=next[4]=3,为步骤3;此时b与a依旧不等,j=next[3]=2,为步骤4;此时b与a依旧不等,j=next[2]=1,为步骤5;此时b与a依旧不等,j=next[1]=0,根据算法,i++,j++,得到i=6,j=1,为步骤6。

分析发现,其中步骤2,3,4,5是多余的判断。由于T串的第二、三、四、五位的字符均和首位”a”相等,那么可以用首位next[1]来取代与它相等的字符后续next[j]的值,所以对next函数进行改进。

假设取代的数组为nextval,具体代码如下:

/*求模式串T的next函数修正值并存入数组nextval*/
void get_nextval(String T,int *nextval){
   int i=1,j=0;
   nextval[0]=1;
      if(j==0 || T[i]==T[j]){
         ++i;++j;
         if(T[i] !=T[j]){ //若当前字符与前缀字符不同
            nextval[i]=j;  //则当前的j为nextval在i位置的值
         }else{  
            //若字符相同,则将前缀字符的nextval值赋值给nextval在i上的值
            nextval[i]=nextval[j];
         }
      }
      else  j=nextval[j]; /*若字符不相同,则j值回溯*/
   }
}

至于匹配算法,只要将“get_next(T,next)”改为“get_nextval(T,next)”即可,就不再重复讲述。

nextval数组值推导

和next值求导一样,举几个具体的例子来说明,如下:

eg1:T=”ababaaaba”,

j 123456789
模式串 ababaaaba
next[j] 011234223
nextval[j] 010104210

1)当j=1时,nextval[1]=0;
2)当j=2时,因第二字符’b’的next值为1,第一位是’a’,l两个字符不相等,所以nextval[2]=next[2]=1;
3)当j=3时,因为第三字符’a’的next值为1,所以和第一位的’a’比较得知二者相等,所以,nextval[3]=nextval[1]=0;
4)当j=4时,第四字符’b’的next值为2,所以和第二位的’b’比较得知二者相等,所以,nextval[4]=nextval[2]=1;
5)当j=5时,第五字符’a’的next值为3,第五个字符’a’和第三位的’a’相等,所以,nextval[5]=nextval[3]=0;
6)当j=6时,第六字符’a’的next值为4,第六个字符’a’和第四位的’b’不相等,所以,nextval[6]=4;
7)当j=7时,第七字符’a’的next值为2,第七个字符’a’和第二位的’b’不相等,所以,nextval[7]=2;
8)当j=8时,第八字符’b’的next值为2,第八个字符’b’和第二位的’b’相等,所以,nextval[8]=nextval[2]=1;
9)当j=9时,第九字符’a’的next值为3,第九个字符’a’和第三位的’a’相等,所以,nextval[9]=nextval[3]=0。

eg2:T=”aaaaaaaab”,

j 123456789
模式串 aaaaaaaab
next[j] 012345678
nextval[j] 000000008

1)当j=1时,nextval[1]=0;
2)当j=2时,因第二字符’a’的next值为1,第一位是’a’,l两个字符相等,所以nextval[2]=next[1]=0;
3)同理,其后均为0 ……;
9)当j=9时,第九字符’b’的next值为8,第九个字符’b’和第八位的’a’不相等,所以,nextval[9]=nextl[9]=8。

小结:改进的KMP算法,在计算next值得同时,若a位字符与它next值指向的b位字符相等,则该a位的nextval就指向b位的nextval值,如果不等,则该a位的nextval值就是它自己a位的next值。

串的应用--模式匹配算法

标签:

原文地址:http://blog.csdn.net/dengpei187/article/details/51883628

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!