我们在一般的高级语言中经常可以看见“串”(String),它其实不是一种新的数据结构,而是一种特殊的线性表,即以字符为元素的线性表。
今天我们就来探讨一下“串”有何不同寻常的功能~
4.1串的基本概念
串(String)又称字符串,与线性表的定义类似,串是由若干个字符组成的序列,记作:
式中s是串的名字,双引号括起来的部分是字符串的值。
可以是字母,数字或其他字符。字符的个数n称为字符串的长度。当n=0时称s是空串。字符的序号是从0开始的。
串中连续的任意多个字符组成的子序称为该串的子串,包含子串的串相应地被称为主串。子串中第0个字符在主串中的序号称为子串在主串中的位置。
在大多数高级语言里(Java等),都为串设计了专门的操作函数,下面给出串的抽象数据类型定义
ADT String
数据对象:
数据关系:
基本操作:
StrAssign(&T,chars)
操作结果:把字符串常量chars赋给T。
参数说明:chars是字符串常量,T是字符串变量。
StrCopy(&T,S)
操作结果:串S复制到串T。
参数说明:串S已存在。
StrEmpty(S)
操作结果:若串S为空则返回TRUE;否则返回FALSE。
参数说明:串S已存在。
StrLength(S)
操作结果:返回栈S中的元素(字符)个数,亦即串的长度。
参数说明:串S已存在。
StrCompare(S,T)
操作结果:若S<T返回负数,若S=T返回0,若S>T返回正数。
参数说明:串S、T已存在。
StrConcat(&T,S1,S2)
操作结果:S1、S2连接成新串并用T返回。
参数说明:串S1、S2已存在。
SubString(&Sub,S,pos,len)
操作结果:用Sub返回串S中起始位置是pos、长度为len的子串。
参数说明:串S已存在,0≤pos≤StrLength(S)-1,0≤len≤StrLength(S)- pos。
Index(S,T,pos)
操作结果:若串S中从pos位置开始存在子串T,则返回第一次出现T的位置,否则返回-1。
参数说明:串S已存在,0≤pos≤StrLengength(S)-1,T已存在。
Replace(&S,T,V)
操作结果:若串S中存在子串T,则用V替换所有不重叠的T。
参数说明:串S、V已存在,T非空。
StrInsert(&S,pos,T)
操作结果:在串S中pos位置插入子串T。
参数说明:串S、T已存在,0≤pos≤StrLength(S),pos= StrLength(S)时表示插在S最后一个字符后面。
StrDelete(&S,pos,len)
操作结果:在串S中pos位置删除长度为len的子串。
参数说明:串S已存在,0≤pos≤StrLength(S)-1,0≤len≤StrLength(S)- pos。
DestroyString(&S)
操作结果:销毁串S。
参数说明:串S已存在。
}end ADT String
在以上操作方法中,基本的操作包括:StrAssign,StrCompare,StrLength,StrConcat和SubString。
其他的操作都可由这五个操作来实现,因此我们称这5个操作构成最小操作子集。
4.2串的表示和实现
顺序存储
顺序存储从空间的分配过程来看,又可以分为静态和动态两种。
1.静态顺序存储
是在程序开始运行时分配一个内存,程序运行后无法改变空间大小,也无法释放,程序结束时内存空间随程序一起释放。
在C语言中一般使用字符数组,其中字符构成字符串,并以‘\0’字符结束。这样C语言中长度为n的字符串需要的数组空间是n+1
char mystr[10];//最多存储9个字符的字符串
连接串的实现为:
void Concat(char T[],char S1[],char S2[]){
i=j=0;
while(S1[i]!='\0')T[j++]=S1[i++];//复制串S1
i=0;
while(S2[i]!='\0')T[j++]=S2[i++];//复制串S2
T[j]='\0';
}
2.动态顺序存储
指存储空间在要使用时才分配,而不是在程序一开始就分配,而且在使用后可以立即释放,因此称为动态存储。
在C语言中用malloc来分配空间,在C++中用new来分配空间。例如
char* p=new char[11];
求字符串长度
int StrLength(char* S) {
int i = 0;
while (S[i] != '\0')i++;
return i;
}
字符串比较
int StrCompare(char* S, char* T) {
int i;
for (i=0; S[i] != '\0'; i++) {
if (S[i] != T[i])return(S[i] - T[i]);
}
return (S[i] - T[i]);
}
字符串取子串
void SubString(char* Sub, char* S, int pos, int len) {
int slen = StrLength(S);
if (pos<0 || pos>slen - 1 || len<0 || len>slen - pos) {
ErrorMsg("非法输入!"); //自定义的错误处理函数
return;
}
for (int i = 0; i < len; i++)Sub[i] = S[pos + i];
Sub[len] = '\0';
}
块链存储
如果模仿链表,使用一个结点来存储一个字符,指针会占用大量存储,造成很大的空间浪费。
此外,由于字符串常用的取子串等操作并不能发挥链式存储的优势,因此一般不用链式存留来直接存储字符串的每个字符,而是采用一种称为块链的结构,即每个结点不是存储一个字符而是多个字符。例如我们可以设定每个结点的存储字符数是4,这样一来在字符串中插入和删除一个子串时如果不是正好 4 的倍数将会导致有的结点存储不满的情况。此外,如果字符串长度不是每个结点容量的整数倍时也会引起最后一个结点不满。对于这些空的单元
还需要设置一个专门的符号以示其空闲而不属于字符串内容。常常使用存储密度来表示块链字符串的空闲单元情况。
存储密度=串实际占用存储单元数/已经分配的存储单元数
块链存储字符串的 C语言实现如下:
#define CHSIZE 4
typedef struct Chunk {
char ch[CHSIZE];
struct Chunk* next;
}Chunk;
typedef struct {
Chunk* head, * tail;
int length;
}CHString;
“串”这一讲比较容易,但是概念部分不少,希望大家对照粗体字部分查漏补缺,
~明天摸鱼~(其实“树”这一讲有点难我也要好好先学几天,后面再总结)
再来一讲 ,我们将讲讲“树”-> 链接如下:【数据结构】五分钟自测主干知识(七)