字符串的匹配算法
一、BF算法
暴风(Brute Force)算法是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法,BF算法的时间复杂度为O(m*n)。
1.BF代码实现:
/*
* 程序名:kmp.c,此程序演示了字符串的匹配模式算法,包括:
* 1)普通的模式匹配算法,即BF算法;
* 2)KMP算法的求next数组;
* 3)KMP算法的求nextval数组;
* 4)KMP算法的实现。
*/
#include<stdio.h>
#include<string.h>
//BF算法
int index_bf(char sstr[],char tstr[]);
int main()
{
printf("请输入带匹配的目标串:");
char sstr[101]; memset(sstr,0,sizeof(sstr));
fgets(sstr,100,stdin);
sstr[strlen(sstr)-1]=0;
printf("请输入匹配模式串:");
char tstr[101]; memset(tstr,0,sizeof(tstr));
fgets(tstr,100,stdin);
tstr[strlen(tstr)-1]=0;
printf("目标串:%s,长度为%d\n",sstr,strlen(sstr));
printf("模式串:%s,长度为%d\n",tstr,strlen(tstr));
printf("BF算法检验到模式串在目标串中的位置为:%d\n",index_bf(sstr,tstr));
return 0;
}
// 采用BF算法,查找在目标串sstr中模式串tstr出现的位置,字符串的起始位置从0开始。
// 只要在目标串sstr中找到了第一个模式串tstr,函数就返回。
// 成功返回模式串tstr在目标串sstr中第一次出现的数组下标,失败返回-1。
int index_bf(char sstr[],char tstr[])
{
if( sstr==0 || tstr==0 )return -1;
int pos1,pos2; //双指针遍历字符串
pos1=pos2=0;
// "hello world"
// "ll0 "
while( pos1<strlen(sstr) && pos2<strlen(tstr) )
{
//逐个比较目标串和模式串的字符
//如果相等,模式串和目标串同时后移
if( sstr[pos1] == tstr[pos2] )
{
pos1++;
pos2++;
}
//如果不相等,目标串回溯到本次匹配失败的起始位置+1,模式串回溯到第一个字符
else{
pos1=pos1-pos2+1;
pos2=0;
}
}
//匹配成功,返回模式串在目标串中的第一个位置
if( pos2==strlen(tstr) )return pos1-strlen(tstr);
return -1;
}
测试实例:
二、KMP算法
1.定义
它的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。kmp算法的时间复杂度O(m+n)。
2.KMP算法的原理:
(1)在匹配的过程中,目标串的指不需要 回溯,只回溯模式串的指针;
(2)如果模式串和目标串前n个字符匹配成功,遇到匹配失败的字符时,模式串的指针回溯的位置由模式串的内容决定(回溯到 匹配失败位置前的模式串内容的最长公共前后缀 的长度+1),然后继续比较。
3.KMP算法的实现
(1)根据模式串的内容,生成一个遇到匹配失败的字符时模式串的指针回溯的位置的数组(next 数组)。
下面代码中函数getnext();实现了生产next数组;
(2)在BF算法上稍作修改,当匹配失败时,模式串的位置指针回溯next数组指示的位置。
代码实现:
/*
* 程序名:kmp.c,此程序演示了字符串的匹配模式算法,包括:
* 1)普通的模式匹配算法,即BF算法;
* 2)KMP算法的求next数组;
* 3)KMP算法的求nextval数组;
* 4)KMP算法的实现。
*/
//KMP算法
//根据模式串tstr,计算next数组,字符串的起始位置从0开始。
void getnext(char tstr[],int next[]);
int index_kmp(char sstr[],char tstr[]);
int main()
{
printf("请输入带匹配的目标串:");
char sstr[101]; memset(sstr,0,sizeof(sstr));
strcpy(sstr,"hello world");
fgets(sstr,100,stdin);
sstr[strlen(sstr)-1]=0;
printf("请输入匹配模式串:");
char tstr[101]; memset(tstr,0,sizeof(tstr));
strcpy(tstr,"llo");
fgets(tstr,100,stdin);
tstr[strlen(tstr)-1]=0;
printf("目标串%s,长度为%d\n",sstr,strlen(sstr));
printf("模式串:%s,长度为%d\n",tstr,strlen(tstr));
/*
int next[strlen(tstr)];
getnext(tstr,next);
printf("模式串next数组值为:");
int ii;
for(ii=0;ii<strlen(tstr);ii++)
{
printf("%3d ",next[ii]);
}
printf("\n");
*/
printf("KMP算法检验到模式串在目标串中的位置为:%d\n",index_kmp(sstr,tstr));
//printf("BF算法检验到模式串在目标串中的位置为:%d\n",index_bf(sstr,tstr));
return 0;
}
// 根据模式串tstr,计算next数组,字符串的起始位置从0开始。
void getnext(char tstr[],int next[])
{
if( tstr==0 || next==0 )return ;
int len=strlen(tstr); //获取模式串字符长度
//第一个位置固定填-1 第二个位置固定填0
if(len ==0 )return ;
if(len == 1){next[0]=-1; return ;}
if(len == 2){next[0]=-1; next[1]=0; return ;}
next[0]=-1;next[1]=0;
//从第三个位置开始计算next指针(最长公共前后缀的长度加1)
int ii;
char str1[1001];
char str2[1001];
int maxlen=0;
for(ii=2; ii<len; ii++)
{
int jj;
for(jj=1; jj<ii; jj++ )
{
memset(str1,0,sizeof(str1));
memset(str2,0,sizeof(str2));
strncpy(str1,tstr,jj); //获取前缀
strncpy(str2,tstr+ii-jj,jj); //获取后缀
//printf("前缀=%s,后缀=%s\t",str1,str2);
//前缀等于后缀,记录其长度
if( strcmp(str1,str2)==0 )maxlen=jj;
}
//printf("\n");
next[ii]=maxlen;
maxlen=0;
}
return ;
}
// 采用kmp算法,查找在目标串sstr中模式串tstr出现的位置,字符串的起始位置从0开始。
// // 只要在目标串sstr中找到了第一个模式串tstr,函数就返回。
// // 成功返回模式串tstr在目标串sstr中第一次出现的数组下标,失败返回-1。
int index_kmp(char sstr[],char tstr[])
{
if( sstr==0 || tstr==0 )return -1;
int pos1,pos2; //双指针遍历字符串
pos1=pos2=0;
// 获取next数组。
int next[strlen(tstr)];
getnext(tstr,next);
//int nextval[strlen(tstr)];
//getnextval(tstr,next,nextval);
int slen=strlen(sstr);
int tlen=strlen(tstr);
// "hello world"
// "ll0 "
//while( pos1<(strlen(sstr)) && (pos2<(strlen(tstr))) ) 用此行代码不行,第二次循环会直接跳出,原因不知道
while( pos1<slen && pos2<tlen )
{
//逐个比较目标串和模式串的字符
//如果相等,模式串和目标串同时后移
if( pos2==-1 || sstr[pos1]==tstr[pos2] )
{
pos1++;
pos2++;
}
else{
pos2=next[pos2];
pos2=nextval[pos2];
}
}
//匹配成功,返回模式串在目标串中的第一个位置
if( pos2 == strlen(tstr) )
{ return (pos1-strlen(tstr));}
return -1;
}
测试实例:
4.KMP算法的优化
用一个例子说明优化的必要性:
目标串: aaaabaaaacaaaadaaaaa
模式串: aaaaa
模式串next数组为: -1 0 1 2 3
第一次不匹配发生的位置 :
此时模式串进行回溯,回溯到 next[4]的位置,其值仍然为a
第二次发生不匹配的位置:
此时模式串继续回溯,回溯到 next[3]的位置,其值仍然为a,以此类推,直到回溯到next[0],目标串才会向后移动一个位置;
所以有必要对kmp算法进行优化,
在进行回溯的时候,如果发现 回溯位置的元素值 等于 当前匹配失败位置 的值,那么直接将该位置nextval的值 设置为待回溯的位置的nextval 的值 ;
用一个 nextval 数组来存放 每个位置 回溯的位置;
代码实现:
// 根据模式串tstr和next数组计算nextval数组,字符串的起始位置从0开始。
// void getnextval(char *tstr,int *next,int *nextval)
void getnextval(char tstr[],int next[],int nextval[])
{
if ( (tstr==0) || (next==0) || (nextval==0) ) return; // 判断空指针。
int tlen=strlen(tstr); // 模式串的长度。
nextval[0]=-1; // 位置0直接填入-1。
int ii;
// 从第1个位置开始扫描。
for (ii=1;ii<tlen;ii++)
{
//当前位置的元素值等于回溯位置的元素值
if (tstr[ii]==tstr[next[ii]])nextval[ii]=nextval[next[ii]];
else
nextval[ii]=next[ii];
}
}