串(或者字符串)是由零个或者多个字符组成的有限序列,一般记为s=‘a1a2a3…an’(n>=0)其中,s是串的名,用单括号括起来的字符串序列是串的值;ai(1<=i<=n)可以是字母、数字或其他字符;串中字符的数目n称为串的长度。零个字符的串称为空串,它的长度为0。
目录
1. 串的概念
1.1 串的定义
串是由零个或者多个字符组成的有序序列。一般记为S=“a1a2…an”(n>=0)。当n=0时称为空串(注意与空格串区分)。
1.2 子串的定义
串中任意个连续的字符组成的子序列称为该串的子串。
只有当两个串的长度相等,并且各个对应位置上的字符都相等时才称这两个串是相等的。
另,串值必须用一对单括号括起来,但单括号本身不属于串,他的作用只是为了避免与变量名或数的常量混淆。
1.3 串与线性表的区别:
(1)串的数据对象限定为字符集;(英文字符——ASCII,中英文——Unicode)
(2)串的基本操作通常以子串为操作对象,而线性表以单个元素作为操作对象。
2. 串的基本操作
2.1 定长顺序储存表示
2.1.1 定义
用一组地址连续的储存单元存储串值得字符序列。在串的定长顺序存储结构中,按照预定义的大小,为每个定义的串变量分配一个固定长度的存储区。
2.1.2 代码如下
#define MAXLEN 255 //预定义最大串长255
typedef struct{
char ch[MAXLEN]; //每个分量存储一个字符
int length; //串的实际长度
}SString;
这里所用的顺序存储的字符串从下标为1的数组分量开始存储,下标为0的分量闲置不用。(本文采用的存储方式)
有的教程采用ch[0]充当length,缺点是只可以表示0-255的长度;还有在串值后面加一个不计入串长的结束标记字符‘\0’,此时的串长为隐含值,缺点是每次需遍历求串长。
2.1.1 串连接
串连接:把两个串s1和s2首尾连接成一个新串s,即s<-s1+s2
2.1.1.1 代码
int StrConcat(s1,s2,s)
char s1[],s2[],s[];//将串s1,s2合并到串s,合并成功返回1,否则返回0
{
int i =0,j,len1,len2;
len1 = StrLength(s1);
len2 = StrLength(s2);
if(len1+len2>MAXSIZE-1) return 0; //s 长度不够
j = 0 ;
while(s1[j]! ="\0"){
s[i] = s1[j];
i++;
j++;
}
while(s2[j]! ="\0"){
s[i] = s2[j];
i++;
j++;
}
s[i] = "\0";
return 1;
}
2.1.2 求子串
将串s中从第i个字符开始长度为len的字符序列复制到串s中,
2.1.2.1 代码
int StrSub(char *t ,char *s,int i , in len){
//用t返回串s中第i个字符串开始的长度为len的子串,1<=i串长
int slen;
slen = StrLength(s);
if(i<1 || i>slen || len<0 || len>slen-i+1){
return 0;
}
for(j =0 ; j<len ; j++){
t[j] =s[i+j-1];
t[j] ="\0";
return 1;
}
}
2.2 堆分配存储表示
2.2.1 定义
在顺序串上的插入、删除操作并不方便,必须移动大量的字符,而且当操作中出现串值序列的长度超过上界MAXSIZE时,只能用截尾法处理。要克服这个弊病,只有不限定串的最大长度,动态分配串值的存储空间。
堆分配存储结构的特点是:仍以一组地址连续的存储单元存放串的字符序列,但其存储空间是在算法执行过程中动态分配得到的。在C语言中,由动态分配函数malloc()和free()来管理。利用函数malloc()为每一个新产生的串分配一块实际需要的存储空间,若分配成功,则返回一个指针,指向串的起始地址。
由于堆分配存储结构的串既有顺序存储结构的特点,在操作中又没有串长的限制,显得很灵活,因此,在串处理的应用程序中常被选用。
2.2.2 代码如下
typedef struct{
char *ch; //按串长分配存储区,ch指向串的基地址
int length; //串的长度
}HString;
HString S;
S.ch=(char *)malloc(MAXLEN*sizeof(char));
S.length=0;
在C语言中,存在一个称为“堆”的自由存储区,并用malloc()和free()函数来完成动态存储管理。
2.2.3 基本操作
2.2.3.1 赋值操作
//赋值操作
void StrAssign(SString &str)
{
char cc;
int i=0;
scanf("%c",&cc);
while(cc!='#'&&i<MAXLEN)
{
i=i+1;
str.ch[i]=cc;
scanf("%c",&cc);
}
str.ch[i+1]='\0'; //字符串结束标记
str.length=i; //串长为i
}
2.2.3.2 求子串
bool SubString(SString &s1,SString &s,int pos,int len)
{
if(pos+len-1>s.length)//子串范围越界
return false;
for(int i=pos;i<pos+len;i++)
s1.ch[i-pos+1]=s.ch[i];
s1.length=len;
return true;
}
2.2.3.3 比较操作
//比较操作
int StrCompare(SString S,SString T)
{
//若S>T,则返回值>0;若S=T,则返回值=0,若S<T,则返回值<0
for(int i=1;i<=S.length&&i<T.length;i++)
{
if(S.ch[i]!=T.ch[i])
return S.ch[i]-T.ch[i];
}
//扫描过的所有字符都相同,则长度长的串更大
return S.length-T.length;
}
2.2.3.4 定位操作
//定位操作
int Index(SString S,SString T)
{
int i=1,n=S.length,m=T.length;
SString sub;//用于暂存子串
while(i<=n-m+1)
{
if(SubString(sub,S,i,m))
{
if(StrCompare(sub,T)!=0)
++i;
else
return i;//返回子串在主串的位置
}
}
return 0;//S中不存在与T相等的子串
}
2.3 串的块链存储表示
定义
串的链式存储与线性表相似,但由于串结构的特殊性,结构中的每个元素数据都是一个字符,如果也简单的应用链表存储串值,一个结点对应一个字符,就会存在很大的空间浪费,因此一个结点可以存放多个字符,最后一个结点若是未被占满时,可以用 “#” 或其它非串值字符补全。
为了便于进行串的操作,当以链表存储串值时,除头指针外还可附设一个尾指针指示链表中的最后一个结点,并给出当前串的长度。称如此定义的串存储结构为块链结构。
3.串的模式匹配
1、BF算法(又称古典的、经典的、朴素的、穷举的)
算法思想:
(1)将主串的第pos个字符和模式的第一个字符比较,若相等,继续逐个比较后续字符;若不等,从主串的下一字符起,重新与模式的第一个字符比较;
(2)直到主串的一个连续子串字符序列与模式相等 。返回值为S中与T匹配的子序列第一个字符的序号,即匹配成功。否则,匹配失败,返回值 0。
//简单模式匹配算法(BF)
int BFIndex(SString S,SString T)
{
int i=1,j=1;//i,j分别指向主串和模式串
while(i<=S.length&&j<=T.length)
{
if(S.ch[i]==T.ch[j])
{
i++;//继续比较后续字符
j++;
}
else
{
i++;//检查下一子串
j=1;
}
}
if(j>T.length)
return i-T.length;
else
return 0;
}
2、KMP算法(特点:速度快)
算法思想:
此算法可以在O(n+m)的时间数量级上完成串的模式匹配操作。其改进在于:每当一趟匹配过程中出现字符比较不等时,不需回溯i指针,而是利用已经得到的“部分匹配”的结果将模式向右“滑动”尽可能远的一端距离后,继续进行比较。
模式串的next函数的定义:
next[i][j]=k:
①当j=1(t1与si比较不等时,下一步进行t1与si+1的比较)时Max{k|1<k<j且有"t1t2. . .tk-1"=“tj-k+1tj-k+2. . .tj-1”}
②k=1(不存在相同子串,下一步进行t1与si的比较)
KMP算法描述:
int Index_KMP(Sstring S, Sstring T,int pos)
{
//利用模式串T的next函数求T在主串S中第pos个字符之后的位置
//其中,T非空,1<=pos<=S.length
i=pos;j=1;
while(i<=S.length&&j<=T.length) //两个串均未比较到串尾
{
if(j==0||S.ch[i]==T.ch[j]){++i; ++j;} //继续比较后继字符
else j=next[j]; //模式串向右移动
}
if(j>T.legnth)return i-T.length; //匹配成功
else return 0; //匹配失败
}
计算next函数值的算法描述:
void get_next(Sstring T,int next[])
{
//求模式串T的next函数值并存入数组next
i=1;next[1]=0;j=0;
while(i<T.length)
{
if(j==0||T.ch[i]==T.ch[j]){++i;++j;next[i]=j;}
else j=next[i][j];
}
}
计算next函数修正值的算法描述:
void get_nextval(Sstring T,int nextval[])
{
//求模式串T的next函数修正值并存入数组nextval
i=1;nextval[1]=0;j=0;
while(i<T.length)
{
if(j==0||T.ch[i]==T.ch[j])
{
++i;++j;
if(T.ch[i]!=T.ch[j])nextval[i]=j;
else nextval[i]=nextval[j];
}
else j=nextval[j];
}
}