第一篇随笔,开始写博客生涯。写程序这么长时间,突然发现也要总结与积累。原来想第一篇博文是关于以前写的代码研究,发现还需要整理。这样,先发表一篇关于字符串

匹配的文章。就这样啦!

字符串匹配主要是关于模式串与主串匹配的问题。关于这个问题,有很多方法。网上也有不少例子,借鉴了不少,以下就介绍下面几种算法。

(1)BF算法(常规算法)

BF算法就是最笨的算法,一个一个进行匹配。这里采用后缀匹配算法。其实与正常的BF算法想法差不多。只不过为了与第四种算法相对应,就用后缀匹配算法代替BF算法。

从网上搞些图(自己实在不想自己画图)

从后面开始进行匹配。当不匹配时,子串整体向右偏移一个单位,再与主串进行比较。从而不断进行循环,直到比较到主串最后一个数。不匹配,则返回-1。否则,返回主

串开始匹配的位置。

Source为主串,SubString为子串。

 1 int Search_Reverse(const char *Source,const char *SubString)           //后缀缀回溯比较法  (常规BF算法)---为BM算法进行铺垫
 2 {
 3   int SourceArry = strlen(Source);                                     //主串的长度
 4   int SubArry = strlen(SubString);                                     //子串的长度
 5   int pSub  ,pSour =  SubArry;                                         //定义pSub,pSour数值
 6   if(SubArry==0)
 7       return -1;
 8   while(pSour <= SourceArry)                                           //主串是否到了尽头
 9   {
10       pSub = SubArry;                                                  //初始化
11       while(SubString[--pSub]==Source[--pSour])                        //进行匹配比较
12     {
13        if(pSour < 0)  return -1;                                       //如果pSour,以子串长度为一组的主串扫描结束
14
15        if(pSub == 0) return  pSour;                                    //为0,匹配成功
16
17     }
18     pSour += (SubArry - pSub) +1 ;                                     //进行偏移,pSour值进行恢复与回溯,SubArry - pSub为以前减去的值补回
19
20   }
21
22   return -1;
23
24 }

(2)KMP算法

KMP算法可能一开始理解有点麻烦。不过,有些时候,就要想为什么用KMP匹配算法。比如有子串“abcabd",为什么要用这算法?

当主串中S[5]=“c"与子串T[5]"d"不匹配了,如果采用BF算法进行匹配,则效率偏低。采用KMP算法之后,将子串中的T[0]与主串中的S[3]对齐,再进行比较。因为在子串

中,T[0]~T[1]的字符串与T[3]~T[4]相等,那么刚才第一次匹配时,已经说明S[3]~S[4]与T[3]~T[4]相等,那么也就说明S[3]~S[4]与T[0]~T[1]相等。所以直接将S[5]与

T[2]比较,从这里开始匹配。所以在第一次匹配失败之后,决定从子串中哪个位置再开始进行比较,就是KMP算法中关于Next[]数组的设置。所以KMP算法比BF算法稍微简单一

点,因为我们对于子串做了处理,不用从有时不用从子串T[0]从头开始比较。

基于子串关于Next[]数组的处理,如下:

下标i 0 1 2 3 4 5 6 7 8 9
p(i) a b c d a a b c a b
next[i] -1 0 0 0 0 1 1 2 3 1
Next[0]设置为-1,说简单一点,就是在后面找到相匹配的最大前缀。这时,T[4]与T[0]相等。则如果在这里不匹配时,则直接T[Next[4]]与S[4]再进行比较。当在S[5]时不
匹配时,则回退到T[Next[5]]再进行比较。所以Next数组中的数字代表这个意思,即可以回溯到哪里。如果运气好,有相同字符,可以节约一定时间。至于Next数组的算法,则可
以采用迭代。

也就是对于序列

找出这样一个k,使其满足

