目录
计算机处理的对象分为数值处理和非数值处理,字符串是最基本的非数值数据。
字符串处理在语言编译、信息检索、文字编辑等领域有着广泛的应用。
1 串的基本概念
串(String)是零个或多个字符组成的有限序列。 一般记为: S=‘a1a2…an’ (n≥0)
子串:串中任意个连续的字符组成的子序列称为该串的子串。
主串:包含子串的串相应地称为主串。
空串: n=0时的串为空串
通常将字符在串中的序号称为该字符在串中的位置。
空格串:由一个或多个称为空格的特殊字符组成的串。
串相等:当且仅当两个串的值相等时,称这两个串是相等的
串的抽象数据类型定义:
ADT String {
数据对象:D={ai| ai ∈CharacterSet,i=1,2,…,n; n≥0}
数据关系:R={<ai-1,ai>| ai-1,ai ∈D,i=2,…,n; n≥0}
基本操作:
(1) StrAsign(S,chars)
初始条件:chars是字符串常量
操作结果:生成一个值等于chars的串S
(2) StrInsert(S,pos,T)
初始条件:串S和T存在,1≤pos≤StrLength(S) +1
操作结果:在串S的第pos个字符之前插入串T
(3) StrDelete(S,pos,len)
初始条件: 串S存在,1≤pos≤StrLength(S) -len +1
操作结果: 从串S中删除第pos个字符起长度为len的子串
(4) StrCopy(S,T)
初始条件: 串S存在
操作结果:由串T复制得串S
(5) StrEmpty(S)
初始条件: 串S存在
操作结果:若串S为空串,则返回TRUE,否则返回FALSE
(6)StrCompare(S,T)
初始条件: 串S和T存在
操作结果:若S>T,则返回值>0;若S=T,则返回值=0;若S<T, 则返回值<0
(7)StrLength(S)
初始条件: 串S存在
操作结果:返回串S的长度,即串S中的元素个数
(8)StrClear(S)
初始条件: 串S存在
操作结果:将S清为空串
(9)StrCat(S,T)
初始条件: 串S和T存在
操作结果:将串T的值连接在串S的后面
(10)SubString(Sub,S,pos,len)
初始条件:串S存在,1≤pos≤StrLength(S)且 1 ≤len≤StrLength(S)-pos+1
操作结果:用Sub返回串S的第pos个字符起长度为len的子串
(11)StrIndex(S,T,pos)
初始条件: 串S和T存在,T是非空串, 1≤pos≤StrLength(S)
操作结果:若串S中存在与串T相同的子串,则返回它在串S中第pos个字符之后第一次出现的位置;否则返回0
(12)StrReplace(S,T,V)
初始条件: 串S,T和V存在, 且T是非空串
操作结果:用V替换串S中出现的所有与T相等的不重叠子串
(13)StrDestroy(S)
初始条件: 串S存在
操作结果:销毁串S
2 串的存储实现
定长顺序串
1定长顺序串的概念
定长顺序串是将串设计成一种静态结构类型,串的存储分配是在编译时完成的。
定长顺序串存储结构
#define MAXLEN 20
typedef struct { /*串结构定义*/
char ch[MAXLEN];
int len;
} SString;
2.定长顺序串基本操作的实现
(1)串插入函数
有三种情况:
(1) 插入后串长(LA+LC+LB)≤MAXLEN,则将B后移LC个元素位置,再将C插入。
(2) 插入后串长>MAXLEN且pos+LC<MAXLEN,则B后移时会有部分字符被舍弃。
(3) 插入后串长>MAXLEN且pos+LC>MAXLEN,则 B的全部字符被舍弃(不需后移),并且 C在插入时也有部分字符被舍弃。
顺序串插入函数算法
StrInsert(SString *s, int pos, SString t)
/*在串s中下标为pos的字符之前插入串t */
{ int i;
if (pos<0 || pos>s->len) return(0); /*插入位置不合法*/
if (s->len + t.len<=MAXLEN) { /*插入后串长≤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;
}
else if (pos+t.len<=MAXLEN) {/*插入后串长>MAXLEN,但串t的字符序列可以全部插入*/
for (i=MAXLEN-1;i>t.len+pos-1;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;
}
else { /*插入后串长>MAXLEN,并且串t的部分字符也要舍弃
for (i=0;i<MAXLEN-pos;i++) s->ch[i+pos]=t.ch[i];
s->len=MAXLEN;
}
return(1);
}
(2)串删除函数
StrDelete(SString *s, int pos, int len)
/*在串s中删除从下标pos起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]; /*从pos+len开始至串尾依次向前移动,实现删除len个字符*/
s->len=s->len - len; /*s串长减len*/
return(1);
}
(3)串复制函数
StrCopy(SString *s, SString t)
/*将串t的值复制到串s中*/
{ int i;
for (i=0;i<t.len;i++) s->ch[i]=t.ch[i];
s->len=t.len;
}
(4)判空函数
StrEmpty(SString s)
/*若串s为空则返回1,否则返回0 */
{
if (s.len==0) return(1);
else return(0);
}
(5)串比较函数
StrCompare(SString s, SString t)
/*若串s和t相等则返回0;若s>t则返回正数;若s<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);
}
(6)求串长函数
StrLength(SString s)
/* 返回串s的长度*/
{
return(s.len);
}
(7)清空函数
StrClear(SString *s)
/*将串s置为空串*/
{
s->len=0;
}
(8)连接函数
(1) 连接后串长≤MAXLEN,则直接将B加在A的后面。
(2) 连接后串长>MAXLEN且LA<MAXLEN,则B会有部分字符被舍弃。
(3) 连接后串长>MAXLEN且LA=MAXLEN,则B的全部字符被舍弃(不需连接)。
串连接函数算法
StrCat(SString *s, SString t)
/*将串连接在串s的后面*/
{ int i, flag;
if (s->len + t.len<=MAXLEN) /*连接后串长小于MAXLEN*/
{
for (i=s->len; i<s->len + t.len; i++)
s->ch[i]=t.ch[i-s->len];
s->len+=t.len;
flag=1;
}
else if (s->len<MAXLEN) /*连接后串长大于MAXLEN,但串s的长度小于MAXLEN,
{ 即连接后串t的部分字符序列被舍弃*/
for (i=s->len;i<MAXLEN;i++)
s->ch[i]=t.ch[i-s->len];
s->len=MAXLEN;
flag=0;
}
else flag=0; /* 串s的长度等于MAXLEN ,串t不被连接*/
return(flag);
}
(9)求子串函数
SubString(SString *sub, SString s, int pos, int len)
/*将串s中下标pos起len个字符复制到sub中*/
{ int i;
if (pos<0 || pos>s.len || len<1 || len>s.len-pos)
{ sub->len=0;
return(0);
}
else {
for (i=0; i<len; i++)
sub->ch[i]=s.ch[i+pos];
sub->len=len;
return(1);
}
}
(10)定位函数
【问题分析】
T为目标串(主串),S为模式串(子串),在主串T中找子串S的过程为模式匹配(pattern matching)。用定位函数实现求子串T在主串S中从pos的位置开始第一次出现的位置序号,定位函数也叫串的模式匹配。
3. 串的简单模式匹配Brute-Force(布鲁特-福斯)算法
【算法思想】
简单的模式匹配算法是一种带回溯的匹配算法,算法的基本思想是:从主串S的第pos个字符开始,和模式串T的第一个字符开始比较,如果相等,就继续比较后续字符,如果不等,则从(回溯到)主串S的第pos+1个字符开始重新和模式串T比较,直到模式串T中的每一个字符和主串S中的一个连续字符子序列全部相等,则称匹配成功,返回和T中第一个字符相等的字符在主串T中的位置;或者主串中没有和模式串相等的字符序列,则称匹配不成功。
【算法描述】
实现时设i、j、start三个指示器:
i——指向主串T中当前比较的字符,起始指向T的首字符,此后,每比较一步,后移一步,一趟匹配失败时,回溯到该趟比较起点的下一位置。
j——指向子串S中当前比较的字符,起始指向S的首字符,此后,每比较一步,后移一步,一趟匹配失败时,回溯到S的首字符处。
start——记录每趟比较时在主串T中的起点,每趟比较后,后移一步,以便确定下一趟的起始位置。
顺序串的简单模式匹配(定位)函数算法
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; /* 主串从start+1开始,模式串从头(0)开始*/
}
if (j>=t.len) return(start); /* 匹配成功时,返回匹配起始位置 */
else return(-1); /* 匹配不成功时,返回-1 */
}
堆串
堆串的定义
字符串包括串名与串值两部分,而串值采用堆串存储方法存储,串名用符号表存储。
堆串存储方法:仍以一组地址连续的存储单元存放串的字符序列,但它们的存储空间是在程序执行过程中动态分配的。系统将一个地址连续、容量很大的存储空间作为字符串的可用空间,每当建立一个新串时,系统就从这个空间中分配一个大小和字符串长度相同的空间存储新串的串值。
堆串存储表示:
在C语言中,已经有一个称为“堆”的自由存储空间,并可用函数malloc()和函数free()完成动态存储管理。因此,我们可以直接利用C语言中的“堆”来实现堆串。此时堆串可定义如下:
typedef struct
{
char * ch;
int len;
} HString;
其中len域指示串的长度,ch域指示串的起始地址。
串名符号表:所有串名的存储映像构成一个符号表。借助此结构可以在串名和串值之间建立一个对应关系,称为串名的存储映像。
堆串的存储映象示例
a='a program',b='string ',c='process',free=23。
堆串的基本操作
(1) 堆串赋值函数
StrAssign(HString *s,char *tval) /*将字符常量tval的值赋给串s */
{ int len,i=0;
if (s->ch!=NULL) free(s->ch);
while (tval[i]!='\0') i++;
len=i;
if (len)
{ s->ch=(char *)malloc(len);
if (s->ch==NULL) return(0);
for (i=0;i<len;i++) s->ch[i]=tval[i];
}
else s->ch=NULL; s->len=len;
return(1);
}
(2) 堆串插入函数
StrInsert(HString *s,int pos,HString *t)
/*在串s中下标为pos的字符之前插入串t */
{ int i;
char *temp;
if (pos<0 || pos>s->len || s->len==0) return(0);
temp=(char *)malloc(s->len + t ->len);
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;
free(s->ch);
s->ch=temp;
return(1);
}
(3) 堆串删除函数
StrDelete(HString *s,int pos,int len)
/*在串s中删除从下标pos起len个字符 */
{ int i;
char *temp;
if (pos<0 || pos>(s->len - len)) return(0);
temp=(char *)malloc(s->len - len);
if (temp==NULL) return(0);
for (i=0;i<pos;i++) temp[i]=s->ch[i];
for (i=pos;i<s->len - len;i++) temp[i]=s->ch[i+len];
s->len=s->len-len;
free(s->ch);
s->ch=temp;
return(1);
}
块链串
块链结构的定义
#define BLOCK_SIZE 4 /*每结点存放字符个数*/
typedef struct Block{
char ch[BLOCK_SIZE];
struct Block *next;
} Block;
typedef struct {
Block *head;
Block *tail;
int length;
} BLString;
3 串的应用举例:简单的行编辑器
文本编辑程序用于源程序的输入和修改,公文书信、报刊和书籍的编辑排版等。常用的文本编辑程序有Edit,WPS,Word等 。
为了编辑方便,可以用分页符和换行符将文本分为若干页,每页有若干行。我们把文本当作一个字符串,称为文本串,页是文本串的子串,行是页的子串。 我们采用堆存储结构来存储文本,同时设立页指针、行指针和字符指针,分别指向当前操作的页、行和字符,同时建立页表和行表存储每一页、每一行的起始位置和长度。
假设有如下Pascal源程序:
FUNC max(x,y:integer):integer;
VAR z:integer;
BEGIN
IF x>y THEN z:=x;
ELSE z:=y;
RETURN(z);
END;
4 总结与提高
知识点
>> 字符串是一种特定的线性表,其特殊性就在于组成线性表的每个元素就是一个单字符。
>> 字符串常用的存储方式有三种:顺序串、堆串和块链串。
>> 顺序串以一维数组作为存储结构,运算实现类同顺序表。块链串以链表作为存储结构,运算实现类同链表。
>> 串的模式匹配算法是本章的重点,简单的模式匹配算法处理思路简单,由于进行了带回溯的处理,时间复杂度较高。改进的KMP模式匹配算法计算滑动位置,因而失配后无回溯,匹配速度较高。
典型题例
要求编写一个用带头结点的单链表实现串的模式匹配算法,每个结点存放一个字符(结点大小为1)。
【问题分析】
该算法类同顺序串的简单模式匹配,实现匹配过程需考虑链表的特征(从头比较的技术,指针保留的技术)。
【算法思想】
从主串s的第一个字符和模式串t的第一个字符开始比较,如果相等,就继续比较后续字符,如果不等,则从主串s的下一个字符开始重新和模式串t比较。一直到模式串t中的每一个字符依次和主串s中的对应字符相等,则匹配成功,返回主串的当前起始位置指针。如果主串中没有和模式串相同的子串,则称匹配不成功,返回空指针NULL。
【算法描述】