多种字符串模拟匹配算法的学习

在此之前,只知道KMP算法是字符串匹配算法中最富盛名的,然而去网上一搜才知道像BM/Horspool/Sunday等算法要比经典的KMP算法效率还要高。所以记录一下所学之物以便查找和温习。


BF算法

暴力匹配。最单纯的匹配方式,每次对应的字符匹配失败就会将模式串往后移动一位,之后循环。BF算法的时间复杂度是O(m*n)。

KMP算法

属于前缀匹配算法。是对传统的后移一位的BF算法的改进,也是最难理解的算法,主要在于next数组使得后移的效率提高了。如果在某位适配时就会调用该位之前计算的next数组中对应的值决定后移的位数。其实next数组的原理是基于求得当前的最长公共前后缀长度,而且这有些像确定最长回文子串算法Manacher算法中利用对称关系来计算出更大的值而不是每次都是默认的1。
求解next数组程序:
void makeNext(const char P[],int next[])
{
    int q,k; //q:模版字符串下标;k:最大前后缀长度
    int m = strlen(P); //模版字符串长度
    next[0] = 0; //模版字符串的第一个字符的最大前后缀长度为0
    for (q = 1,k = 0; q < m; ++q) //for循环,从第二个字符开始,依次计算每一个字符对应的next值
    {
        while(k > 0 && P[q] != P[k]) //递归的求出P[0]···P[q]的最大的相同的前后缀长度k
            k = next[k-1];          //while循环是整段代码的精髓所在,确实不好理解  
        if (P[q] == P[k]) //如果相等,那么最大相同前后缀长度加1
        {
            k++;
        }
        next[q] = k;
    }
}
时间复杂度 O(n×m)。

BM算法

基于后匹配的算法。理解程度要比KMP简单一些但是BM算法分出了好后缀和坏后缀。并且针对两种后缀指定相应的规则。
对于坏后缀,如果坏字符没有出现在模式字符中,则直接将模式串移动到坏字符的下一个字符;如果坏字符出现在模式串中,则将模式串最靠近好后缀的坏字符与母串的坏字符对齐。而对于好后缀,模式串中有子串匹配上好后缀,此时移动模式串,让该子串和好后缀对齐即可,如果超过一个子串匹配上好后缀,则选择最靠靠近好后缀的子串对齐;模式串中没有子串匹配上后后缀,此时需要寻找模式串的一个最长前缀,并让该前缀等于好后缀的后缀,寻找到该前缀后,让该前缀和好后缀对齐;模式串中没有子串匹配上后后缀,并且在模式串中找不到最长前缀,让该前缀等于好后缀的后缀。此时,直接移动模式到好后缀的下一个字符。
/*坏后缀数组建立
参数:
pattern:                 模式串
pattern_length:模式串长度
badc:                 坏数组
size:       坏数组大小
由于参数在传参中所以返回值为void
*/

 void BuildBadC(const char* pattern, int pattern_length, unsigned int* badc, int  size)
{
unsigned int i;

for(i = 0; i < size; ++i)
{
badc[i] = pattern_length; //预先赋值为pattern_length是因为这是从后先前匹配
}

for(i = 0; i < pattern_length; ++i)
{
badc[pattern[i] - 'A'] = pattern_length - 1 - i; //将串中对应的字符在坏数组对应的位置附上该字符距离最后字符的距离,而且是最右字符。
}
}

