第32章:字符串匹配问题: 朴素算法,Rabin-Karp算法

在文本编辑中,经常要找出某一个模式在一段文本中全部出现的位置。这可以用字符串匹配问题来求解,不过这一章节仅考虑长度有限的字符串。如果一个模式P(长度为m)是从文本T(长度为n)中第(s+1)个字符开始出现,我们则说模式p在文本T中出现并且位移为s(0<=s<=n-m)。

本章节给出了求解字符串匹配问题的四种算法,分别是朴素算法,Rabin-Karp算法,有限自动机算法,Knuth-Morris-Praat算法。除了朴素算法外,另外三个算法都对模式P进行了一些预处理,然后找寻所有位移,我们称第二步为匹配。在这篇文章中,先介绍朴素算法和Rabin-Karp算法。

朴素算法:

朴素算法应该属于暴力搜索法,用一个循环找出所有有效位移s(0<=s<=n-m),该循环对n-m+1个可能的每一个s值检查模式P是否与文本中从第s+1个字符开始匹配。代码如下:

//判断模式字符串P是否与从文本中位置pos开始的字符串匹配(文本从位置0开始);
template<class C>
bool is_equal(const C& P,const C& T,size_t pos)
{
        for(size_t i=0;i!=P.size();++i)
                if(P[i]!=T[pos+i])
                        return false;

        return true;
}

void naive_string_matcher(const string& T,const string& P)
{
        size_t n=T.size();
        size_t m=P.size();

        if(n<m){
                cout<<"the length of patter is greater than the length of text!!"<<endl;
                return;
        }

        for(int s=0;s<=n-m;++s)
                if(is_equal(P,T,s))
                        cout<<"pattern starts to occur from the "<<s+1<<"th character of Text"<<endl;
}

这个算法由于没有对模式P进行预处理,因此预处理时间为0,匹配时间为O((n-m+1)*m),因为对s的for循环有(n-m+1)步,is_equal函数所花费的时间为O(m)。

Rabin-Karp算法:

假设文本T和模式P的元素都是来自一个有限子母集C的字符,我们可以把C中的字符等价地转化为数字,C={0,1,2,…,d-1}(d为字母集C中元素的个数,也称做基数),依据C中字符转化数字的关系,我们相应地也把文本T和模式P中的字符也转换成了数字。

假设给定了一个素数q。给定一个由数字组成的模式P[0…(m-1)],令p表示相应的d进制值对q的模
p=[P[m-1]+d(P[m-2]+10(P[m-3]+…+10(P[1]+10P[0])…))]%q.

类似地,给定一个由数字组成的文本T[0…(n-1)],假设 ts 为长度为m的字符串T[s,s+1…s+m-1]所对应的d进制值对q的模。当p和 ts 不等时,那么P[0…(m-1)]一定与T[s,s+1…s+m-1]不匹配;如果两者相等,则可能匹配也可能不匹配,这是我们可以通过直接验证P[0…(m-1)]==T[s,s+1…s+m-1]是否成立。

我们可以用求p相同的方法来求 t0 ,但当我们求 t1,t2,...,tnm 时,我们可以用如下关系式来求解:
ts+1=(d(tsT[s](dm1 mod q))+T[s+m]) mod q

有一个细节需要主要的是,我们应该要确保
d(tsT[s](dm1 mod q))+T[s+m] 这一项应大于0,如果小于0则要相应地转换成大于0的形式。

Rabin-Karp算法代码如下:

//to calculate a^b%c;
unsigned long mod(unsigned long a, unsigned long b, unsigned long c)
{
        unsigned long ret=1;
        unsigned long tmp=a%c;

        while(b!=0){
                if(b%2!=0)
                        ret=(ret*tmp)%c;
                b/=2;
                tmp=(tmp*tmp)%c;
        }

        return ret;
}

void Rabbin_Karp_matcher(const vector<unsigned long>& T,const vector<unsigned long>& P,unsigned long d,unsigned long q)
{
        size_t n=T.size();
        size_t m=P.size();

        // to calculate d^(m-1)%q;
        unsigned long h=mod(d,m-1,q);

        //to calculate T[0...(m-1)]%q,P[0...(m-1)]%q;
        unsigned long p=0;
        unsigned long t=0;
        for(size_t i=0;i!=m;++i)
        {
                p=(d*p+P[i])%q;
                t=(d*t+T[i])%q;
        }

        // to find the match of P in T;
        for(size_t s=0;s<=n-m;++s)
        {

                if(p==t)
                        if(is_equal(P,T,s))
                                cout<<"pattern starts to occur from the "<<s+1<<"th character of Text"<<endl;

                if(s<n-m){
                        long tmp=d*(t-T[s]*h)+T[s+m];

                //if tmp<0, the result of tmp%q is machine independent.The case should be avoided.
                        if(tmp<0){
                                tmp=-tmp;

                                int k=0;
                                while(++k){
                                        if(tmp<k*q){
                                                tmp=k*q-tmp;
                                                break;
                                        }
                                }
                        }

                        t=tmp%q;
                }
        }
}

Rabin-Karp算法计算p和 t0 的预处理时间为 Θ(m) 。计算 t1,t2,...,tnm 分别花费常数时间,在最坏情况下,n-m+1个s所对应的偏移中的每一个都是有效的,则验证所花费的时间为 Θ((nm+1)m) 。因此这个算法预处理时间为 Θ(m) ,匹配时间为 Θ((nm+1)m)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
串匹配是指在一个文本串查找另一个模式串的过程。常用的串匹配算法有Naïve算法、Rabin-Karp算法和Knuth-Morris-Pratt算法。 1. Naïve算法 Naïve算法是最简单的串匹配算法,也称为暴力匹配算法。它的思路是从文本串的第一个字符开始,依次比较文本串的每个字符是否与模式串的字符相等。若不相等,则继续向后比较;若相等,则比较下一个字符,直到找到完全匹配的子串或文本串被匹配完为止。 Naïve算法的时间复杂度是O(mn),其m和n分别是模式串和文本串的长度。当模式串和文本串长度相等时,最坏情况下时间复杂度达到O(n^2)。 2. Rabin-Karp算法 Rabin-Karp算法是一种基于哈希值的串匹配算法。它的思路是先将模式串和文本串都转换为哈希值,然后比较它们的哈希值是否相等。如果哈希值相等,则再逐个比较模式串和文本串的字符是否相等。这种方法可以有效地减少比较次数,提高匹配效率。 Rabin-Karp算法的时间复杂度是O(m+n),其m和n分别是模式串和文本串的长度。但是,由于哈希函数的不完全性和哈希冲突的存在,Rabin-Karp算法在某些情况下可能会出现误判。 3. Knuth-Morris-Pratt算法 Knuth-Morris-Pratt算法是一种基于前缀函数的串匹配算法。它的思路是先计算出模式串的前缀函数,然后利用前缀函数的信息来跳过已经匹配过的部分,减少比较次数。 具体来说,KMP算法在匹配过程维护一个指针i和一个指针j,其i指向文本串当前匹配的位置,j指向模式串当前匹配的位置。如果当前字符匹配成功,则i和j同时向后移动一位;如果匹配失败,则通过前缀函数计算出j需要跳转到的位置,使得前j-1个字符与文本串的对应字符已经匹配成功,然后将j指向这个位置,i不变,继续比较下一个字符。 KMP算法的时间复杂度是O(m+n),其m和n分别是模式串和文本串的长度。由于利用了前缀函数的信息,KMP算法可以在最坏情况下达到O(n)的时间复杂度,比Naïve算法和Rabin-Karp算法更加高效。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值