6.1 串的实现详解和朴素的模式匹配算法
文章目录
6.1.1 串的简介
串是由零个或多个字符组成的有序序列,又称字符串。一般记为s=“a1a2a3···an”(n>=0),s是串的名称,串的字符数目n称为串的长度,零个字符的串称为空串。
6.1.2 串的顺序存储结构定义
串的顺序存储结构是用一组地址连续的存储单元来存储串中的字符序列,如下图所示:
数组下标为0的位置存储串的长度。
代码实现串也十分简单,利用字符数组即可,代码如下:
typedef char String[MAXSIZE+1]; //0号单元存放串的长度
6.1.3 串的各种操作
如下表所示:
操作函数 | 操作结果 |
---|---|
StrAssign(String T,char *chars) | 生成一个其值等于字符串常量chars的串T |
StrCopy(String T,String S) | 复制串S到T |
ClearString(String S) | 清空串 |
StrEmpty(String S) | 判断串是否为空 |
StrLength(String S) | 获取串S长度 |
Concat(String T,String S1,String S2) | S1和S2连结成T |
SubString(String Sub,String S,int pos,int len) | 用sub返回串s的第pos个字符起长度为len的子串 |
StrCompare(String S,String T) | 字符串比较 |
Index(String S,String T,int pos) | 返回子串T在主串S中第pos个字符之后的位置 |
StrInsert(String S,int pos,String T) | 在串S的第pos个字符之前插入串T |
StrDelete(String S,int pos,int len) | 从串S中删除第pos个字符起长度为len的子串 |
Replace(String S,String T,String V) | 用V替换主串S中出现的所有与T相等的不重叠的子串 |
StrPrint(String T) | 输出字符串T |
6.1.4 串的部分简单操作详解
1. 生成串T
生成一个其值等于字符串常量chars的串T实现步骤如下:
- 首先判断chars数组是否超过数组最大长度
- 将chars的长度存入T[0]处
- 利用for循环,逐个将chars的字符复制到串T中
代码如下:
/*生成一个其值等于字符串常量chars的串T*/
Status StrAssign(String T,char *chars){
int i;
if(strlen(chars) > MAXSIZE) //chars长度超过串T最大存储空间
return ERROR;
else{
T[0] = strlen(chars);
for(i=1;i<=T[0];i++)
T[i] = *(chars+i-1); //将chars赋值给T
return OK;
}
}
2. 复制串
串S存在,将串S复制得串T,用for循环将串S每个字符赋值给T即可,包括用来存长度的首元素。
代码如下:
/*串S存在,将串S复制得串T*/
Status StrCopy(String T,String S){
for(int i=0;i<=S[0];i++){
T[i] = S[i];
}
return OK;
}
3. 清空串
代码如下:
/*清空串*/
Status ClearString(String S){
S[0] = 0;
return OK;
}
4. 判断串是否为空
判断串空利用串的首元素索引就可以,代码如下:
/*判断串是否为空*/
Status StrEmpty(String S){
if(S[0]==0)
return TRUE;
else
return FALSE;
}
5. 获取串长度
S[0]处存放了串S的长度,代码如下:
/*返回长度*/
int StrLength(String S){
return S[0];
}
6. 连结串
用T返回S1,S2连结而成的新串 若未截断返回TRUE,否则FALSE。
- 未截断的判断条件
S1[0]+S2[0]<=MAXSIZE
- 截断的判断条件
S1[0]+S2[0]>MAXSIZE
代码如下:
Status Concat(String T,String S1,String S2){
int i;
if(S1[0]+S2[0]<=MAXSIZE){
//未截断
for(i=1;i<=S1[0];i++)
T[i] = S1[i];
for(i=1;i<=S2[0];i++)
T[S1[0]+i] = S2[i];
T[0] = S1[0]+S2[0];
return TRUE;
}else{
//截断S2
for(i=1;i<=S1[0];i++)
T[i] = S1[i];
for(i=1;i<=MAXSIZE-S1[0];i++)
T[S1[0]+i] = S2[i];
T[0] = MAXSIZE;
return FALSE;
}
}
7. 获取子串
用sub返回串s的第pos个字符起长度为len的子串,代码如下:
Status SubString(String Sub,String S,int pos,int len){
int i;
if(pos<1 || pos>S[0] || len<0 || len>S[0] || len>S[0]-pos+1)
return ERROR;
for(i=1;i<=len;i++)
Sub[i] = S[pos+i-1];
Sub[0] = len;
return OK;
}
特别注意,if语句里面的内容,pos和len必须在一定范围内。
8. 比较两串
代码如下:
/*字符串比较 若S>T,返回值>0,若S<T,返回值<0,S=T,返回值=0*/
int StrCompare(String S,String T){
for(int i=1;i<=S[0]&&i<=T[0];i++){
if(S[i]!=T[i])
return S[i]-T[i];
}
return S[0]-T[0];
}
9. 输出串
代码如下:
/* 输出字符串T */
void StrPrint(String T){
int i;
for(i=1;i<=T[0];i++)
printf("%c",T[i]);
printf("\n");
}
6.1.5 朴素的模式匹配算法
朴素的模式匹配算法就是一个在主串中找相匹配的子串的操作,图解如下:
-
当
S[i]!=T[j]
时,j回到串的第一个位置,即j=1
,而i回到上一个匹配成功的首位的下一位。 -
当
S[i]=T[j]
,i和j都加1,向前继续匹配。 -
最后一个字符匹配完成后,i和j还要加1,最后返回子串T在主串S中第pos个字符(图中是第一个位置)之后的位置,
i-T[0]
,图中i-T[0]=5-2=3
代码如下:
/* 返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回值为0。 */
/* 其中,T非空,1≤pos≤StrLength(S)。 */
int Index(String S,String T,int pos){
int i = pos;
int j = 1;
while(i<=S[0] && j<=T[0]){
if(S[i]==T[j]){
//两字母相同则继续
++i;
++j;
}else{
//字母不同,指针后退重新开始匹配
i = i-j+2; //i回到上次匹配首位的下一位
j = 1; //j回到子串T首位
}
}
if(j > T[0])
return i-T[0];
return 0;
}
6.1.6 串的插入
在串S的第pos个字符之前插入串T,这边有两种情况:
- 完全插入,当
S[0]+T[0]<=MAXSIZE
- 部分插入
代码如下:
/* 初始条件: 串S和T存在,1≤pos≤StrLength(S)+1 */
/* 操作结果: 在串S的第pos个字符之前插入串T。完全插入返回TRUE,部分插入返回FALSE */
Status StrInsert(String S,int pos,String T){
int i;
if(pos<1 || pos>S[0]+1)
return ERROR;
if(S[0]+T[0]<=MAXSIZE){
//完全插入
for(i=S[0];i>=pos;i--)
S[i+T[0]] = S[i];
for(i=pos;i<pos+T[0];i++)
S[i] = T[i-pos+1];
S[0] = S[0]+T[0];
return TRUE;
}else{
//部分插入
for(i=MAXSIZE;i<=pos;i--)
S[i]=S[i-T[0]];
for(i=pos;i<pos+T[0];i++)
S[i]=T[i-pos+1];
S[0]=MAXSIZE;
return FALSE;
}
}
6.1.7 串的删除和替换
1. 删除子串
从串S中删除第pos个字符起长度为len的子串,代码如下:
/* 初始条件: 串S存在,1≤pos≤StrLength(S)-len+1 */
/* 操作结果: 从串S中删除第pos个字符起长度为len的子串 */
Status StrDelete(String S,int pos,int len){
int i;
if(pos<1 || pos>S[0]-len+1 || len<0)
return ERROR;
for(i=pos+len;i<=S[0];i++)
S[i-len] = S[i];
S[0]-=len;
return OK;
}
2. 替换子串
用V替换主串S中出现的所有与T相等的不重叠的子串,这里会调用朴素的模式匹配算法来匹配子串。
代码如下:
/* 初始条件: 串S,T和V存在,T是非空串(此函数与串的存储结构无关) */
/* 操作结果: 用V替换主串S中出现的所有与T相等的不重叠的子串 */
Status Replace(String S,String T,String V){
int i=1; //从串S的第一个字符开始查找T
if(StrEmpty(T)) //如果T是空串
return ERROR;
do{
i = Index(S,T,i); //i为从上一个i之后找到的子串T的位置
if(i){ //S中找到串T了
StrDelete(S,i,StrLength(T)); //删除该串
StrInsert(S,i,V);
i+=StrLength(V);
}
} while (i);
return OK;
}
6.1.8 测试
测试代码:
int main(){
int i,j;
Status k;
char s;
String t,s1,s2;
printf("请输入串s1: ");
k=StrAssign(s1,"abcd");
if(!k)
{
printf("串长超过MAXSIZE(=%d)\n",MAXSIZE);
exit(0);
}
printf("串长为%d 串空否?%d(1:是 0:否)\n",StrLength(s1),StrEmpty(s1));
StrCopy(s2,s1);
printf("拷贝s1生成的串为: ");
StrPrint(s2);
printf("请输入串s2: ");
k=StrAssign(s2,"efghijk");
if(!k)
{
printf("串长超过MAXSIZE(%d)\n",MAXSIZE);
exit(0);
}
i=StrCompare(s1,s2);
if(i<0)
s='<';
else if(i==0)
s='=';
else
s='>';
printf("串s1%c串s2\n",s);
k=Concat(t,s1,s2);
printf("串s1联接串s2得到的串t为: ");
StrPrint(t);
if(k==FALSE)
printf("串t有截断\n");
ClearString(s1);
printf("清为空串后,串s1为: ");
StrPrint(s1);
printf("串长为%d 串空否?%d(1:是 0:否)\n",StrLength(s1),StrEmpty(s1));
printf("求串t的子串,请输入子串的起始位置,子串长度: ");
i=2;
j=3;
printf("%d,%d \n",i,j);
k=SubString(s2,t,i,j);
if(k)
{
printf("子串s2为: ");
StrPrint(s2);
}
printf("从串t的第pos个字符起,删除len个字符,请输入pos,len: ");
i=4;
j=2;
printf("%d,%d \n",i,j);
StrDelete(t,i,j);
printf("删除后的串t为: ");
StrPrint(t);
i=StrLength(s2)/2;
StrInsert(s2,i,t);
printf("在串s2的第%d个字符之前插入串t后,串s2为:\n",i);
StrPrint(s2);
i=Index(s2,t,1);
printf("s2的第%d个字母起和t第一次匹配\n",i);
SubString(t,s2,1,1);
printf("串t为:");
StrPrint(t);
Concat(s1,t,t);
printf("串s1为:");
StrPrint(s1);
Replace(s2,t,s1);
printf("用串s1取代串s2中和串t相同的不重叠的串后,串s2为: ");
StrPrint(s2);
system("pause");
return 0;
}
运行结果:
请输入串s1: 串长为4 串空否?0(1:是 0:否)
拷贝s1生成的串为: abcd
请输入串s2: 串s1<串s2
串s1联接串s2得到的串t为: abcdefghijk
清为空串后,串s1为:
串长为0 串空否?1(1:是 0:否)
求串t的子串,请输入子串的起始位置,子串长度: 2,3
子串s2为: bcd
从串t的第pos个字符起,删除len个字符,请输入pos,len: 4,2
删除后的串t为: abcfghijk
在串s2的第1个字符之前插入串t后,串s2为:
abcfghijkbcd
s2的第1个字母起和t第一次匹配
串t为:a
串s1为:aa
用串s1取代串s2中和串t相同的不重叠的串后,串s2为: aabcfghijkbcd
请按任意键继续. . .
6.1.9 完整代码
下面附上完整代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 40
typedef int Status;
typedef int ElemType;
typedef char String[MAXSIZE+1]; //0号单元存放串的长度
/*生成一个其值等于字符串常量chars的串T*/
Status StrAssign(String T,char *chars){
int i;
if(strlen(chars) > MAXSIZE) //chars长度超过串T最大存储空间
return ERROR;
else{
T[0] = strlen(chars);
for(i=1;i<=T[0];i++)
T[i] = *(chars+i-1); //将chars赋值给T
return OK;
}
}
/*串S存在,将串S复制得串T*/
Status StrCopy(String T,String S){
for(int i=0;i<=S[0];i++){
T[i] = S[i];
}
return OK;
}
/*清空串*/
Status ClearString(String S){
S[0] = 0;
return OK;
}
/*判断串是否为空*/
Status StrEmpty(String S){
if(S[0]==0)
return TRUE;
else
return FALSE;
}
/*返回长度*/
int StrLength(String S){
return S[0];
}
/*字符串比较 若S>T,返回值>0,若S<T,返回值<0,S=T,返回值=0*/
int StrCompare(String S,String T){
for(int i=1;i<=S[0]&&i<=T[0];i++){
if(S[i]!=T[i])
return S[i]-T[i];
}
return S[0]-T[0];
}
/*用T返回S1,S2连结而成的新串 若未截断返回TRUE,否则FALSE*/
Status Concat(String T,String S1,String S2){
int i;
if(S1[0]+S2[0]<=MAXSIZE){
//未截断
for(i=1;i<=S1[0];i++)
T[i] = S1[i];
for(i=1;i<=S2[0];i++)
T[S1[0]+i] = S2[i];
T[0] = S1[0]+S2[0];
return TRUE;
}else{
//截断S2
for(i=1;i<=S1[0];i++)
T[i] = S1[i];
for(i=1;i<=MAXSIZE-S1[0];i++)
T[S1[0]+i] = S2[i];
T[0] = MAXSIZE;
return FALSE;
}
}
/*用sub返回串s的第pos个字符起长度为len的子串*/
Status SubString(String Sub,String S,int pos,int len){
int i;
if(pos<1 || pos>S[0] || len<0 || len>S[0] || len>S[0]-pos+1)
return ERROR;
for(i=1;i<=len;i++)
Sub[i] = S[pos+i-1];
Sub[0] = len;
return OK;
}
/* 返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回值为0。 */
/* 其中,T非空,1≤pos≤StrLength(S)。 */
int Index(String S,String T,int pos){
int i = pos;
int j = 1;
while(i<=S[0] && j<=T[0]){
if(S[i]==T[j]){
//两字母相同则继续
++i;
++j;
}else{
//字母不同,指针后退重新开始匹配
i = i-j+2; //i回到上次匹配首位的下一位
j = 1; //j回到子串T首位
}
}
if(j > T[0])
return i-T[0];
return 0;
}
/* 初始条件: 串S和T存在,1≤pos≤StrLength(S)+1 */
/* 操作结果: 在串S的第pos个字符之前插入串T。完全插入返回TRUE,部分插入返回FALSE */
Status StrInsert(String S,int pos,String T){
int i;
if(pos<1 || pos>S[0]+1)
return ERROR;
if(S[0]+T[0]<=MAXSIZE){
//完全插入
for(i=S[0];i>=pos;i--)
S[i+T[0]] = S[i];
for(i=pos;i<pos+T[0];i++)
S[i] = T[i-pos+1];
S[0] = S[0]+T[0];
return TRUE;
}else{
//部分插入
for(i=MAXSIZE;i<=pos;i--)
S[i]=S[i-T[0]];
for(i=pos;i<pos+T[0];i++)
S[i]=T[i-pos+1];
S[0]=MAXSIZE;
return FALSE;
}
}
/* 初始条件: 串S存在,1≤pos≤StrLength(S)-len+1 */
/* 操作结果: 从串S中删除第pos个字符起长度为len的子串 */
Status StrDelete(String S,int pos,int len){
int i;
if(pos<1 || pos>S[0]-len+1 || len<0)
return ERROR;
for(i=pos+len;i<=S[0];i++)
S[i-len] = S[i];
S[0]-=len;
return OK;
}
/* 初始条件: 串S,T和V存在,T是非空串(此函数与串的存储结构无关) */
/* 操作结果: 用V替换主串S中出现的所有与T相等的不重叠的子串 */
Status Replace(String S,String T,String V){
int i=1; //从串S的第一个字符开始查找T
if(StrEmpty(T)) //如果T是空串
return ERROR;
do{
i = Index(S,T,i); //i为从上一个i之后找到的子串T的位置
if(i){ //S中找到串T了
StrDelete(S,i,StrLength(T)); //删除该串
StrInsert(S,i,V);
i+=StrLength(V);
}
} while (i);
return OK;
}
/* 输出字符串T */
void StrPrint(String T){
int i;
for(i=1;i<=T[0];i++)
printf("%c",T[i]);
printf("\n");
}
int main(){
int i,j;
Status k;
char s;
String t,s1,s2;
printf("请输入串s1: ");
k=StrAssign(s1,"abcd");
if(!k)
{
printf("串长超过MAXSIZE(=%d)\n",MAXSIZE);
exit(0);
}
printf("串长为%d 串空否?%d(1:是 0:否)\n",StrLength(s1),StrEmpty(s1));
StrCopy(s2,s1);
printf("拷贝s1生成的串为: ");
StrPrint(s2);
printf("请输入串s2: ");
k=StrAssign(s2,"efghijk");
if(!k)
{
printf("串长超过MAXSIZE(%d)\n",MAXSIZE);
exit(0);
}
i=StrCompare(s1,s2);
if(i<0)
s='<';
else if(i==0)
s='=';
else
s='>';
printf("串s1%c串s2\n",s);
k=Concat(t,s1,s2);
printf("串s1联接串s2得到的串t为: ");
StrPrint(t);
if(k==FALSE)
printf("串t有截断\n");
ClearString(s1);
printf("清为空串后,串s1为: ");
StrPrint(s1);
printf("串长为%d 串空否?%d(1:是 0:否)\n",StrLength(s1),StrEmpty(s1));
printf("求串t的子串,请输入子串的起始位置,子串长度: ");
i=2;
j=3;
printf("%d,%d \n",i,j);
k=SubString(s2,t,i,j);
if(k)
{
printf("子串s2为: ");
StrPrint(s2);
}
printf("从串t的第pos个字符起,删除len个字符,请输入pos,len: ");
i=4;
j=2;
printf("%d,%d \n",i,j);
StrDelete(t,i,j);
printf("删除后的串t为: ");
StrPrint(t);
i=StrLength(s2)/2;
StrInsert(s2,i,t);
printf("在串s2的第%d个字符之前插入串t后,串s2为:\n",i);
StrPrint(s2);
i=Index(s2,t,1);
printf("s2的第%d个字母起和t第一次匹配\n",i);
SubString(t,s2,1,1);
printf("串t为:");
StrPrint(t);
Concat(s1,t,t);
printf("串s1为:");
StrPrint(s1);
Replace(s2,t,s1);
printf("用串s1取代串s2中和串t相同的不重叠的串后,串s2为: ");
StrPrint(s2);
system("pause");
return 0;
}
6.1.10 总结
从这节,我们学会了朴素的模式匹配算法,但是一但匹配的长度上去了,这种算法太低效,因为当匹配不成功,主串的i总是要回到首次匹配的下一位置,而不是直接到匹配失败的位置,下一节,会补充解决这种方式的算法-----KMP匹配算法。