数据结构——串
串中元素逻辑关系与线性表的相同,串可以采用与线性表相同的存储结构——顺序存储结构-顺序串、链式存储结构-链串
本章从串的顺序存储结构了解串
一、串的定义
串是一种特殊的线性表,数据仅由字符组成
串是零个或多个任意字符组成的有限序列
S=“a1a2a3a4a5”(n>=0)
a1a2a3a4a5:串值
S:串名
n:串的长度
二、串的相关术语
1、空串
不包含任何字符的串(串的长度n=0)
2、子串
串中任意个连续字符组成的子序列(含空串)称为该串的子串
"abcde"的子串有:“”、“a”、“ab”、“abc”、“abcd”和“abcde”等
空串是任意串的子串,任意串是自身的子串
2.1真子串
不包含自身的所有子串
3、主串
包含该子串的串称为主串
4、字符位置
字符在序列中的序号
5、模式匹配(子串位置)
子串第一个字符在主串中第一次出现的位置
6、空格串
由一个或多个空格组成的串,与空串不同
7、两个串相等
当且仅当两个串的长度相等并且各个对应位置上的字符都相等
所有空串是相等的
三、串的应用
文字编辑系统——字符串被用来表示和存储用户输入的文本数据。编译器可对这些字符串进行增删改查等操作,以实现文本的编辑功能;
信息检索系统——使用字符串来存储和查找网页内容;
数据库系统——数据库使用字符串进行排序、查找、添加、删除等操作;
语言翻译系统、通过字符串修改图片、文件路径等
等等
四、串的基本操作及代码实现
相关头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXLEN 255
typedef char DataType;
定义串的结构体
顺序串的结构体包含:
- 存储串的数组
- 一个整数用来存储串的长度
//结构体
typedef struct
{
DataType ch[MAXLEN];
int len; //长度
} SString;
串的赋值
串的赋值操作直接使用strcpy赋值,并且设置串的长度
strcpy(S->ch, "abcdef");
S->len = 6;
串的复制StrCopy(S,T)
初始条件:串S、T存在
操作结果:把串S复制在串T后面
//串的复制 串S负责在串T后面
void StrCopy(SString *S, SString *T)
{
if (S->len + T->len >= MAXLEN)
{
printf("没有剩余足够的地方复制,复制失败!\n");
return;
}
for (int i = 0; i < S->len; i++)
{
T->ch[T->len + i] = S->ch[i];
}
T->len += S->len;
T->ch[T->len] = '\0';
printf("复制完成!\n");
}
测试代码
// main函数
int main()
{
SString *S = (SString *)malloc(sizeof(SString)); // 为S分配内存
SString *T = (SString *)malloc(sizeof(SString)); // 为chars分配内存
if (S == NULL || T == NULL)
{
// 如果分配失败,打印错误信息并退出程序
fprintf(stderr, "Memory allocation failed\n");
return 1;
}
//连接字符串
strcpy(S->ch, "abcdef");
S->len = 6;
strcpy(T->ch, "123456");
T->len = 6;
printf("串S为:");
DispStr(S);
printf("串T为:");
DispStr(T);
//串的复制
StrCopy(S, T);
printf("把串S复制到串T后面,串T变为:");
DispStr(T);
return 0;
}
测试结果
串的连接StrCat(S,T)
初始条件:串S和T存在
操作结果:将串T的值连接在串S后面
//串的连接
void StrCat(SString *S, SString *T)
{
if (S->len + T->len >= MAXLEN)
{
printf("没有剩余足够的地方连接,连接失败!\n");
return;
}
int n = S->len;
for (int i = 0; i < T->len; i++)
{
S->ch[n + i] = T->ch[i];
}
S->len += T->len;
S->ch[S->len] = '\0';
printf("串连接成功!\n");
}
测试代码
// main函数
int main()
{
SString *S = (SString *)malloc(sizeof(SString)); // 为S分配内存
SString *T = (SString *)malloc(sizeof(SString)); // 为chars分配内存
if (S == NULL || T == NULL)
{
// 如果分配失败,打印错误信息并退出程序
fprintf(stderr, "Memory allocation failed\n");
return 1;
}
//连接字符串
strcpy(S->ch, "abcdef");
S->len = 6;
printf("串S为:");
DispStr(S);
strcpy(T->ch, "123456");
T->len = 6;
printf("串T为:");
DispStr(T);
// 串的连接
StrCat(S, T);
printf("把串S和串T连接后串S变为:");
DispStr(S);
return 0;
}
测试结果
求子串SubString(Sub,S,pos,len)
初始条件:串S存在
操作结果:用Sub返回串S的第pos个字符起,长度为len的子串
//求子串 在串S中的起始位置position,找长度为Length的子串
void SubString(SString *S, int position, int length)
{
if (position + length >= MAXLEN)
{
printf("串S中没有要找的子串\n");
return;
}
for (int i = position - 1; i <= S->len - length; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%c ", S->ch[i + j]);
}
printf("\n");
}
}
测试代码
// main函数
int main()
{
SString *S = (SString *)malloc(sizeof(SString)); // 为S分配内存
SString *T = (SString *)malloc(sizeof(SString)); // 为chars分配内存
if (S == NULL || T == NULL)
{
// 如果分配失败,打印错误信息并退出程序
fprintf(stderr, "Memory allocation failed\n");
return 1;
}
//连接字符串
strcpy(S->ch, "abcdef");
S->len = 6;
strcpy(T->ch, "123456");
T->len = 6;
printf("串S为:");
DispStr(S);
printf("串T为:");
DispStr(T);
//求子串
printf("在串S里,从第二个位置开始找长度为3的子串有:\n");
SubString(S, 2, 3);
return 0;
}
测试结果
串的定位(模式匹配)StrIndex(S,T)
初始条件:串S,T都存在,T是非空串
操作结果:若串中存在与串T相同的子串,则返回串S中第一次出现的位置;否则返回-1
BF算法(Brute-Force)(简单匹配算法)——穷举法
思路:从S的每一个字符开始依次与T的字符进行匹配
BF算法匹配过程如下:
设目标串S=“aaaaab”,模式串T=“aaab”
int StrIndex(SString *S, SString *T)
{
int i = 0, j = 0;
while (i < S->len && j < T->len)
{
if (S->ch[i] == T->ch[j])
{
i++;
j++;
}
else
{
i = i - j + 1;
j = 0;
}
}
if (j == T->len)
return i - T->len + 1;
else
return -1;
}
```c
// 串的定位
int Index_BF(SString *S, SString *T)
{
if (T->len > S->len)
{
printf("子串长度比原串长,不可能查得到\n");
return -1;
}
for (int i = 0; i < (S->len - T->len) + 1; i++)
{
for (int j = 0; j < T->len; j++)
{
if (S->ch[i + j] != T->ch[j])
break;
if (j == T->len - 1 && S->ch[i + j] == T->ch[j])
return i + 1; // 返回匹配的起始位置
}
}
}
```c
测试代码
// main函数
int main()
{
SString *S = (SString *)malloc(sizeof(SString)); // 为S分配内存
SString *T = (SString *)malloc(sizeof(SString)); // 为chars分配内存
if (S == NULL || T == NULL)
{
// 如果分配失败,打印错误信息并退出程序
fprintf(stderr, "Memory allocation failed\n");
return 1;
}
//连接字符串
strcpy(S->ch, "aaaaab");
S->len = 6;
strcpy(T->ch, "aaab");
T->len = 4;
printf("串S为:");
DispStr(S);
printf("串T为:");
DispStr(T);
//串的定位BF算法
int n = Index_BF(S, T);
printf("子串T在串S中的位置是:%d", n);
return 0;
}
测试结果
串的插入StrInsert(S,pos,T)
初始条件:串S,T都存在
操作结果:在串S的第pos个字符插入串T
//串的插入 在串S中的position位置插入T
void StrInsert(SString *S, int position, SString *T)
{
if (S->len + T->len >= MAXLEN)
{
printf("超出串的最大存储,无法进行插入!\n");
return;
}
for (int i = S->len; i >= position; i--)
{
S->ch[i + T->len] = S->ch[i];
}
for (int i = 0; i < T->len; i++)
{
S->ch[position + i] = T->ch[i];
}
S->len += T->len;
printf("插入完毕\n");
}
测试代码
// main函数
int main()
{
SString *S = (SString *)malloc(sizeof(SString)); // 为S分配内存
SString *T = (SString *)malloc(sizeof(SString)); // 为chars分配内存
if (S == NULL || T == NULL)
{
// 如果分配失败,打印错误信息并退出程序
fprintf(stderr, "Memory allocation failed\n");
return 1;
}
//连接字符串
strcpy(S->ch, "abcdefg");
S->len = 7;
strcpy(T->ch, "123456");
T->len = 6;
printf("串S为:");
DispStr(S);
printf("串T为:");
DispStr(T);
//把串T插入到串S的position位置
StrInsert(S, 2, T);
printf("在串S的2号位置插入串T后串S变为:");
DispStr(S);
return 0;
}
测试结果
串的删除StrDelete(S,pos,len)
初始条件:串S寻找
操作结果:从串S中删除第pos个字符起长度为len的子串
//串的删除 在串S的position位置删除长度为length的子串
void StrDelete(SString *S, int position, int length)
{
for (int i = 0; i < length; i++)
{
S->ch[position - 1 + i] = S->ch[position - 1 + length + i];
}
S->len -= length;
}
测试代码
// main函数
int main()
{
SString *S = (SString *)malloc(sizeof(SString)); // 为S分配内存
SString *T = (SString *)malloc(sizeof(SString)); // 为chars分配内存
if (S == NULL || T == NULL)
{
// 如果分配失败,打印错误信息并退出程序
fprintf(stderr, "Memory allocation failed\n");
return 1;
}
//连接字符串
strcpy(S->ch, "abcdefg");
S->len = 7;
strcpy(T->ch, "123456");
T->len = 6;
printf("串S为:");
DispStr(S);
printf("串T为:");
DispStr(T);
//串的删除
StrDelete(S, 3, 3);
printf("在串S的第三个位置删除长度为3的子串后,串S变为:");
DispStr(S);
return 0;
}
测试结果
串的替换StrReplace(S,T,V)
初始条件:串S,T和V存在,且T是非空串
操作结果:用V替换串S中出现的所有与T相等的不重叠子串
//串替换 把串S里的子串T替换为串V
void StrReplace(SString *S, SString *T, SString *V)
{
int index = StrIndex(S, T);
if (index != -1)
{
StrDelete(S, index - 1, T->len); // 删除匹配的子串T
StrInsert(S, index - 1, V); // 插入子串V
}
S->len = S->len - T->len + V->len;
S->ch[S->len] = '\0';
}
测试代码
int main()
{
SString S, T, V;
strcpy(S.ch, "abcdefg");
S.len = 7;
strcpy(T.ch, "cde");
T.len = 3;
printf("串S为:");
DispStr(&S);
printf("串T为:");
DispStr(&T);
strcpy(V.ch, "456");
V.len = 3;
printf("串V为:");
DispStr(&V);
printf("把串S中的子串T替换为串V后,串S为:");
StrReplace(&S, &T, &V);
DispStr(&S);
return 0;
}
测试结果
判断串空StrEmpty(S)
初始条件:串S存在
操作结果:若串S为空串,则返回1;否则返回0
//判断串空
int StrEmpty(SString *S)
{
if (S->len == 0)
return 1;
return 0;
}
串的比较StrCompare(S,T)
初始条件:串S和T存在
操作结果:相等返回1,不等返回0;
//串的比较
int StrCompare(SString *S, SString *T)
{
if (S->len == T->len)
{
for (int i = 0; i < S->len; i++)
{
if (S->ch[i] != T->ch[i])
return 0;
}
return 1;
}
return 0;
}
测试代码
// main函数
int main()
{
SString S, T, V;
strcpy(S.ch, "cde");
S.len = 3;
strcpy(T.ch, "cde");
T.len = 3;
printf("串S为:");
DispStr(&S);
printf("串T为:");
DispStr(&T);
//串的比较
if (StrCompare(&S, &T))
printf("串S和串T相等\n");
DispStr(&S);
return 0;
}
测试结果
串的清空StrClear(S)
初始条件:串S存在
操作结果:将串S清为空串
//串的清空
void StrClear(SString *S)
{
S->len = 0;
S->ch[0] = '\0'; // 设置字符串起始位置为结束符
}
输出串DispStr(S)
初始条件:串S存在
操作结果:显示串中所有字符
//输出串
void DispStr(SString *S)
{
printf("%s \n", S->ch);
}
五、串的优缺点
缺点
- 存储效率低:字符串通常需要额外的存储空间来记录其长度,这可能会导致存储效率不如其他数据结构。
- 内存分配:在某些情况下,字符串可能需要动态内存分配,这可能会导致内存碎片和额外的内存管理开销。
- 性能问题:对于某些操作,如字符串拼接,如果处理不当,可能会导致性能问题,因为它们可能涉及到频繁的内存分配和复制。
- 不可预测的内存使用:由于字符串的长度可以变化,因此在某些情况下,它们可能导致堆栈溢出或内存泄漏。
- 比较效率慢:符串比较通常需要逐个字符比较,直到找到不匹配的字符或到达字符串的末尾,这可能比比较数值类型的数据结构慢。
优点
- 操作丰富:字符串支持大量的操作,如连接、比较、搜索、替换、插入和删除等,这些操作在编程语言的标准库中通常都有实现。
- 灵活性:字符串可以动态地增长和缩减,适应不同的存储需求。
- 简单直观:字符串是最容易理解和使用的数据结构之一。它们由字符序列组成,这与人类语言的表达方式相似,因此易于理解和操作。
- 应用广泛:字符串在各种应用中都是必不可少的,包括文本处理、网络通信、用户界面、数据库操作等。
本章为数据结构——串