一个字符串是一个定义在有限字母表∑上的字符序列。例如,ABCDABC是字母表∑ = {A,BC,D}上的一个字符串。字符串匹配问题就是在一个大的字符串T中搜索某个字符串P的所有出现位置。其中,T称为文本(或称主串,模式串),P称为模式(或称子串),T和P都定义在同一个字母表∑上。
设文本为长度为n,用字符数组T[1..n]表示,模式串长度为m,m<=n,用字符数组P[1..m] 表示。
如果T[s+1..s+m] = P[1..m](即对1<=j<=m,有T[s+j]=P[j]),则说模式P在文本T中出现且位移为s。主串中共含有n-m+1个长度为m的子串,显然有0<=s<=n-m。如果模式P在文本T中出现并且位移为s,则称s是一个有效位移,否则称s为无效位移。所以字符串匹配问题就变成了在一段指定的文本T中,查找模式P出现的所有有效位移问题。
算法思想:把文本T中的n-m+1个模式分别和模式P进行比较,也就是说对位移s的n-m+1个可能的取值,依次比较两个子串,如果匹配,则s为一个有效位移,输出s。
算法伪代码:
Native-String-Matcher(T,P)
{
n = length[T];
m = length[P];
for s=0 to n-m
if(T[s+1..s+m] = P[1..m])
print”Pattern occurs with shift”s
}
算法分析:
从伪代码可知,比较两个长度为m的子串,复杂度为O(m),而外层循环n-m+1次,所以本算法的运行时间为O((n-m+1)m)。
一个简单的C++实现如下:
void Native_String_Matcher(const char* T, const char* P)
{
int n = strlen(T);
int m = strlen(P);
for(int s=0; s<=n-m; s++)
{
if(strncmp(T+s,P,m) == 0)//比较m个字符
{
cout<<"Pattern occurs with shift"<<s<<endl;
}
}
}
另一个简单C++实现如下:
/*
Description:在文本T中,从下标pos处开始查找模式P
Return:若找到,则返回第一次匹配的(首字符)下标位置 ,否则返回-1;
*/
int index(const char* T, const char* P, int pos)
{
int n = strlen(T);
int m = strlen(P);
int i = pos;
int j= 0;
while(i<n && j<m)
{
if(T[i]==P[j])
{
i++;
j++;
}
else
{
i = i-j+1; //匹配失败,指针回溯
j = 0;
}
}
if(j==m)
return i-m;
return -1;
}
这个实现可能不如第一个实现那么清晰,但是本质上是一样的。该程序初始化i=pos,j=0,然后开始逐一比较对应字符值,若相等则指针i和j都向后移动,若不相等则指针回溯。i=i-j+1;也就是把本次扫描前指针i的位置(通过此时i的值减去移动的值j求得)向后移动一位,相当于把模式向右移一位。
循环结束时可能有两种情况:(1)j==m,也就是说找到了第一个匹配的子串,我们返回下标值就可以。(2)i==n且j!=m,也就是说查找完了文本,但是没有找到匹配的子串,出现此种情况是因为当位移s> n-m的时候,文本中剩余的子串长度不足m。
测试程序如下:
#include <cstdlib>
#include <iostream>
#include <cstring>
using namespace std;
int main(int argc, char *argv[])
{
const int Max_Length = 1000;
char T[Max_Length];
char P[Max_Length];
while(gets(T))
{
gets(P);
//Native_String_Matcher(T,P);
//cout<<"next case:"<<endl;
cout<<"first match at "<<index(T,P,0)<<endl;
}
system("PAUSE");
return EXIT_SUCCESS;
}
注:
(1)所有程序都是在DEV-C++上测试通过的,除非特别说明。
(2)由于使用的是C的字符输入函数gets(),输入时文本和模式应该以回车符作为分隔。