并且要求k尽可能的大!

 1 void NextNumber(int Next[],void*S,unsigned int ElemtSize)                    //对需要查找的字符串进行预处理
 2 {
 3
 4     char *ps = (char*) malloc(ElemtSize);
 5     memcpy(ps,S, ElemtSize);
 6     int i=strlen(ps);
 7     int j = 0;                       //从头开始计数的j标识
 8     int p = 1;                       //与j进行比较的标识
 9     Next[0] = -1;                    //Next[0] = -1,Next[1] = 0;
10     Next[1] = 0;
11     while(p<i-1)                     //p<i-1,为循环限制条件
12     {
13         if(ps[p]==ps[j])
14       {
15         Next[p+1] = j+1 ;            //最主要的区别在于,Next[]为一数组,ps又为一数组,且数组之间相差为一
16         ++p;
17         ++j;
18       }
19       else if(j==0)                  //当j为0时,Next[p+1] = 0;这个有点初始化的味道。
20       {
21         Next[p+1] = 0;
22         ++p;
23
24       }
25       else
26         j = Next[j];                 //数组适当进行回退
27     }
28
29 }

Next数组处理好了,接下来就是进行字符串匹配了,即主函数。

 1 const int Max = 100;
 2 void NextNumber(int Next[],void*S,unsigned int ElemtSize);
 3 int KMP(void*T,void*S,unsigned int ElemtSizeT,unsigned int ElemtSizeS)        //T为主串,S为子串
 4 {
 5     char *Ts = (char*) malloc(ElemtSizeT);                                    //将Void做一个转换
 6     memcpy(Ts,T,ElemtSizeT);
 7     char *Ps = (char*) malloc(ElemtSizeS);
 8     memcpy(Ps,S,ElemtSizeS);
 9     int Next[Max];
10     NextNumber(Next,S,ElemtSizeS);
11     int t = 0;
12     int s = 0;
13     int v;                                                                     //v为偏移距离
14     while(t < ElemtSizeT-1 && (s< ElemtSizeS-1)||s==-1)
15     {
16       if(s == -1|| Ts[t] == Ps[s])                                             //s为-1时,即比较从头开始,Ts为主串,Ps为子串
17       {
18           ++t;++s;
19       }
20       else
21        s = Next[s];
22
23       if(s == ElemtSizeS-1) v = t-ElemtSizeS+1;
24       else  v = -1;
25     }
26
27      return v;
28 }

上述代码中,字符类型为Void*,不过下面为此做了转换。为什么用此类型,纯属作为实验,尝试用void*类型写一下,大家完全可以用char*.哈哈!

如果还有什么不懂,尽量百度,我发现自己的表达水平果然不怎么样!

(3)Rabin_Karp算法

这个算法是从《算法导论》中借鉴过来的,主要是利用公式。相对来说,前期的工作可能比较辛苦些,主要是做一个Hash表,具体咋样?这里不具体说明。

大概思想就是下面这个图:

其实也就是不断增加找到匹配子串的概率,但也有可能不是,相对来说好一些。当主串中某位置计算出来的值与整个子串计算出来的值相等时,就说明可能在主串这个位置,与

子串相匹配。有这个概率,降低了比较的次数。

算法精华在于在于首先判断出现子串首字母的地方进行标记,然后再进行判断比较

算法利用到了初等算法的理论,两个数对于第三个数等价。

 1 int Rabin_Karp_Match( string T,string P,int d,int q)          //d为基数/q为除数,最好是一个素数(T为主串,P为子串)
 2 {
 3   int n = T.length();
 4   int m = P.length();
 5   int h = d^(m-1);
 6   int v  ;                                                     //v为返回值(-1)为失败,其他v值代表匹配的偏移量
 7   int p = 0;
 8   int t[Max];
 9   t[0] = 0;
10   for(int i=0;i<m;++i)
11   {
12      p=(d*p+P[i])%q;                                          //整个子串计算出来的值
13      t[0]=(d*t[0] +T[i])%q;                                   //t0为T主串首个开始点,方便后面用迭代
14   }
15   for(int j=0;j<n-m;++j)
16   {
17       if(p==t[j])
18           for(int s=0;s<m;++s)
19           {
20               if(P[s] != T[j]) break;
21               else if(s = m-1)  { v = j ; return v ;}
22               else {++s;++j;}
23           }
24
25       if(j<n-m)
26           t[j+1] = (d*(t[j] -T[j+1]*h) +T[j+m])%q;
27   }
28 return -1;
29
30 }

