zoukankan      html  css  js  c++  java
  • 字符串匹配

    字符串匹配是经常遇到的问题,比如信息检索、拼写检查,甚至是生物信息学中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 *src, char *dst)
     2 {
     3     int m = strlen(src), n = strlen(dst);
     4     int i = 0, j = 0;
     5 
     6     while (i < m && j < n)
     7     {   
     8         if (src[i] == dst[j])
     9         {   
    10             i++;
    11             j++;
    12         }   
    13         else
    14         {   
    15             if (j != 0) // j = 0, means no match at all.
    16                 i = i - match_table[j - 1]; 
    17             else
    18                 i = i + 1;
    19             j = 0;
    20         }   
    21     }   
    22 
    23     return (j == n) ? (i - j) : -1; 
    24 };

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

    15-18行,如果j = 0, 说明一次匹配都没有成功,直接i+1,匹配下一位。

      如果j > 0,则说明存在大于一个元素的匹配,执行i - match_table[j - 1];

      实际上,只要在match_table开头插入 -1, 即可实现将15-18行改为:

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

      和暴力匹配算法相比,仅仅多出了第16行,即上述提到的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的匹配表match_table.

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

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

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

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

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

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

      则m = j + 1时

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

      b)当a[j+1] != a[k]时, 令k1= mt[k].

         如果k1 < 1, 则match_table[j+1] = 0.

         否则, k1 >=1.

           由 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)进行递归比较。

       代码如下: 

    void generate_match_table(char *P, int match_table[])
        {
            int n = strlen(P);
            int j, k;
            match_table[0] = -1;
            k = match_table[0];
            
            j = 0;
            while (j < n)
            {
                if (k == -1 || P[j] == P[k])
                {
                    k++;
                    j++;
                    match_table[j] = k; 
                }
                else
                {
                    k = match_table[k];
                }
            }
        }
  • 相关阅读:
    因安装包依赖问题导致无法安装的解决办法!
    Ubuntu18.04安装qemu遇到问题-qemu : Depends: qemu-system (>= 1:2.11+dfsg-1ubuntu7)
    理解mount -t proc proc /proc
    printf "%.*s"
    Linux 内核内存分配函数devm_kmalloc()和devm_kzalloc()
    为什么 extern 使用 const 修饰的变量会编译不过?
    php openssl_sign 对应 C#版 RSA签名
    win7中用iis部署ssl服务
    找出windows系统上最大的文件
    windows 创建指定大小文件
  • 原文地址:https://www.cnblogs.com/ym65536/p/4009786.html
Copyright © 2011-2022 走看看