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

来源:互联网 发布:圣思园java视频百度云 编辑:程序博客网 时间:2024/05/18 03:14

在文本编辑中,经常要找出某一个模式在一段文本中全部出现的位置。这可以用字符串匹配问题来求解,不过这一章节仅考虑长度有限的字符串。如果一个模式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
原创粉丝点击