具体参照http://net.pku.edu.cn/~course/cs101/2007/resource/Intro2Algorithm/book6/chap34.htm

(4)BM算法

BM算法可能是相对来说,算法效率最高的一种,一般是KMP算法的3~5倍。

BM算法是从后缀比较法开始的。就是给出的算法(1)代码。然而,普通的后缀比较法一般移动只有1。而BM算法其实是对后缀蛮力匹配算法的改进。为了实现更快移动模式

串,BM算法定义了两个规则,好后缀规则和坏字符规则。用好后缀和坏字符可以大大加快模式串的移动距离,不是简单的 j,而是j =max (shift(好后缀), shift(坏字符))。

具体算法,如下图(网上百度别人博客中的图):

1、坏字符算法(情况分为两种):

  • 坏字符没出现在模式串中,这时可以把模式串移动到坏字符的下一个字符,继续比较,如下图:

  • 坏字符出现在模式串中,这时可以把模式串第一个出现的坏字符和母串的坏字符对齐,当然,这样可能造成模式串倒退移动,如下图:

为了用代码来描述上述的两种情况,设计一个数组bmBc['k'],表示坏字符‘k’在模式串中出现的位置距离模式串末尾的最大长度,那么当遇到坏字符的时候,模式串可以移动距离为: shift(坏字符) = bmBc[T[i]]-(m-1-i)。如下图:

2、好后缀算法(分为三种情况)

  • 模式串中有子串匹配上好后缀,此时移动模式串,让该子串和好后缀对齐即可,如果超过一个子串匹配上好后缀,则选择最靠左边的子串对齐。

  • 模式串中没有子串匹配上后后缀,此时需要寻找模式串的一个最长前缀,并让该前缀等于好后缀的后缀,寻找到该前缀后,让该前缀和好后缀对齐即可。

  • 模式串中没有子串匹配上后后缀,并且在模式串中找不到最长前缀,让该前缀等于好后缀的后缀。此时,直接移动模式到好后缀的下一个字符。

为了实现好后缀规则,需要定义一个数组suffix[],其中suffix[i] = s 表示以i为边界,与模式串后缀匹配的最大长度,如下图所示,用公式可以描述:满足P[i-s, i] == P[m-s, m]的最大长度s。

好后缀算法比较难理解,理论解释是:

好后缀数组(其中Suffx数组n内数字主要表示好后缀的最右边下标与最左边之间的差值)

i.  如果在P中位置t处已匹配部分P'在P中的某位置t'也出现,且位置t'的前一个字符与位置t的前一个字符不相同,则将P右移使t'对应t方才的所在的位置。

ii.  如果在P中任何位置已匹配部分P'都没有再出现,则找到与P'的后缀P''相同的P的最长前缀x,向右移动P,使x对应方才P''后缀所在的位置。

关于好后缀算法:

 1  int *Suff = new int[SubLen];
 2   Suff[SubLen-1] = SubLen;                                   //Suff数组主要记录以数组内标号为子串中的距离
 3
 4   int *Suff2 = new int[SubLen];                              //Suff2数组主要记录下标为以i为首字母的字符后缀离最近的匹配字符串的距离
 5   for(int i=0;i<SubLen;i++)
 6   Suff2[i] = 0;                                              //初始化为0
 7
 8
 9  for(int i=SubLen-2;i>=0;i--)
