1.串的基本概念
首先,我们看看以下几个词语的概念:
字符串(串):由零个或者多个字符组成的有限序列,由单引号括起来(C语言中是双引号),括起来的目的也很简单,就是避免与常量名或者常量混淆嘛
字串:串中任意连续的字符组成的子序列
主串:包含字串的串
字串在主串中的位置:通常以字串的第一个字符在主串中的位置来表示
串相等:当且仅当两个串的值相等时
2.串的存储实现
2.1定长顺序串
定长顺序串是将串设计成一种静态结构类型,串的存储分配是在编译时完成的,与线性表的顺序存储结构类似
2.1.1定长顺序串存储结构
#define MAXLEN 40
typedef struct{
char ch[MAXLEN]; //存储字符串的一维数组
int len; //字符串长度
}SString;
2.1.2定长顺序串的基本操作实现
(1)串插入函数:在串s中下标为pos的字符之前插入串t
#define MAXLEN 40
typedef struct{
char ch[MAXLEN]; //存储字符串的一维数组
int len; //字符串长度
}SString;
int StrInsert(SString *s,int pos,SString t)
{
int i;
if(pos<0||pos>s->len) return 0; //不管是插入还是删除操作,都应该检查以下插入和删除的位置合不合理,养成良好的习惯
/*第一种情况,插入完之后并没有超过最大长度,于是先将pos后面的字符通通后移,在插入字符串t*/
if(s->len+t.len<=MAXLEN){
for(i=s->len+t.len-1;i>=t.len+pos;i--) s->ch[i]=s->ch[i-t.len];
for(i=0;i<t.len;i++) s->ch[i+pos]=t.ch[i];
s->len=s->len+t.len;
}
/*第二种,插入完之后大于最大长度,但是字符串t不用舍弃*/
else if(pos+t.len<=MAXLEN){
for(i=MAXLEN-1;i>=t.len+pos;i--) s->ch[i]=s->ch[i-t.len];
for(i=0;i<t.len;i++) s->ch[i+pos]=t.ch[i];
s->len=MAXLEN;
}
/*第三种,就连字符串t都得舍弃一部分了*/
else{
for(i=0;i<MAXLEN-pos;i++) s->ch[i+pos]=t.ch[i];
s->len=MAXLEN;
}
}
(2)串删除函数:在串s中删除从下标pos起的len个字符
int StrDelete(SString *s,int pos,int len)
{
int i;
if(pos<0||pos>(s->len-len)) return 0; //删除参数不合理
for(i=pos+len;i<s->len;i++){
s->ch[i-len]=s->ch[i];
}
s->len=s->len-len;
return 1;
}
(3)串比较函数:比较串s和t是否相等,相等返回0,若s>t,返回正数,反之,返回负数
int StrCompare(SString s,SString t)
{
int i;
for(i=0;i<s.len&&i<t.len;i++){
if(s.ch[i]!=t.ch[i]) return (s.ch[i]-t.ch[i]); //这种返回正负数就返回两数之差的方法可以学学,可以简化很多操作(也可能只有我不知道,呜呜呜)
}
return (s.len-t.len); //假设上述都相等了,最后要判断的就是两个串的长度,所以直接返回两个串长度之差
}
2.1.3串的简单模式匹配Brute-Force算法
什么叫简单模式匹配,其实通俗化来讲就是一个个的匹配,来判断字符串是否为另一个字符串的字串,同时也可以找到该字符串在主串的位置
int StrIndex(SString s,int pos,SString t)
{/*求从主串s的下标pos起,串t第一次出现的位置,成功返回位置序号,失败返回-1*/
int i,j,start;
if(t.len==0) return 0; //模式串为空串时,是任意串的匹配串
start=pos;i=start;j=0; //主串从pos开始,模式串从头(0)开始
while(i<s.len&&j<t.len){
if(s.ch[i]==t.ch[j]) i++,j++;
else{
start++;
i=start;j=0; //当不匹配时,主串从下一个开始,t又从头开始,即回溯
}
}
if(j>=t.len) return start;
else return -1;
}
当然,是个明眼人都知道这个程序的效率很低,所用时间会很多,有了问题就肯定会有人解决问题,KMP算法就是人类智慧的结晶,这里不做赘述,感兴趣的朋友可以参考以下文章
上链接:https://blog.csdn.net/weixin_46007276/article/details/104372119
2.3堆串
通过前面的学习,我们知道了字符串包括串名和串值两个部分,而现在我们就采用符号表存储串名,用堆串存储串值,接下来,我们先了解新的两个词汇
符号表:所有串名的存储映像构成一个符号表。借助此结构可以在串名和串值之间建立一个对应关系,称为串名的存储映像
堆串:以一堆地址连续的存储单元顺序存放串中的字符,但他们的存储空间是在程序执行过程中动态分配的。系统将一个地址连续,容量很大的存储空间作为字符串的可用空间,每当建立一个新串时,系统就从该空间中分配以一个大小和字符串长度相同的空间用于存储新串的串值
C语言中已经有一个称为“堆”的自由存储空间,并可以用malloc和free函数完成动态存储管理。因此,可以使用C语言中的堆来实现堆串。定义如下:
typedef struct
{
char *ch; //表示串的起始地址
int len; //表示串的长度
}HString;
因为很多操作与定长顺序串差不多,所以以下就给出几个比较简单的操作
堆串插入函数:在串s中下标为pos的字符前插入串t
StrInsert(HString *s,HString *t,int pos)
{
int i;
char *temp;
if(pos<0||pos>s->len||s->len==0) return 0;
temp=(char*)malloc(s->len+t->len); //动态产生足够空间来存放,不用sizeof是因为字符本来就是一个字节的
if(temp==NULL) return 0; //申请空间失败
for(i=0;i<pos;i++) temp[i]=s->ch[i];
for(i=0;i<t->len;i++) temp[i+pos]=t->ch[i];
for(i=pos;i<s->len;i++) temp[i+t->len]=s->ch[i];
s->len+=t->len;
s->ch=temp; //修改结构体中的成员
return 1;
}
堆串赋值函数:将字符串常量tval的值赋给堆串s
StrAssign(HString *s, char *tval)
{
int len, i = 0;
while (tval[i] != '\0') i++; // 计算字符串长度
len = i;
if (s->ch != NULL) free(s->ch); // 如果 s->ch 不为 NULL,则释放之前的内存
// 如果字符串长度大于 0,则分配内存并复制字符串
if (len){
s->ch = (char *)malloc(len + 1); // 分配 len+1 个字节的空间,包括结尾的空字符 '\0'
if (s->ch == NULL) return 0; // 申请空间失败
for (i = 0; i < len; i++) s->ch[i] = tval[i];
s->ch[len] = '\0'; // 添加结尾的空字符
}
else // 如果字符串长度为 0,则将 s->ch 设置为 NULL
{
s->ch = NULL;
}
s->len = len; // 设置字符串长度
return 1; // 赋值成功
}
2.4块链串
因为串也是一个特殊的线性表,所以当然也可以采用链式存储啦。在具体实现时,一个链表存放一个串值,每个结点既可以存放一个字符,也可以存放多个字符。每个结点称为块,整个链表称为块链结构。为便于操作,再增加一个尾指针。定义如下:
#define BLOCK_SIZE 4 //每个结点存放的字符个数为4
typedef struct Block{
char ch[BLOCK_SIZE];
struct Block *next;
}Block;
typedef struct BLString{
Block *head;
Block *tail;
int len;
}BLString;
/*ch[]域为值域,存放字符块;next域为指针域,存放下一结点的地址。存储密度等于值域所占字节数/(值域所占字节数+指针域所占字节数)*/
/*显然串的存储密度越低,运算处理就越方便,但存储空间占用量较大,当最后一个结点为存满时,不足处可用特定字符补齐*/