串的定义与实现
基本概念
-
串(String):由零个或多个字符组成的有限序列,一般记作(S=‘a1,a2,a3…an’, n>=0)
- 其中S是串名,单括号引起来的字符序列是串值
- 子串:串中任意个连续字符组成的子序列
- 主串:包含子串的串
-
子串在主串中的位置:子串的第一个字符在主串中的位置
-
串相等:两个串的长度相等且对应位置的字符都相等
-
空格串与空串:
- 空格串:由一个或多个空格组成的串
- 空串:没有字符的串
串的基本操作
- 串赋值:StrAssign(T,chars),把串T赋值为chars
//初始化串并赋值(块链存储,尾插法)
void SetAssign(LString *S,char str[]){
//进行初始化操作
S->curlen = 0; //将串的长度初始化为0
S->head = (Chunk *)malloc(sizeof(Chunk)); //设置头结点
S->tail = S->head; //初始化将尾结点设在头结点上
S->head->next = NULL; //头结点的后继为NULL
//进行赋值操作
int i = 0; //新增节点数
Chunk *p;
while (str[i*CHUNKSIZE] != '\0'){
//在尾部创建一个新结点
p = (Chunk *)malloc(sizeof(Chunk));
S->tail->next = p;
p->next = NULL;
S->tail = p;
//将字符数组赋值给新结点
for (int j = 0; j<CHUNKSIZE; j++) {
if (str[i*CHUNKSIZE+j] != '\0')
S->tail->ch[j] = str[i*CHUNKSIZE+j]; //将CHUNLSIZE个字符存储在结点上
else
S->tail->ch[j] = '#'; //赋值完成后将多余的空间用#补上
}
S->curlen++;
i++;
}
}
- 串复制:StrCopy(&T,S),由串S复制得到T
//实现串的复制
void StrCopy(LString *T, LString S){
//初始化对象串T
T->head = (Chunk *)malloc(sizeof(Chunk)); //设置头结点
T->tail = T->head; //初始化将尾结点设在头结点上
T->head->next = NULL; //头结点的后继为NULL
//进行复制
T->curlen = S.curlen; //复制串的长度
Chunk *p,*q; //分别代表T和S目前所在的的结点
q = S.head;
for (int i = 0; i<T->curlen; i++) {
//在尾部创建一个新结点
p = (Chunk *)malloc(sizeof(Chunk));
T->tail->next = p;
p->next = NULL;
T->tail = p;
q = q->next;
//将串S的数据域赋值给串T
for (int i = 0; i < CHUNKSIZE; i++) {
p->ch[i] = q->ch[i];
}
}
}
- 串判空:StrEmpty(S)
//判空
bool StrEmpty(LString S){
if (S.curlen == 0) { //串的长度为0则为空
return true;
}
return false;
}
- 串比较:StrCompare(S,T),若S>T,则返回值>0,若S=T,则返回值=0,若S<T则返回值<0
//串比较
int StrCompare(LString T, LString S){
Chunk *p,*q; //分别代表T和S目前结点
p = T.head->next;
q = S.head->next; //设p和q分别为T和S的第一个结点
int i=0; //代表当前正在比较的字符的位置
while (p!=NULL && q!=NULL) {
if (p->ch[i] != q->ch[i]) {
if (p->ch[i] == '#') return -1; //T比S小
if (q->ch[i] == '#') return 1; //T比S大
return p->ch[i] - q->ch[i]; //若字符不等返回两个字符相减
}
i++;
if (i == 4) {
i = 0;
p = p->next;
q = q->next;
}
}
return T.curlen-S.curlen; //若字符相等,长的一方大
}
- 求串长:StrLength(S),返回串S的元素个数
//求串长
int StrLength(LString S){
if (S.curlen == 0) {
return 0;
}
int len = 0; //用于记录串长
len = CHUNKSIZE*(S.curlen-1); //尾结点前的元素数量直接计算得出
for (int i = 0; i<CHUNKSIZE; i++) {
if (S.tail->ch[i] == '#') {
len += i; //得到尾结点中不为'#'的字符的数量
break;
}
}
return len;
}
-
求子串:SubString(&Sub,S,pos,len),用Sub返回串S的第pos个字符起长度为len的子串
-
串联接:Concat(&T,S1,S2),由T返回S1和S2联接成的新串
-
串定位:Index(S,T),若主串S中存在与串T值相同的子串,则返回它在主串S中第一次出现的位置,否则函数值为0
-
串清空:ClearString(&S),将S清为空串
-
串销毁:DestoryString(&S),将串S销毁
串的储存结构
串的定长顺序储存
- 类似于线性表的储存结构:用一组地址连续的存储单元存储串值的字符序列
- 截断:串的实际长度可以在预定义的范围内(MAXLEN)随意设定,超过预定义长度的串值会被舍去
- 可以采用动态分配的方式解决串值序列截断的问题
- 定义:
#define MAXLEN 255 //预定义最大长度为255
typedef struct{
char ch[MAXLEN]; //每个分量储存一个字符
int length; //穿的实际长度
}SString;
串的堆分配存储
串的块链存储
- 结点大小:一个结点可以存储一个字符,也可以存储多个字符
- 最后一个结点没占满时,常用“#”进行填充
- 定义:
#define CHUNKSIZE 4 //定义结点块的大小
typedef struct Chunk{
char ch[CHUNKSIZE]; //每个结点存多个字符
struct Chunk *next; //指向下个结点
} Chunk;
typedef struct{
Chunk *head, *tail; //定义头指针和尾指针
int curlen; //串的当前长度
}LString;
串的模式匹配
- 模式匹配:给定一个子串,要求在某个字符串中找到与之相同的所有子串
朴素模式匹配算法
- 实现思想:
- 从目标串的第一个字符起与模式串的字符进行一一比较,若有字符不相等,则从目标串的第二个字符起开始比较,依次进行下去直到模式串中的每个字符与目标串中的一段连续字符序列相等,此时匹配成功,否则匹配失败。
- 缺点:主串指针会出现回溯现象导致时间开销增加
- 时间复杂度:m与n分别代表主串与模式串的长度
- 最好时间复杂度:O(m)
- 最坏时间复杂度:O(mn)
KMP算法
- 目的:尽可能的减少模式串和主串的匹配次数
- 具体实现:通过构建一个next数组,当模式串的第j个字符匹配失败时,令模式串跳到第next[j]项再继续匹配
- 如何构建next数组的值:前缀与后缀相等的字符的数量+1;
- 例:“ababaaba”