/*
好后缀数组
参数:
pattern 模式串
pattern_length:             模式串长度
goods: 好数组
*/
void BuildGoodS(const char* pattern, int  pattern_length, unsigned int* goods)
{
unsigned int i, j, c;

for(i = 0; i < pattern_length - 1; ++i)
{
goods[i] = pattern_length;                          //预先赋值
}

//初始化pattern最末元素的好后缀值
goods[pattern_length - 1] = 1;

//找出pattern中各元素的pre值
for(i = pattern_length -1, c = 0; i != 0; --i)
{
for(j = 0; j < i; ++j)
{
if(memcmp(pattern + i, pattern + j, (pattern_length - i) * sizeof(char)) == 0)
{
if(j == 0)
{
c = pattern_length - i;
}
else
{
if(pattern[i - 1] != pattern[j - 1])
{
goods[i - 1] = j - 1;
}
}
}
}
}


//根据pattern中个元素的pre值,计算goods值
for(i = 0; i < pattern_length - 1; ++i)
{
if(goods[i] != pattern_length)
{
goods[i] = pattern_length - 1 - goods[i];
}
else
{
goods[i] = pattern_length - 1 - i + goods[i];

if(c != 0 && pattern_length - 1 - i >= c)
{
goods[i] -= c;
}
}
}
}
/*BM匹配算法*/
unsigned int BM(const char* text, int  text_length, const char* pattern, int  pattern_length, unsigned int* matches)
{
unsigned int i, j, m;
unsigned int badc[ALPHABET_SIZE];
unsigned int goods[pattern_length];
i = j = pattern_length - 1;
m = 0;

//构建好后缀和坏字符表
BuildBadC(pattern, pattern_length, badc, 256);
BuildGoodS(pattern, pattern_length, goods);


while(j < text_length)
{
//发现目标传与模式传从后向前第1个不匹配的位置
while((i != 0) && (pattern[i] == text[j]))
{
--i;
--j;
}

//找到一个匹配的情况
if(i == 0 && pattern[i] == text[j])
{
matches[m++] = j;
j += goods[0];
}
else
{
j += goods[i] > badc[text[j]-'A'] ? goods[i] : badc[text[j]-'A'];     //好后缀与坏后缀数组的值进行比较取较大值
}

i = pattern_length - 1;
}

return m;
}
最差时间复杂度 O(n×m)。


Horspool算法

horspool算法将主串中匹配窗口的最后一个字符跟模式串中的最后一个字符比较。如果相等,继续从后向前对主串和模式串进行比较,直到完全相等或者在某个字符处不匹配为止 。如果不匹配,则根据主串匹配窗口中的最后一个字符β在模式串中的下一个出现位置将窗口向右移动。


 #define WORD 26
 int horspool(char *T, int lenT, char *P, int lenP)
 {
     int d[WORD];
     int i, pos, j;
 
     for(i = 0; i < WORD; i++)
         d[i] = lenP; //与BM算法相似之处

     for(i = 0; i != (lenP-1); i++)
         d[P[i]-'A'] = lenP-i-1; //与BM算法相似之处
 
     pos = 0;
     while(pos < (lenT-lenP))
{
         j = lenP-1;
         while(j >= 0 && T[pos+j]==P[j])  
             j--;
         if(j == -1)
             return pos;      //匹配
         else 
             pos += d[T[pos+lenP-1]-'A'];      //未匹配
         }
     return -1;
 }
Horspool算法要比BM理解简单而且代码量少。平均情况下时间复杂度是 O(n),最差为O(mn)。

Sunday算法

sunday算法关键在于匹配不成功之后的跳跃。在匹配失败时关注的是文本串中参加匹配的最末位字符的下一位字符。如果该字符没有在匹配串中出现则直接跳过,即移动步长= 匹配串长度+1;否则,同BM算法一样其移动步长=匹配串中最右端的该字符到末尾的距离+1。理解程度比BM简单,代码量更少。

int sunday(const char* src, const char* des)
{
int len_s = strlen(src);
int len_d = strlen(des);
int next[26] = {0};

for (int j = 0; j < 26; ++j)
next[j] = len_d + 1;

for (int j = 0; j < len_d; ++j)
next[des[j] - 'a'] = len_d - j; //记录字符到最右段的最短距离+1,而且是最右端字符。

int pos = 0;
while (pos < (len_s - len_d + 1)) //末端对齐
{
int i = pos;
int j;
for (j = 0; j < len_d; ++j, ++i)
{
if (src[i] != des[j])
{
pos += next[src[pos + len_d] - 'a'];
break;
}
}
if ( j == len_d )
return pos;
}
return -1;
}


向前辈学习。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值