《软件技术基础》实验指导 实验三
串
实验三 串
一、实验目的
-
熟悉串的顺序存储结构
-
掌握串的基本运算及应用
二、实验内容
-
串采用顺序存储结构,编写朴素模式匹配算法,查找在串中是否存在给定的子串。(3.1.1采用暴力匹配、KMP实现,3.1.2采用BM实现)
-
编写一个算法
void strDelete(seqstring*S,int pos,int len)
,从串S中删除第pos个字符开始长度为len的子串。要求:1≤pos≤S->length-len+1
Tips
-
暴力匹配算法:i指向文本串,j指向模式串。当匹配成功,继续匹配下一个字符;当匹配失败,令
i = i - (j - 1); j = 0;
。即失配,i 回溯,j 被置为0。(因gets()
作为字符串输入函数不安全,故改用scanf()
进行输入,字符串内不能有空格) -
KMP 算法:https://en.wikipedia.org/wiki/Knuth-Morris-Pratt_algorithm
-
BM 算法:https://en.wikipedia.org/wiki/Boyer-Moore_string_search_algorithm
-
删除函数运用循环直到长度条件满足即可,注意长度的改变。
Answer
3.1.1
//模式匹配的程序代码
#include<stdio.h>
#include<string.h>
//#include<malloc.h>
#include<stdlib.h>
//顺序串的结构类型定义
#define maxsize 100
typedef struct
{
char str[maxsize];
int len;
}seqstring;
int Index(seqstring*, seqstring*);
int Index2(seqstring*, seqstring*);
//void main()
int main()
{
seqstring*S,*subS;
S=(seqstring*)malloc(sizeof(seqstring));
subS=(seqstring*)malloc(sizeof(seqstring));
printf("输入串:");
//gets(S->str);
scanf("%s",S->str);
S->len=strlen(S->str);
printf("输入子串:");
//gets(subS->str);
scanf("%s",subS->str);
subS->len=strlen(subS->str);
if(Index2(S,subS)>0)
{
printf("匹配成功!\n");
}
else
{
printf("匹配失败!\n");
}
return 0;
}
//顺序串的朴素模式匹配
int Index(seqstring* s,seqstring* p)
{
int i = 0;
int j = 0;
while (i < s->len && j < p->len)
{
if (s->str[i] == p->str[j])
{
//如果当前字符匹配成功(即S[i] == P[j]),则i++,j++
i++;
j++;
}
else
{
//如果失配(即S[i]! = P[j]),令i = i - (j - 1),j = 0
i = i - j + 1;
j = 0;
}
}
//返回匹配结果
if (j == p->len)
{
return 1 + i - j;
}
else
{
return 0;
}
}
//顺序串的KMP匹配
int Index2(seqstring* s,seqstring* p)
{
int i = 0;
int j = 0;
void GetNext(seqstring*,int*);
int next[maxsize];
GetNext(p,next);
while (i < s->len && j < p->len)
{
//如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++
if (j == -1 || s->str[i] == p->str[j])
{
i++;
j++;
}
else
{
//如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]
//next[j]即为j所对应的next值
j = next[j];
}
}
//返回匹配结果
if (j == p->len)
{
return 1 + i - j;
}
else
{
return 0;
}
}
//顺序串的KMP匹配,求next数组函数
void GetNext(seqstring* p,int* next)
{
next[0] = -1;
int k = -1;
int j = 0;
while (j < p->len - 1)
{
//p[k]表示前缀,p[j]表示后缀
if (k == -1 || p->str[j] == p->str[k])
{
++k;
++j;
if (p->str[j] != p->str[k])
{
next[j] = k;
}
else
//因为不能出现p[j] = p[next[j]],所以当出现时需要继续递归,k = next[k] = next[next[k]]
{
next[j] = next[k];
}
}
else
{
k = next[k];
}
}
}
3.1.2
//模式匹配的程序代码
#include<stdio.h>
#include<string.h>
//#include<malloc.h>
#include<stdlib.h>
//顺序串的结构类型定义
#define maxsize 100
typedef struct
{
char str[maxsize];
int len;
}seqstring;
int Index(seqstring*, seqstring*);
//void main()
int main()
{
seqstring*S,*subS;
S=(seqstring*)malloc(sizeof(seqstring));
subS=(seqstring*)malloc(sizeof(seqstring));
printf("输入串:");
//gets(S->str);
scanf("%s",S->str);
S->len=strlen(S->str);
printf("输入子串:");
//gets(subS->str);
scanf("%s",subS->str);
subS->len=strlen(subS->str);
if(Index(S,subS)>0)
{
printf("匹配成功!\n");
}
else
{
printf("匹配失败!\n");
}
return 0;
}
//顺序串的BM匹配
int Index(seqstring* s,seqstring* p)
{
int* MakeSkip(char *ptrn, int pLen);
int* MakeShift(char* ptrn,int pLen);
int* skip=MakeSkip(p->str,p->len);
int* shift=MakeShift(p->str,p->len);
int s_idx = p->len;
if (p->len == 0)
{
return 1;
}
while (s_idx <= s->len)//计算字符串是否匹配到了尽头
{
int p_idx = p->len, skip_stride, shift_stride;
while (s->str[--s_idx] == p->str[--p_idx])//开始匹配
{
if (s_idx < 0)
{
return 0;
}
if (p_idx == 0)
{
return 1;
}
}
skip_stride = skip[(unsigned char)s->str[s_idx]];//根据坏字符规则计算跳跃的距离
shift_stride = shift[p_idx];//根据好后缀规则计算跳跃的距离
s_idx += (skip_stride > shift_stride) ? skip_stride : shift_stride;//取大者
}
return 0;
}
//根据坏字符规则做预处理,建立一张坏字符表
int* MakeSkip(char *ptrn, int pLen)
{
int i;
//为建立坏字符表,申请256个int的空间
//之所以要申请256个,是因为一个字符是8位,所以字符可能有2的8次方即256种不同情况
int *skip = (int*)malloc(256*sizeof(int));
if(skip == NULL)
{
fprintf(stderr, "malloc failed!");
return 0;
}
//初始化坏字符表,256个单元全部初始化为pLen,没有在模式串出现的字符距离为pLen。
for(i = 0; i < 256; i++)
{
*(skip+i) = pLen;
}
//给表中需要赋值的单元赋值,不在模式串中出现的字符就不用再赋值了
while(pLen != 0)
{
*(skip+(unsigned char)*ptrn++) = pLen--;
}
return skip;
}
//根据好后缀规则做预处理,建立一张好后缀表
int* MakeShift(char* ptrn,int pLen)
{
//为好后缀表申请pLen个int的空间
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--;//边界移动到倒数第二个字符
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所在字符为边界时,要移动的位置
pptr--;//边界继续向前移动
}
return shift;
}
3.2
//删除子串的程序代码
#include<stdio.h>
#include<string.h>
//#include<malloc.h>
#include<stdlib.h>
//顺序串的结构类型定义
#define maxsize 256
typedef struct
{
char str[maxsize];
int length;
}seqstring;
void strPut(seqstring*);
void strDelete(seqstring*,int,int);
//void main()
int main()
{
seqstring*S;
int pos,len;
S=(seqstring*)malloc(sizeof(seqstring));
printf("输入串:");
// gets(S->str);
scanf("%s",S->str);
S->length=strlen(S->str);
strPut(S);
printf("删除的开始位置:");scanf("%d",&pos);
printf("删除的字符个数:");scanf("%d",&len);
strDelete(S,pos,len);
strPut(S);
return 0;
}
//输出串
void strPut(seqstring*S)
{
int i;
for(i=0;i<S->length;i++)
printf("%c",S->str[i]);
printf("\n"); printf("Length:%d\n",S->length);
}
//添加删除子串算法
void strDelete(seqstring*S,int pos,int len)
{
int i;
if(pos < 0 || len < 0 || pos+len-1 > S->length)
{
printf("删除位置不正确\n");
}
else
{
for(i = pos+len;i <= S->length-1;i++)
{
S->str[i-len] = S->str[i];
}
S->length = S->length -len;//修改串S的长度
}
}