串模式匹配之BF和KMP,Sunday算法

来源:互联网 发布:淘宝女装上新波度 编辑:程序博客网 时间:2024/06/03 02:32

本文简要谈一下串的模式匹配。主要阐述BF算法和KMP算法。力求讲的清楚又简洁。

一 BF算法

核心思想是:对于主串s和模式串t,长度令为len1,len2,   依次遍历主串s,即第一次从位置0开始len2个字符是否与t对应的字符相等,如果完全相等,匹配成功;否则,从下个位置1开始,再次比较从1开始len2个字符是否与t对应的字符相等。。。。

BF算法思路清晰简单,但是每次匹配不成功时都要回溯。

下面直接贴代码:

int BF_Match(char *s, char *t){int i=0,j=0;int k;while(i<StringLength(s)&&j<StringLength(t)){if(s[i]==t[j]){i++;j++;}else{i=i-j+1;j=0;}}if(j>=StringLength(t))k=i-StringLength(t)+1;//匹配成功,返回匹配位置elsek=-1;return k;}

二 KMP算法

它是一种改进的BF算法。主要消除了主串指针i的回溯,利用已经得到的部分匹配结果将模式串尽可能远的右滑再继续比较。使算法的时间复杂度有BF的O(len1*len2)变为O(len1+len2).在kmp算法中,串指针i不回溯,由模式串指针j退到某一个位置k上,使模式串t中的k之前的k-1个字符与s中的i之前的k-1个字符相等,这样可以减少匹配的次数

从而提高效率。

当是s[i]!=t[j]  表示主串s的第是s[i-j+1]----->s[i]元素   与模式串t[0]----->t[j-1]元素对应相等;这时为了尽可能右移指针,应该从主串的i-j+1到i-1个子串当中由最后一个往前寻找到

一个最大子串完全匹配。也相当于在模式串的t[0]----->t[j-1]元素中寻找k最大值使前k个元素 与后k个元素对应相等。

下面用next[]数组保存每次比较时,模式串开始的位置。

下面以主串”ababcacabcabbab“和模式串”abcab“为例进行说明

开始时,从第一个元素开始比较,如果t所有元素都匹配到,匹配成功。否则,s[i] !=  t[j],如果j==0 下一次比较位置 next[j]=-1,即从t[0]开始和s[i+1]比较。

如果k=next[j]不等于0,属于case1 则下一次比较,从t[k]与s[i]开始;如果k=0,case2,下次比较时直接从t[0]与s[i]比较 没有优化。

总之,比较过程中s的指针i一直在增加。模式串指针j可以根据上一次部分匹配结果优化。

关 于求解 next[ ]数组的几点说明:

1 求 next 数组的计算只利用了模式串

2 next 数组中的值保存的头尾相同字符串的索引位置。

简单来说 next [ j ] 表示在匹配失配的情况下即  t [ j ] != s[ i ]  时,主串 i 保持不变,不回溯,模式串匹配的位置从 j 变为 next [ j ]  , 即下一次匹配位置从next [ j ]开始。

3 next数组求解过程: 在 第 j 个元素失去匹配时,next [ j ]  = {0~j-1, 注意是j-1,之间包含首尾的相同前缀后缀}。 

关于下面 GetNext 函数的理解:

根据模式串求next[]数组用到了递归。初始next[0] = -1; 已知 next[ 0~j ] 求 next [ j+1 ]:

当已经求得 next [ j ] = k 时 ,要求 next [ j+1]  = ?

(即当 t[j] != s[i] 主串第i个位置 与 模式串 j 失去匹配时,将模式串 匹配位置回溯到第 k 个位置再次进行匹配。这句是废话,可以不看)

如果 t[0~k-1] = t[j-k-1  j-1]  如果 还有 t[ k ] = t[ j ] 表示相同前缀后缀长度还可以增加,所以 next[ j] = next[ j-1] +1 = k+1 = new_k (具体见代码)有点DP思想。

如果 t[k] != t[j]  为了寻找新的后缀下的相同前缀后缀,可以不断递归前缀索引 k = next[k],直到找到一个t[k'] = t[j] 。

那么,为何递归前缀索引k = next[k],就能找到长度更小的相同前缀后缀呢?这又归根到next数组的含义。为了寻找长度相同的前缀后缀,我们拿前缀 p0 pk-1 pk 去跟后缀pj-k pj-1 pj匹配,如果pk 跟pj 失配,下一步就是用p[next[k]] 去跟pj 继续匹配,如果p[ next[k] ]跟pj还是不匹配,则下一步用p[ next[ next[k] ] ]去跟pj匹配。相当于模式串的自我匹配,所以不断的递归k = next[k],直到要么找到长度更小的相同前缀后缀,要么没有长度更小的相同前缀后缀。(见参考文献)

附:原来next是用于s和t匹配。这里同样可以理解为模式串 t 自身的前缀 后缀匹配在第k个位置失去匹配的的位置,这也就是说为什么用 k = next [k] 寻找长度更小相同前缀后缀的原因。

 例: 模式串  为  abcdabc  在最后一个c 匹配失败时,根据 首尾 abc = abc 这时回溯到字符d 比较。



下面直接贴代码:

//KMP算法,利用部分匹配结果将模式串右移尽可能远,减少比较次数//GetNext用来保存每次比较模式串开始的位置//已知next[0~j]求next[j+1]//已知next[0~j]求next[j+1]void GetNext(char *t,int *next){int j = 0, k = -1;next[0] = -1;while(j < (strlen(t) -1)){//t[k]表示前缀 t[j]表示后缀if( k == -1 || t[j] == t[k]){//如果t[k] == t[j] 则 next[j+1] = next[j] + 1 = k + 1; j++;k++;next[j] = k;//上一句k=k+1后就指向了下一个位置!}else//如果t[k]!=t[j],用t[next[k]]去跟t[j]继续匹配,如果t[next[k]]跟t[j]还是不匹配,则下一步用//t[next[next[k]]]去跟t[j]匹配//相当于不断递归前缀索引 k = next[k],直到找到一个t[k'] = t[j]k = next[k];}}int KMP_Match(char *s,char *t){int next[maximum];int i=0,j=0;int k;GetNext(t,next);while(i<StringLength(s)&&j<StringLength(t)){if(j==-1||s[i]==t[j])//j==-1时,表示没有相同前后缀模式串平移最多,从第一个元素开始比较{i++;j++;}elsej=next[j];}if(j>=StringLength(t))k=i-StringLength(t)+1;//匹配成功,返回匹配位置elsek=-1;return k;}void main(){char *a="ababcacabcabbab";char *b="abcab";int next2[10];int i;printf("BF匹配位置:%d",BF_Match(a,b));printf("KMP匹配位置:%d",KMP_Match(a,b));GetNext(b,next2);printf("\nnext[]数组为:");for(i=0;i<5;i++)printf("%d ",next2[i]);}


BF算法最好情况下的时间复杂度为O(m) m为模式串的长度,即当主串前m个字符和模式串正好完全匹配。

BF算法最坏情况下的时间复杂度为O(m*n) ,这种情况下每次比较模式串的前m-1个字符序列和主串的相应位置总是相等,最后一个匹配失败。

KMP算法主要消除了主串指针的回溯,使算法的时间复杂度变为O(m+n)。即使最坏情况下也是达到线性复杂度。

1990年Daniel M.Sunday提出了Sunday算法。具体过程如下:

1.刚开始时,将模式串与主串左边对齐,主串和模式串开始匹配。

2.如果在某一位字符匹配失败,模式串后移 =  next (ch)   ch为主串参与匹配的下一个字符

  注:不是匹配失败的字符,模式串最后一位的下一位对应的主串字符。如 abcdefg  与abf  第一次匹配在字符 c 与 f 匹配失败,这时 ch = d  即f 下一位对应字符

如果 在模式串中没有ch  则后移位数 next ( ch ) = 模式串长度+1

否则,后移位数 next (ch) = 从右往左数出现的第一个ch字符 到 末尾距离 + 1;

3 下一次比较。

具体见代码:

  int strStr_Sunday(char *s, char *t)   {         int tlen = strlen(t);         int next[26], i, j;<span style="white-space:pre"></span> char *head = s;//指向主串         int result = -1;         int times;         for (i = 0; i<26; i++)             next[i] = tlen + 1;   //主串参与匹配下一个字符ch不在模式串中 后移 tlen+1         for (i = 0; i<tlen; i++)             next[t[i] - 'a'] = tlen - i; //主串字符ch在模式串中的该字符(最右端)后移:到最后一位的长度+1         while (strlen(head) >= tlen) //head指针不断后移,但肯定长度要>=tlen才能和 t 完全匹配的 {             if (memcmp(head, t, tlen) == 0)             {                 result = head - s;                   break;             }             else      {                 times = next[*(head + tlen) - 'a'];//*(head + plen) 为主串参与匹配的下一个字符ch                  head += times;//下一次匹配位置             }         }         return result;   }




参考文献:http://blog.csdn.net/v_july_v/article/details/7041827

 


0 0
原创粉丝点击