返回介绍

5.6 朴素的模式匹配算法

发布于 2024-08-19 23:28:45 字数 3329 浏览 0 评论 0 收藏 0

记得我在刚做软件开发的时候,需要阅读一些英文的文章或帮助。此时才发现学习英语不只是为了过四六级,工作中它还是挺重要的。而我那只为应付考试的英语,早已经忘得差不多了。于是我想在短时间内突击一下,很明显,找一本词典从头开始背不是什么好的办法。要背也得背那些最常用的,至少是计算机文献中常用的,于是我就想自己写一个程序,只要输入一些英文的文档,就可以计算出这当中所用频率最高的词汇是哪些。把它们都背好了,基本上阅读也就不成问题了。

当然,说说容易,要实现这一需求,当中会有很多困难,有兴趣的同学,不妨去试试看。不过,这里面最重要其实就是去找一个单词在一篇文章(相当于一个大字符串)中的定位问题。这种子串的定位操作通常称做串的模式匹配,应该算是串中最重要的操作之一。

假设我们要从下面的主串S="goodgoogle"中,找到T="google"这个子串的位置。我们通常需要下面的步骤。

1.主串S第一位开始,S与T前三个字母都匹配成功,但S第四个字母是d而T的是g。第一位匹配失败。如图5-6-1所示,其中竖直连线表示相等,闪电状弯折连线表示不等。

图5-6-1

2.主串S第二位开始,主串S首字母是o,要匹配的T首字母是g,匹配失败,如图5-6-2所示。

图5-6-2

3.主串S第三位开始,主串S首字母是o,要匹配的T首字母是g,匹配失败,如图5-6-3所示。

图5-6-3

4.主串S第四位开始,主串S首字母是d,要匹配的T首字母是g,匹配失败,如图5-6-4所示。

图5-6-4

5.主串S第五位开始,S与T,6个字母全匹配,匹配成功,如图5-6-5所示。

图5-6-5

简单的说,就是对主串的每一个字符作为子串开头,与要匹配的字符串进行匹配。对主串做大循环,每个字符开头做T的长度的小循环,直到匹配成功或全部遍历完成为止。

前面我们已经用串的其他操作实现了模式匹配的算法Index。现在考虑不用串的其他操作,而是只用基本的数组来实现同样的算法。注意我们假设主串S和要匹配的子串T的长度存在S[0]与T[0]中。实现代码如下:

/* 返回子串T在主串S中第pos个字符之后的位置。
若不存在,则函数返回值为0。 */
/* T非空,1≤pos≤StrLength(S)。 */
int Index(String S, String T, int pos)
{
    /* i用于主串S中当前位置下标,若pos不为1 */
    /* 则从pos位置开始匹配 */
    int i = pos;                      
    /* j用于子串T中当前位置下标值 */
    int j = 1;                        
    /* 若i小于S长度且j小于T的长度时循环 */
    while (i <= S[0] && j <= T[0])    
    {
        /* 两字母相等则继续 */
        if (S[i] == T[j])             
        {
            ++i;
            ++j;
        }
        /* 指针后退重新开始匹配 */
        else                          
        {
            /* i退回到上次匹配首位的下一位 */
            i = i - j + 2;            
            /* j退回到子串T的首位 */
            j = 1;                    
        }
    }
    if (j = T[0])
        return i - T[0];
    else
        return 0;
}

分析一下,最好的情况是什么?那就是一开始就区配成功,比如“googlegood”中去找“google”,时间复杂度为O(1)。稍差一些,如果像刚才例子中第二、三、四位一样,每次都是首字母就不匹配,那么对T串的循环就不必进行了,比如“abcdef-google”中去找“google”。那么时间复杂度为O(n+m),其中n为主串长度,m为要匹配的子串长度。根据等概率原则,平均是(n+m)/2次查找,时间复杂度为O(n+m)。

那么最坏的情况又是什么?就是每次不成功的匹配都发生在串T的最后一个字符。举一个很极端的例子。主串为S="00000000000000000000000000000000000000000000000001",而要匹配的子串为T="0000000001",前者是有49个“0”和1个“1”的主串,后者是9个“0”和1个“1”的子串。在匹配时,每次都得将T中字符循环到最后一位才发现:哦,原来它们是不匹配的。这样等于T串需要在S串的前40个位置都需要判断10次,并得出不匹配的结论,如图5-6-6所示。

图5-6-6

直到最后第41个位置,因为全部匹配相等,所以不需要再继续进行下去,如图5-6-7所示。如果最终没有可匹配的子串,比如是T="0000000002",到了第41位置判断不匹配后同样不需要继续比对下去。因此最坏情况的时间复杂度为O((n-m+1)*m)。

图5-6-7

不要以为我这只是危言耸听,在实际运用中,对于计算机来说,处理的都是二进位的0和1的串,一个字符的ASCII码也可以看成是8位的二进位01串,当然,汉字等所有的字符也都可以看成是多个0和1组成的串。再比如像计算机图形也可以理解为是由许许多多个0和1的串组成。所以在计算机的运算当中,模式匹配操作可说是随处可见,而刚才的这个算法,就显得太低效了。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文