【前言】
选题仍然是源自课堂习题,是要设计一个含有通配符问号的KMP字符匹配算法。其实这个目标是非常容易达到的。但是,接着想到通配符星号时,就感到有些困难了。因为星号可以匹配的字符个数不是确定的,所以子串中含有星号时要多次扫描母串,每种情况都要逐一比对才行,现有的匹配算法显然达不到这样的目的。现在要设计一种算法,即能完成不含通配符的匹配问题,同时又能适应含通配符的匹配问题。
【分析】
通配符共有两种,问号和星号。“?”表示一个任意字符,而“*”则表示任意个任意字符。
首先来想问号的处理,子串扫描到问号时,不管母串是什么字符我们都认为它们是匹配的就行了。就是在匹配条件中加一条,变成s[i]==t[j]||t[j]==’?’
(假设s是母串、t是子串),就行了。
问题的重点是星号,当扫描到星号时,母串从当前位置之后的所有字符都要一一和子串的下一个字符比对,这样才能真正表述“*”的含义。显然,我们就要记住所有将要比对的位置,然后再一一处理,这就需要栈或者队列帮忙了。我就选择了队列。如果,接下来的处理中又遇到了“*”,同样入队。直到队列为空,所有的比对工作才真正完成了。
为了协调各种情况,就统一采用只要需要比对就入队,比对前先出队的方法。这样就可以适应不管是否含通配符的多种情况了。这里入队的元素是字符串的下标。
【算法实现】
程序依以下流程编写:1.用一个大循环来读取母串。2.先将量字符串的开头位置入队。3.用一个
while循环来读去队列元素,直到对空才停止。4.比对之前先出队元素。5.如果两位置上的字符相同(含字串字符是“?”),我们将各自的下一位置入
队。6.如果遇到“*”,则将母串从当前位置到结尾和子串的下一位置依次入队。7.以上两种情况中,如果子串读到了末尾,那么匹配成功,可以输出结果了。
8.如果字符不相同,不作任何处理;如果母串扫描完毕,子串还没有扫完,同样不作任何处理。
还有一些细节需要设计:1.母串和子串的位置用一个结构存储,出队入队用这个结构操作。2.结果输出我采用了颜色标记的方法,另用函数实现。3.多个连续的“*”按一个处理。4.用s存储母串,用t存储子串,更改这两个字符串,多次运行。
【源代码】
#include
<stdio.h>
#include
<conio.h>
#include
<stdlib.h>
typedef struct
{
int s,t;
}STRINFO;
typedef STRINFO Elemtype;
typedef struct
{
Elemtype
*Elem;
int
Front,Rear;
int Size;
}SQQUEUE;
int InitSqqueue(SQQUEUE *q,int n)
{
q->Elem=(Elemtype*)malloc((n+1)*sizeof(Elemtype));
if(q->Elem==NULL) return 0;
q->Front=q->Rear=0;
q->Size=n+1;
return 1;
}
void DestroySqqueue(SQQUEUE *q)
{
free(q->Elem);
q->Elem=NULL;
q->Front=q->Rear=0;
q->Size=0;
}
int IsSqqueueEmpty(SQQUEUE q)
{
return
q.Front==q.Rear;
}
int IsSqqueueFull(SQQUEUE q)
{
return
q.Front==(q.Rear+1)%q.Size;
}
int EnSqqueue(SQQUEUE *q,Elemtype e)
{
if(IsSqqueueFull(*q)) return 0;
q->Elem[q->Rear]=e;
q->Rear=(q->Rear+1)%q->Size;
return 1;
}
int DeSqqueue(SQQUEUE *q,Elemtype *e)
{
if(IsSqqueueEmpty(*q)) return 0;
*e=q->Elem[q->Front];
q->Front=(q->Front+1)%q->Size;
return 1;
}
void ShowStr(char *Str,int Begin,int End)
{
int i;
textcolor(LIGHTGRAY);
printf("From
%2d To %2d : ",Begin,End);
for(i=0;Str[i]!=0;i++)
{
if(i==Begin) textcolor(YELLOW);
cprintf("%c",Str[i]);
if(i==End) textcolor(LIGHTGRAY);
}
printf("\n");
}
void InStr(char *s,char *t)
{
int i=0,j;
STRINFO
Info;
SQQUEUE q;
InitSqqueue(&q,100);
while(s[i]!=0)
{
Info.s=i;Info.t=0;
EnSqqueue(&q,Info);
while(!IsSqqueueEmpty(q))
{
DeSqqueue(&q,&Info);
if(s[Info.s]==t[Info.t]||t[Info.t]=='?')
{
if(t[Info.t+1]==0)
{
ShowStr(s,i,Info.s);
}
else
{
Info.s++;Info.t++;
if(s[Info.s]!=0) EnSqqueue(&q,Info);
}
}
else if(t[Info.t]=='*')
{
while(t[Info.t]=='*')
Info.t++;
for(j=Info.s;s[j]!=0;j++)
{
if(t[Info.t]==0)
ShowStr(s,i,j);
else
{
Info.s=j;
EnSqqueue(&q,Info);
}
}
}
}
i++;
}
DestroySqqueue(&q);
}
void main()
{
char
s[80]="aabcdabcdabceab";
char
t[80]="a?b*c";
clrscr();
printf("Mother
Str: %s\n",s);
printf(" Child
Str: %s\n",t);
InStr(s,t);
}
这里是完整版,复制下来就可以直接运行了。下面给出一些测试用例,可以检测这个算法。
Mother
Str : aabcdabcdabceab
Child
Str : a?b*c
Child
Str : abc?
Child
Str : a*a*a
Child
Str : ???
Child
Str : **
Child
Str : a*
Child
Str : *b
Child
Str : abcdabce
【总结】
其实,这个程序是含有通配符的简单字符串匹配的算法。至于含有“?”的KMP算法,该一个匹配条件就行
了。而含有“*”的KMP是求不出Next值的。譬如说:a*abcd,c的Next是什么呢?如果“*”表示空白,其值为0;如果“*”表示be,其值
应该是e的下标而不是b的下标,但是be都是一个下标,算那个呢?含有“*”的子串求不出Next值。