您的位置:首页 > 理论基础 > 数据结构算法

数据结构 - 字符串的模式匹配

2010-12-25 20:44 267 查看
什么叫做模式匹配呢?其实就是看字符串S中是否有子串T,那么T就叫匹配串。
我们平时查找方式是逐个匹配,那么时间复杂度就是O(n*m).
比如s=’abcabde’,t=’abd’
那么比较的时候s[1]=t[1],所以接下来比较s[2]和t[2],结果s[2]=t[2],那么接下来就比较s[3]和t[3],这一次比较匹配不上。
然后就是普通匹配方法比较耗时的地方:比较s[2]和t[1]继续上面那样的流程。这样就可以看到,最坏的情况需要比较O(n*m)次。
画一下匹配过程:
1 abcabde
   abd      匹配失败
2 abcabde
    a        匹配失败
3 abcabde
      a      匹配失败
4 abcabde
        abd 匹配成功
我们看到比了4次。那么怎么用程序来实现呢?

function CommonIndex(const Sub,Dest:string):Integer ;
var
i,j:Integer;
Len1,Len2:Integer;
begin
Len1:=Length(Sub);
Len2:=Length(Dest);
Result:=0;
if Len1>Len2 then
Exit;
j:=1;
i:=1;
//判断过程
while (I<= Len2 - Len1 + 1)and(J <= Len1) do
if Dest[J] = Sub[I] then
begin
Inc(I);
Inc(J);
end
else
begin
J:=1;     //sub回溯
I:=I-J+2; //Dest回溯
end;
//判断是否查找成功
if J = Len1 then
Result:=I-J+1;
end;


接下来看看C怎么实现的:

int CommonIndex(char* sub,char* dest){
int i=0,j=0;
while (dest[i]&&sub[j]){
if (dest[i]==sub[j]){
++i;
++j;
}else{
j=0;
i-=j-1;
}
}//endwhile
if (!sub[j])
return i-j+1;
else
return 0;
}


就像上面注释那样,每次匹配失败的话,都需要把两个字符串都回溯,所以这里就必要浪费时间了。

整个算法的时间复杂度是O(m*n)

我们再回来看看上面的匹配过程。

1 abcabde

   abd      匹配失败

在匹配到c和d这里,我们发现不用再去比较b和a 以及c和a了,因为肯定不能匹配。所以现在我们只需要回溯sub字符串。

看看改进后的匹配过程:

1 abcabde

   abd      匹配失败

2 abcabde

       abd  匹配成功

但是Sub每次匹配失败后回溯多少呢?这就是这个KMP算法里面精髓的地方。

我们先看看代码吧!

function KMPIndex(const Sub,Dest:String):Integer;
var
i,j:Integer;
len1,len2:Integer;
next:array of Integer;
procedure MakeNext;//自过程生产Next表
begin
i:=1;
j:=0;
next[1]:=0;
while i<=len1 do
if(j=0)or (sub[i]=sub[j])then
begin
Inc(i);
Inc(j);
next[i]:=j;
end
else
j:=next[j];
end;
begin
len1:=Length(sub);
len2:=Length(Dest);
Result:=0;
if len1>len2 then
Exit;
SetLength(next,len1 + 1); //动态数组是从0开始的,我们要从1开始,所以这里+1,多一个元素
//生成Sub的回溯表。
MakeNext;
try
i:=1;
j:=1;
while (i<=len1)and(j<=len2) do
if (i=0)or(Sub[i] = Dest[j]) then  //匹配成功
begin
inc(i);
Inc(j);
end
else
i:=next[i]; //失败,只有Sub回溯一下
if i > len1 then
Result:=j - i + 1;
finally
SetLength(next,0);
end;
end;


那么C的代码会是怎么样呢?

int KMPIndex(char* sub,char* dest){
int len = 0;
//计算sub的长度
for(;sub[len];++len);
//动态分配next数组
int* next = (int*)malloc(len *sizeof(int));
//生成next数组
int i = 0,k = -1;
//*(next++) = 0;
next[0] = -1;
while (i<len)
if (k==-1||sub[i]==sub[k]){
++i;
++k;
//*(next++ )= k;
next[i] = k;
}else{
k = next[k];
}
//现在next数组生成了 ,接下来就是匹配比较了。
i = 0;
k = 0;
while (dest[i]&&sub[k]){
if (k==-1||dest[i] ==sub[k]){
++i;
++k;
}else
k=next[k];
}//endwhile
//比较完了释放next空间
free(next);
//判断是否匹配成功
if (!sub[k])
return i - k + 1;
else
return 0;
}


我们看看时间复杂度吧。理论来说应该大于O(n+m),小于O(n*m).在实际过程中近似O(n+m).

从上面的代码中我们可以看见next的第i个元素是靠比较第i-1个得到的。

while (i<len)
if (k==-1||sub[i]==sub[k]){
++i;
++k;
//*(next++ )= k;
next[i] = k;
}else{
k = next[k];
}


while i<=len1 do
if(j=0)or (sub[i]=sub[j])then
begin
Inc(i);
Inc(j);
next[i]:=j;
end
else
j:=next[j];


如果字符是从1开始的,那么next[0] = 0,如果字符从0开始的,那么next[0]=-1;

那么我们手动来计算一下 'abababd’的next数组字,假设字符串从1开始。

a b a b a b d

0  //第一个直接等于0

0 1 //第二个要靠第一个去判断。第一个字符为a,对应next[1]值为0,那么结束比较。next[2]=1

0 1 1 //第三个要靠第二个去判断。第二个字符为b,对应的k值为1,而第一个字符是a,由于b不等于a,继续取第一个字符a对应的k值,k为0比较结束,next[3]=1;

0 1 1 2 //第四个要靠第三个。第三个字符是a,对应的k值为1,而第一个字符是a,由于a=a,比较结束next[4]=1+1=2

0 1 1 2 3 //第五个要靠第四个。第四个字符是b,对应k值是2,而第二个字符是b,由于b=b,比较结束next[5]=2+1=3

0 1 1 2 3 4 //同理。第五个字符是a,对应的k值是3,而第三个字符是a,由于a=a,比较结束,所有next[6] = 3+1 = 4;

0 1 1 2 3 4 5 //第六个字符是b,对应的k是4,而第四个字符是b,由于b=b,比较结束,所以next[7] = 4 + 1 = 5;

整个next数组就是 0 1 1 2 3 4 5

不知道大家对上面这个求的过程看明白了没有。

当然这个生成next的过程在特殊情况是有错误的。所以就有了改进的地方,nextval。这个我们下回再说。有条件的朋友可以自己看看书。

书中自有黄金屋,书中自有颜如玉。

金钱与女人都出自书本。。。哈哈,难怪古代文人那么喜欢逛青楼。。。蛋疼。!!

等着我的nextval 谢谢
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: