实验:
字符串的匹配算法
1 测试串的生成
(1)随机生成 10,000 个 0~9 十个数字字符构成的文本串 s0。
(2)模式串 p 定义为 2269162268。
(2.1)令 i=0
(2.2)将模式串 p 插入 si的一个随机位置之后,生成新的串 si+1。
(2.3) i = i+1
(2.4)转到(2.2),直到 i=10
(2.5)记最后得到的串为 s*
(3) 将你的学号分别插入文本串 s*的开始位置和结束位置,使得新的文本串 s 以你的学
号开始和结束。
(4) 以文件形式输出文本串 s。
2 字符串的匹配实现
读取以文件形式存储的串,分别应用蛮力算法(BF)、KMP 算法和 BM 算法,找出模式串
p 在文本串 s 中的所有出现位置,并分别输出对应的比较次数。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
typedef struct str{
int textstr[10];
}str; //以十个随机数为一组的文本串
typedef struct string{
str s[10100];
int length;
}string; //题目中数据串*s
void input(string *L)
{//生成s0
int a;
L->length=0; //初始化为0
srand(time(NULL));
for(int i=1;i<=10000;i++) //s[0]留出存学号
{
for(int k=0;k<10;k++)
L->s[i].textstr[k]=rand()%(10); //int num=rand()%(n-m+1)+m 让随机数在[m,n]范围
L->length++;
}
}
void print(string *L)
{//在终端打印
int i;
for(i=0;i<=L->length;i++)
{
printf("s[%d]:",i);
for(int k=0;k<10;k++)
printf("%d",L->s[i].textstr[k]);
printf("\n");
}
printf("length:%d\n",L->length);
}
void insert(string *L,int *p,int *q)
{//插入模式串p和学号
int a,k;
// printf("the locationg p insert:");
for(int i=0;i<10;i++,L->length++) //p插入重复十次
{
a=rand()%(10000)+1; //从1~10000中随机选一个位置插入
// printf("%d ",a);
for(k=L->length;k>=a;k--)
{//a后的元素全部后移
for(int j=0;j<10;j++)
L->s[k+1].textstr[j]=L->s[k].textstr[j];
}
for(int j=0;j<10;j++) //p插入
L->s[a].textstr[j]=p[j];
}
for(int i=0;i<10;i++)
{//插入学号到首位与末位
L->s[0].textstr[i]=q[i];
L->s[L->length+1].textstr[i]=q[i];
}
L->length++;
}
void filewrite(string *L)
{//生成文件并输出串s
FILE *fp;
if((fp=fopen("fout.txt","a"))==NULL) //打开输出文件并使fp指向此文件
{//a 操作对文件内容追加
printf("cannot open file\n");
exit(0); //若不成功,终止程序
}
for(int i=0;i<=L->length;i++)
{
for(int j=0;j<10;j++) //每一行为一个s[]
fprintf(fp,"%d",L->s[i].textstr[j]); //文件内容写入
}
fclose(fp);
}
char *fileread_translate()
{//从文件中读取文本,从而对复杂结构体进行转化,同时将整型转化为字符型
FILE *fp;
char s[10012];
if((fp=fopen("fout.txt","r"))==NULL) //打开输出文件并使fp指向此文件
{
printf("cannot open file\n");
exit(0); //若不成功,终止程序
}
char buf[1000111] = { 0 };
char *p; //p用于返回buf数组的内容
while (!feof(fp)) //没有到文件末尾
{
memset(buf, 0, sizeof(buf)); //初始化数组为0
fread(buf, sizeof(char), sizeof(buf), fp);
}
fclose(fp);
strcpy(p,buf);
return p; //通过指针返回一个数组
}
/*BF算法*/
int BF(char *s,char *t)
{//BF算法 s是原字符串,t是匹配字符串
int m,n;
int i=0,j=0,k=0; //从0位置开始匹配
m= strlen(s);
n = strlen(t);
// printf("%d %d",m,n);
while(i<=m)
{
while (i<=m&&j<n) //m,n是串长
{
k++;
if (s[i]==t[j])
{
i++;
j++; //逐个匹配,成功s++ t++
// printf("%d %d\n",i-1,j);
}
else
{
i=i-j+1; //不成功,s返回到此次循环匹配的初始位置
j=0; //不成功,t返回到0位置
// printf("%d %d\n",i,j);
}
}
if(j>n-1)
{
printf("\nlocation:%d",i-n);
i=i-j+1; //不成功,s返回到此次循环匹配的初始位置
j=0; //不成功,t返回到0位置
}
}
return k; //返回比较次数
}
/*KMP算法*/
void get_next(char *s,int *next)
{//生成next表
int len=0;
int i=0;
int j=-1;
next[0]=-1; //首位为-1
len=strlen(s);
while(i<len-1)
{//当不超过模式串长度时
if(j==-1||s[i]==s[j])
{//若有数与前面的相等或为首位,next对应数值加一
i++;
j++;
next[i]=j;
}
else
{
j=next[j];
}
}
// printf("\n%d",next);
}
int KMP(char *s1,char *s2,int *next)
{//KMP主算法
int i=0;
int j=0;
int k=0;
int len1=strlen(s1);
int len2=strlen(s2);
while (i<len1){
while(i<len1&&j<len2)
{//当不超过数组长度时,一直执行
k++;//计数
if(j==-1||s1[i]==s2[j])
{//若匹配到了,往后移一位并重复比较
i++;
j++;
}
else
{
j=next[j];
}
// printf("%d %d\n",i,j);
}
if(j>=len2)
printf("location:%d\n",i-len2);
i++;
j=0;
}
return k;
}
/*BM算法*/
/*
函数:int* MakeSkip(char *, int)
目的:根据坏字符规则做预处理,建立一张坏字符表
参数:
ptrn => 模式串P
PLen => 模式串P长度
返回:
int* - 坏字符表
*/
int * MakeSkip( char *ptrn)
{//建立坏字符表,申请256个int的空间
int i;
int pLen=strlen(ptrn);
int *skip = ( int *)malloc(256* sizeof ( int ));
if (skip == NULL)
{
fprintf(stderr, "malloc failed!" );
return 0;
}
//初始化坏字符表,256个单元全部初始化为pLen
for (i = 0; i < 256; i++)
{
*(skip+i) = pLen;
}
//给表中需要赋值的单元赋值,不在模式串中出现的字符就不用再赋值了
while (pLen != 0)
{
*(skip+(unsigned char )*ptrn++) = pLen--;
}
return skip;
}
/*
函数:int* MakeShift(char *, int)
目的:根据好后缀规则做预处理,建立一张好后缀表
参数:
ptrn => 模式串P
PLen => 模式串P长度
返回:
int* - 好后缀表
*/
int * MakeShift( char * ptrn)
{//为好后缀表申请pLen个int的空间
int pLen=strlen(ptrn);
int *shift = ( int *)malloc(pLen* sizeof ( int ));
int *sptr = shift + pLen - 1; //方便给好后缀表进行赋值的指标
char *pptr = ptrn + pLen - 1; //记录好后缀表边界位置的指标
char c;
if (shift == NULL)
{
fprintf(stderr,"malloc failed!" );
return 0;
}
c = *(ptrn + pLen - 1); //保存模式串中最后一个字符,因为要反复用到它
*sptr = 1; //以最后一个字符为边界时,确定移动1的距离
pptr--; //边界移动到倒数第二个字符(这句是我自己加上去的,因为我总觉得不加上去会有BUG,大家试试“abcdd”的情况,即末尾两位重复的情况)
while (sptr-- != shift) //该最外层循环完成给好后缀表中每一个单元进行赋值的工作
{
char *p1 = ptrn + pLen - 2, *p2,*p3;
//该do...while循环完成以当前pptr所指的字符为边界时,要移动的距离
do {
while (p1 >= ptrn && *p1-- != c); //该空循环,寻找与最后一个字符c匹配的字符所指向的位置
p2 = ptrn + pLen - 2;
p3 = p1;
while (p3 >= ptrn && *p3-- == *p2-- && p2 >= pptr); //该空循环,判断在边界内字符匹配到了什么位置
}while (p3 >= ptrn && p2 >= pptr);
*sptr = shift + pLen - sptr + p2 - p3; //保存好后缀表中,以pptr所在字符为边界时,要移动的位置
/*
PS:*sptr = (shift + pLen - sptr) + p2 - p3;
括号括起来的部分,如果只需要计算字符串移动的距离,那么括号中的那部分是不需要的。
因为在字符串自左向右做匹配的时候,指标是一直向左移的,这里*sptr保存的内容,实际是指标要移动
距离,而不是字符串移动的距离。
*/
pptr--; //边界继续向前移动
}
return shift;
}
/*
函数:int* BMSearch(char *, int , char *, int, int *, int *)
目的:判断文本串T中是否包含模式串P
参数:
buf => 文本串T
blen => 文本串T长度
ptrn => 模式串P
PLen => 模式串P长度
skip => 坏字符表
shift => 好后缀表
返回:
int - 1表示成功(文本串包含模式串),0表示失败(文本串不包含模式串)。
*/
int BMSearch( char *buf,char *ptrn,int *skip,int *shift)
{
int i=0,j; //记录比较次数
int blen=strlen(buf);
int plen=strlen(ptrn);
int b_idx = plen; //b_idx赋值为模式串长度
while (b_idx <= blen)
{//计算字符串是否匹配到了尽头
int p_idx = plen, skip_stride, shift_stride;
i++;
while (buf[--b_idx] == ptrn[--p_idx])
{//开始匹配
i++;
if (p_idx == 0)
{
j=1; //标志已经查找到一处
if(b_idx>=0)
printf("location:%d\n",b_idx);
}
}
/*跳转*/
if(j==0) //若没有查找到
{
skip_stride = skip[(unsigned char )buf[b_idx]]; //根据坏字符规则计算跳跃的距离
shift_stride = shift[p_idx]; //根据好后缀规则计算跳跃的距离
b_idx += (skip_stride > shift_stride) ? skip_stride : shift_stride; //取大者
}
if(j==1) //若已经查找到,则b_idx移动到下一位,p_idx重置为10
{
b_idx+=plen*2;
p_idx=plen;
j=0;
}
}
return i;
}
int main()
{
int num[]={2,1,1,5,3,0,0,0,0,8};
int p[]={2,2,6,9,1,6,2,2,6,8};
string L;
input(&L); //生成s
insert(&L,p,num); //插入p与学号
// filewrite(&L); //以文件形式输出
char *buf=fileread_translate(); //读取文件
// printf("%s",buf); //buf为长度为10011*10的文本串,以下算法由它进行运算
printf("\n");
char *t="2269162268";
char *q="2115300008";
/*BF算法*/
printf("BF start:");
int k;
k=BF(buf,t);
printf("\n%d\n",k);
/*KMP算法*/
printf("\nKMP start:\n");
int next[11];
get_next(t,next);
k=KMP(buf,t,next);
printf("%d\n",k);
/*BM算法*/
printf("\nBM start:\n");
int *bad=MakeSkip(t);
int *good=MakeShift(t);
int BM=BMSearch(buf,t,bad,good);
printf("%d",BM);
}
代码计算的比较次数有问题,我一个小菜鸟不知道怎么改,有大佬懂的可以联系我!
BF算法KMP算法和BM算法都参考了其他大神的代码,我只是对代码进行包装以符合实验要求,因找的代码很多现在难以找到原始的参考代码了,在此抱歉,侵删。