您的位置:首页 > 其它

Rabin-Karp-MATCHER字符串匹配算法; 一种效率还不错的匹配算法; 思想是关键.

2010-06-21 19:23 309 查看
#include <stdio.h>
#include <string.h>

/*********************************/
*   n:文本串T长度
*   m:模式串P长度
*   h:d^(m-1)
*   d:进制基数(这里采用26进制)
*   t[s]:对应位移s的文本串T子串的d进制表示
*   p:模式串P的d进制表示
*   q:对d进制整数模q,q是素数(越大越容易排出非法位移s,但是过大会导致模运算溢出,这里取13)
/*********************************/

const int MAXN = 10000;
int n,m,h,p,d,q,t[MAXN];
char T[MAXN],P[MAXN];

/*********************************/
*   RabinKarpMatcher()串匹配算法
*   预处理时间:O(n-m+1)+O(m)
*   匹配时间最坏:O((n-m+1)*m)
*   但实际运行效果一般比这个好
*   预处理:将T[s]与P都转化成d进制整数
*   匹配问题就变成了判断整数T[s]=P
*   由于整数可能很大,所以必须作Mod运算
*   这样就导致即便Mod后T[s]=p
*   也不能确定是否真的完全相等
*   所以,需要检测这个T[s]是否是伪命中
*   如果Mod后都不相等,则肯定非命中
/*********************************/

int modPow(int x,int y,int z)
{
int ret=1,i=0;
for(;i<y;++i)
{
ret=(int )( (ret*(long long)x) % z );  // 严格要求 q * d 在一个机器字长内, 即C语言里long long 的64 bits
}
return ret;
}

void dNum()
{
int i=1;
p=0;
t[0]=0;
for(;i<=m;++i)
{
p= (int ) (( (long long )p*d+(P[i]-'a') ) %d );
t[0]= (int) (((long long)t[0]*d+(T[i]-'a') )%d);
}

}

bool RabinKarpMatcher()
{
d=26; //26进制数
q=13; //模13
n=strlen(T)-1;
m=strlen(P)-1;
h=modPow(d,m-1,q); //计算d^(m-1) mod q
dNum();
int s,j;
bool has=false;

for(s=0;s<=n-m;++s)
{
if(t[s]==p)
{
bool is=true;
for(j=1;j<=m;++j)
{
if(T[s+j]!=P[j])
{
is=false;
break;
}
}
if(is)
{
printf("T中有效位置:%d/n",s+1);
has=true;
}
}
if(s<n-m)
{
// +26*q防止出现运算过程中的负数
t[s+1]=( int ) ( ( d* ( t[s]- ( (T[s+1]-'a') * (long long )h )+ 26*q ) + ( T[s+m+1]-'a') ) %q );
}
}
return has;
}

int main()
{
T[0]=P[0]='z';
printf("输入文本串T,模式串S,以空格/回车作为间隔./n");
scanf("%s%s",T+1,P+1);
if(!RabinKarpMatcher())
{
printf("对不起,没有匹配成功!/n");
}
return 0;
}


 

 

描述一下算法的思想:

 

朴素的字符串匹配, 是对文本串T的每一位开始的子串与模式串P进行匹配, 一共有n-m+1位, 每次最坏扫描m位, 所以复杂度O(n(n-m+1))

 

RabinKarp算法是怎么优化这个过程的呢?

 

首先, 讲朴素的字符串匹配变的更简单一些, 把从s位开始的T子串转化为一个整数, 把P串也转化成一个整数, 只要t[s]=p ,即两个串对应的整数相等, 很明显两个串也就匹配了, 举个例子就懂了;

 

假设字符串里只有a,b,c三个字符, 对应的10进制数为: a:0, b:1, c:2;

 

假设文本串T:abc

假设模式串P: ab

 

那么先将P串转化成整数就是0*10+1=1

文本串T的子串转化成整数分别是: T[0]=0*10+1=1;  T[1]=1*10+2=12;

 

其实就是把每个字符对应一个d进制数的1位, 然后像二进制一样, 求出它对应的整数 ,这里转化有个迭代运算方法,比较方便,叫做霍纳法则,可以查一下,代码里也有体现.

 

现在匹配变成了判断T[s]==p ,那么就匹配成功.

 

但是, 如果串P很长, 那么整数会很大很大, 会溢出, 所以必须做一些处理, 就是MOD运算.

 

两个不同的串对应的整数MOD后也可能相等,这样判断起来就没啥意思了, 但是有一个特性, 如果两个串对应的整数的MOD结果不同,那么这两个串不可能相同!

 

所以, 利用这种方法, 可以在朴素的匹配方法的基础上, 利用这个性质直接排除一些位置的判断 ,这样就减小了复杂度.

 

预处理就是求t[s]与p, 单独求每个t[s] 复杂度就是平方级了, 所以这里有一个巧妙的递推方法求所有的t[s].

 

举个例子, 不讲公式了.

 

假设文本串T:abc

假设模式串P: ab

 

T[0]= ab , 对应01

T[1]= (01 - 0*10) *10 +2 =12

 

就是本来ab对应01 , 把a的0去掉,在1的右边加上c=2, 变成12 ,这个过程是可以利用不同进制间的转化的原理用算式算出来的.

 

 

就讲这些,看算法导论应该可以轻松看懂,很简单.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  算法 include 优化 语言 c