码迷,mamicode.com
首页 > 其他好文 > 详细

字符串匹配

时间:2014-10-07 22:05:14      阅读:142      评论:0      收藏:0      [点我收藏+]

标签:style   blog   http   color   使用   ar   for   strong   sp   

字符串匹配是经常遇到的问题,比如信息检索、拼写检查,甚至是生物信息学中DNA相关的问题。

1、比较简单的匹配算法是直接暴力匹配,算法原理:

1)取指针i,j分别指向字符串S和目标串P,如果S[i] == P[j],i和j分别自增。

2)如果不相等,i回溯到初始位置的下一个位置,即i = i - j + 1,j指向目标串首位。

代码如下:

 1 int string_index(char *S, char *P) 
 2 {
 3     int i = 0, j = 0;
 4     int m = strlen(S);
 5     int n = strlen(P);
 6 
 7     while (i < m && j < n)
 8     {   
 9         if (S[i] == P[j])
10         {   
11             i++;
12             j++;
13         }   
14         else
15         {   
16             i = i - j + 1;
17             j = 0;
18         }   
19     }
20 
21     return ((j == n) ? (i - j): -1);
22 }

  举一个简单的例子(取自wiki:http://zh.wikipedia.org/wiki/%E5%85%8B%E5%8A%AA%E6%96%AF-%E8%8E%AB%E9%87%8C%E6%96%AF-%E6%99%AE%E6%8B%89%E7%89%B9%E7%AE%97%E6%B3%95)

S: ABC ABCDAB ABCDABCDABDE

P: ABCDABD

  前三次匹配S[0] = P[0],...,S[2] = P[2], 到第四次,S[3] != P[3],此时i = i - j + 1 = 1, j = 0, 依次往下进行。

  最坏情况下,i等于0,1,2,...,n时,目标串均需匹配m次,算法时间复杂度O(mn).

2、KMP算法  

  KMP算法是由Knuth、Morris和Pratt三人于1977年联合提出的字符串匹配算法,其中Knuth就是大名鼎鼎的The Art of Computer Programming的作者。

和直接匹配相比,kmp算法利用已知信息,减少了回溯次数。

  回到刚才的字符串S和P。当i = 10,即i指向S中第11个元素时,S[10] != P[7],i = i - j + 1 = 4, j = 0. i此时递增了1,再次匹配时,匹配肯定是失败的,因为我们已经知道了S中“ABC ABCDAB ABCDABCDABDE”第4-9位为ABCDAB,只要直接将i移动到第8位的A上就可以了。这里主要用到ABCDAB的前缀AB和后缀AB匹配。根据目标串P,我们可以构建一个前后缀匹配表T1,如下(构造过程后文分析):

字符串 A B C D A B D
匹配值 0 0 0 0 1 2 0

  根据匹配值,我们就可以得到字符串S中i的移动位数计算方法:

移动位数 = 匹配字符数 - 表T1中的匹配值

  相应的算法代码:

 1 int string_kmp(char *S, char *P)
 2 {
 3     int i = 0, j = 0;
 4     int m = strlen(S);
 5     int n = strlen(P);
 6 
 7     while (i < m && j < n)
 8     {
 9         if (S[i] == P[j])
10         {
11             i++;
12             j++;
13         }
14         else
15         {
16             i = i - j + 1;
17             if (j != 0)  /* j = 0, 表明没有匹配,忽略 */
18             {  /* 此处j - 1是因为第16行已经默认加了1 */
19                 i += (j - 1) - mt[j - 1];  
20                 j = 0;
21             }
22         }
23     }
24 
25     return ((j == n) ? (i - j): -1);
26 }

  其中mt记为刚才构造的匹配表T1。mt = {0, 0, 0, 0, 1, 2, 0}.

  和暴力匹配算法相比,仅仅多出了第19行,即上述提到的i的移动位次计算。那么,mt表是怎样计算出来的呢?可以采用枚举法:

  - "A"的前缀和后缀都为空集,共有元素的长度为0;

  - "AB"的前缀为[A],后缀为[B],共有元素的长度为0;

  - "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;

  - "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;

  - "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;

  - "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;

  - "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。

  由此到底P的匹配表mt.

  根据上述分析,得到匹配表实际上是对任意字符串a[0]a[1]...a[m-1],构造一张表mt[m], 对于任意j,mt[j-1]对应最大的k,使得

a[0]a[1]...a[k-1] = a[j - k]...a[j-1]

简记为a[0, k-1] = a[j-k, j-1]

  对于构造表mt,我们可以使用数学归纳法,假设k=mt[j], 即a[0, k] = a[j-k, j]:

1) j = 0时,a0的前后缀均为空集,显然mt[j] = 0.

2) 当m = j(j >=1) 时,已知a[0, k-1] = a[j-k, j-1].

  a)当a[j] = a[k]时,mt[j] = mt[j - 1] + 1;

  b)当a[j] != a[k]时,比较a[j]和a[k1], k1= mt[k].

   由k1 = mt[k] => a[0, k1-1] = a[k-k1, k-1]

    k = mt[j] => a[0, k-1] = a[j-k, j-1]

   可得a[0, k1-1] = a[j-k1, j-1],于是可以回到步骤2)进行递归比较。

   代码如下: 

 1 void compute_mt(char *P, int mt[])
 2 {
 3     int j, k = 0;
 4     int m = strlen(P);
 5 
 6     mt[0] = 0;
 7     for (j = 1 ; j < m; j++)
 8     {   
 9 label:
10         if (P[k] == P[j])
11         {   
12             mt[j] = mt[j - 1] + 1;
13         }   
14         else if (k > 0)
15         {   
16             k = mt[k];
17             goto label;
18         }   
19 }

考虑到代码可读性,用while循环取代goto语句:

 1 void compute_mt(char *P, int mt[])
 2 {
 3     int j, k = 0;
 4     int m = strlen(P);
 5 
 6     mt[0] = 0;
 7     for (j = 1 ; j < m; j++)
 8     {
 9         while ((k > 0) && (P[k] != P[j]))
10             k = mt[k];
11         if (P[k] == P[j])
12             k++;
13         mt[j] = k;
14     }
15 }

字符串匹配

标签:style   blog   http   color   使用   ar   for   strong   sp   

原文地址:http://www.cnblogs.com/ym65536/p/4009786.html

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