10   {
11     int k=i;
12     while(k>=0 && SubString[k] == SubString[SubLen-1-i+k])   //下标SubLen+k-1-i与k相对应(从后面两个数进行比较,从而确定好后缀)
13     {
14         k--;
15     }
16     Suff[i] = i-k;                                           //其中i-k为最右边下标,数组中的下标为i,i代表子串中与最好后缀匹配的位置
17     if(Suff[i] != 0)
18         Suff2[m-Suff[i]] = SubLen-1-i;                       //确定偏移量
19   }
20
21  int *bmGs = new int[SubLen];                                //bmGs数组主要记录当在i处(i处为坏字符),则需要移动的距离
22  for(int i=0;i<SubLen-1;++i)
23      bmGs[i] = SubLen;
24  bmGs[m-1] = 0;
25
26  for(int i=SubLen-2;i >= 0;i--)
27  {
28       if(Suff[m-1-Suff2[i+1]] !=0 && Suff[m-1-Suff2[i+1]]!=11)
29
30           bmGs[i] = Suff2[i+1];                                                                 //移动距离S,当与好后缀匹配时,移动距离
31
32       else
33         {
34
35             for(int j=i+2;j<m;j++)
36                 if(Suff[m-1-Suff2[j]] == (m-j))                                                 //判断与前缀是否匹配
37                 {
38                     bmGs[i] = Suff2[j];                                                         //测算出偏移距离
39                     break;
40                 }
41
42
43          }
44  } 

bmGs数组主要记录当在i处(i处为坏字符),则需要移动的距离;Suff2数组,下标表示i处出现坏字符时,在字符串前部最靠近坏字符且与i以后好后缀匹配的字符串的具体位

移量。从而可以测算出具体前部匹配具体坐标。

下面给出具体BM算法:

  1 //坏字符算法,主要用来在移动时找到最大位移S,坏字符即移动最大距离
  2 void BM_ErrorChar(int SubLen,int CharByte[256],const char *SubString)
  3 {
  4    //BM_ErrorChar(SubLen,ByteChar);
  5    for(int i=0; i<SubLen; ++i)                  //SubLen-1,不考虑最后一个数
  6       CharByte[SubString[i]] = SubLen-i;      //如果i最后一个为SubLen-1,则最后一个距离根据计算,需要-1
  7
  8 }
  9
 10
 11 int BM(const char *Source,const char *SubString)                      //Source为主串,SubString为子串
 12 {
 13   int SourceLen = strlen(Source);
 14   int SubLen = strlen(SubString);
 15   int m=SubLen;
 16   //坏字符数组 (其中ByteChar数组的下标为字符,表示此字符出现最后一次离子串尾最近的距离)
 17   int ByteChar[256];                        //256是因为下标为字符,字符为ASCII码,8位,总共有256种组合
 18   for(int i=0;i<256;++i)
 19       ByteChar[i] = SubLen;
 20
 21
 22   //int *bmBc = new int[SubLen];
 23   //for(int i=1;i<SubLen;i++)
 24
 25
 26   //好后缀数组(其中Suff数组n内数字主要表示好后缀的最右边下标与最左边之间的差值)
 27   //   i.  如果在P中位置t处已匹配部分P'在P中的某位置t'也出现,且位置t'的前一个字符与位置t的前一个字符不相同,则将P右移使t'对应t方才的所在的位置。
 28
 29   //  ii.  如果在P中任何位置已匹配部分P'都没有再出现,则找到与P'的后缀P''相同的P的最长前缀x,向右移动P,使x对应方才P''后缀所在的位置。
 30
 31
 32
 33   int *Suff = new int[SubLen];
 34   Suff[SubLen-1] = SubLen;                                   //Suff数组主要记录以数组内标号为子串中的距离
 35
 36   int *Suff2 = new int[SubLen];                              //Suff2数组主要记录下标为以i为首字母的字符后缀离最近的匹配字符串的距离
 37   for(int i=0;i<SubLen;i++)
 38   Suff2[i] = 0;                                              //初始化为0
 39
 40
 41  for(int i=SubLen-2;i>=0;i--)
 42   {
 43     int k=i;
 44     while(k>=0 && SubString[k] == SubString[SubLen-1-i+k])   //下标SubLen+k-1-i与k相对应(从后面两个数进行比较,从而确定好后缀)
 45     {
 46         k--;
 47     }
 48     Suff[i] = i-k;                                           //其中i-k为最右边下标,数组中的下标为i,i代表子串中与最好后缀匹配的位置
 49     if(Suff[i] != 0)
 50         Suff2[m-Suff[i]] = SubLen-1-i;                       //确定偏移量
 51   }
 52
 53  int *bmGs = new int[SubLen];                                //bmGs数组主要记录当在i处(i处为坏字符),则需要移动的距离
 54  for(int i=0;i<SubLen-1;++i)
 55      bmGs[i] = SubLen;
 56  bmGs[m-1] = 0;
 57
 58  for(int i=SubLen-2;i >= 0;i--)
 59  {
 60       if(Suff[m-1-Suff2[i+1]] !=0 && Suff[m-1-Suff2[i+1]]!=11)
 61
 62           bmGs[i] = Suff2[i+1];                                                                 //移动距离S,当与好后缀匹配时,移动距离
 63
 64       else
 65         {
 66
 67             for(int j=i+2;j<m;j++)
 68                 if(Suff[m-1-Suff2[j]] == (m-j))                                                 //判断与前缀是否匹配
 69                 {
 70                     bmGs[i] = Suff2[j];                                                         //测算出偏移距离
 71                     break;
 72                 }
 73
 74
 75          }
 76  }
 77
 78
 79 //开始BM算法
 80   //基于后缀回溯比较法
 81   int pSubLen  ,pSourLen =  SubLen;
 82   if(SubLen==0)
 83       return -1;
 84   while(pSourLen<=SourceLen)                                               //主串是否到了尽头
 85   {
 86       pSubLen = SubLen;                                                    //初始化
 87       while(SubString[--pSubLen]==Source[--pSourLen])                      //进行匹配比较
 88     {
 89
 90        if(pSourLen < 0)  return -1;                                         //如果pSour,以子串长度为一组的主串扫描结束
 91
 92        if(pSubLen == 0) return  pSourLen;                                   //为0,匹配成功
 93
 94     }
 95     BM_ErrorChar(pSubLen,ByteChar,SubString);                                           //计算字符偏移量
 96     int CharLen = ByteChar[Source[pSourLen]];                                           //进行坏字符算法计算CharLen;
 97     int SuffLength = bmGs[pSubLen] ;                                                    //进行最好后缀法计算出来的偏移长度
 98     int MaxLen = MAX(CharLen, SuffLength);                                              //最后后缀算法,分情况讨论
 99         pSourLen += (SubLen-pSubLen);
100         pSourLen += MaxLen>1 ? MaxLen:1 ;                                               //进行偏移,pSour值进行恢复与回溯,SubArry - pSub为以前减去的值补回
101
102   }
103
104   return -1;
105
106 }

测试结果:

char *T ="cddcdepcdedefgbcde";     //T为主串
       char *S = "cdedefgbcde" ;              //S为子串

在T[7]处开始匹配。

引用请注明出处:http://www.cnblogs.com/Su-30MKK/archive/2012/09/17/2688122.html

  

转载于:https://www.cnblogs.com/Su-30MKK/archive/2012/09/17/2688122.html

关于字符串匹配算法研究相关推荐

  1. 字符串匹配算法研究(一)

    字符串匹配算法研究(一) 上星期拜读了2004年中国IOI集训队中朱泽园(当时就读南京市外国语学校)的一篇论文<多串匹配算法及启示>,并将其中的部分算法在理解的基础上用C++和Java做了 ...

  2. 字符串匹配算法 -- BM(Boyer-Moore) 和 KMP(Knuth-Morris-Pratt)详细设计及实现

    文章目录 1. 算法背景 2. BM(Boyer-Moore)算法 2.1 坏字符规则(bad character rule) 2.2 好后缀规则(good suffix shift) 2.3 复杂度 ...

  3. 数据结构与算法之美笔记——基础篇(下):图、字符串匹配算法(BF 算法和 RK 算法、BM 算法和 KMP 算法 、Trie 树和 AC 自动机)

    图 如何存储微博.微信等社交网络中的好友关系?图.实际上,涉及图的算法有很多,也非常复杂,比如图的搜索.最短路径.最小生成树.二分图等等.我们今天聚焦在图存储这一方面,后面会分好几节来依次讲解图相关的 ...

  4. iptables --algo 字符串匹配算法 bm kmp

    http://blog.csdn.net/l953972252/article/details/51331001 字符串匹配一直是计算机领域热门的研究问题之一,多种算法层出不穷.字符串匹配算法有着很强 ...

  5. 对ZZL字符串匹配算法的改 ——ZZL最短匹配定理

    对ZZL字符串匹配算法的改进 --ZZL最短匹配定理 张亮 ZZL算法是一种可做特殊用途的字符串匹配算法,本文将改进ZZL算法,在ZZL算法的基础上,提出了ZZL最短匹配定理,根据模式串的自身特征以进 ...

  6. 一看就懂的字符串匹配算法 之 BM算法

    先来一个导读,关于BM算法开始有了难度,大家一定要静下心,咬着呀也得给我读下去,这样你才能有收获. 我们在文本编辑器中,我们经常用到查找及替换功能.比如说,在Word文件中,通过查找及替换功能,可以把 ...

  7. 字符串匹配算法:Sunday算法

    背景 我们第一次接触字符串匹配,想到的肯定是直接用2个循环来遍历,这样代码虽然简单,但时间复杂度却是\(Ω(m*n)\),也就是达到了字符串匹配效率的下限.于是后来人经过研究,构造出了著名的KMP算法 ...

  8. Go 语言实现字符串匹配算法 -- BF(Brute Force) 和 RK(Rabin Karp)

    今天介绍两种基础的字符串匹配算法,当然核心还是熟悉一下Go的语法,巩固一下基础知识 BF(Brute Force) RK(Rabin Karp) 源字符串:src, 目标字符串:dest: 确认des ...

  9. Boyer-Moore 字符串匹配算法

    字符串匹配问题的形式定义: 文本(Text)是一个长度为 n 的数组 T[1..n]: 模式(Pattern)是一个长度为 m 且 m≤n 的数组 P[1..m]: T 和 P 中的元素都属于有限的字 ...

最新文章

  1. WPF 4 Ribbon 开发 之 快捷工具栏(Quick Access Toolbar)
  2. 设计模式 — 结构型模式 — 桥接模式
  3. windows上安装gcc/g++环境(MinGW,msys64等)
  4. 你方唱罢我登场,“全宇宙仅此一回”的小米手机青春版竞争策略分析
  5. php 分享微博,php微信分享到朋友圈、QQ、朋友、微博
  6. 2019.7.29学习整理python
  7. jspspy database help
  8. 过滤你不想看见的网页里的东西
  9. 使用jQuery来实现一个简单的ajax请求
  10. python 获取数据库字段类型_python中如何读取数据库数据类型
  11. python属于汇编语言还是高级语言_计算机语言Python解释器
  12. java 在类中定义接口_我们可以在Java接口中定义一个类吗?
  13. 【DIY贴片机】基于opencv识别定位电子元件
  14. php tableau,Tableau函数
  15. QT 代码添加QScrollArea
  16. 如何通过Python进行图片批量下载?
  17. R forcast auto arima用法
  18. 联想ghost重装系统_如何使用ghost手动安装系统_手动ghost安装系统图文步骤
  19. 这几个消除笔软件,值得你们收藏
  20. stm32cubemx配置pwm

热门文章

  1. QUdpSocket 4.6 严重丢包
  2. 统一建模语言UML轻松入门之用例
  3. (进阶)LeetCode(242)——有效的字母异位词(JavaScript)
  4. JavaScript学习(四十三)—构造方法创建对象的图解和注意事项
  5. 月收入不足三千的人,每月的开销都是怎样的?
  6. 80后的你现在有多少资产?
  7. http与https的有什么不同
  8. 微商人赚钱的4个关键点
  9. 还可以这样玩?揭秘打通线上线下新思路
  10. OpenStack 是什么