数据结构与算法学习笔记(C语言)
1.定义:串是由零个或多个字符组成的有限序列,一般记为
s = 'a1a2a3......an' (n >= 0)
其中,s是串的名,用单引号括起来的字符序列是串的值;ai (1 ≤ i ≤ n)可以是字母、数字或其他字符;串中字符的数目 n 称为串的长度。零个字符的串称为空串(null string),它的长度为零。
注意:’ ’ 这个里面有一个空格,这个叫空格串,它可不是空串。
生活中应用串的实例很多,比如各种账号密码,就是一个串,编辑的一段文本也是串
这里纠正一下可能会犯的一个小错误,有些人觉得汉字也是字符,其实是不对的,每个汉字都是一个字符串
在图中可以看到,不同的汉字在不同的编码方式下对应不同的字符串。
上面的四个汉字在GB2312、GBK、GB18030编码系统中又对应着相同的字符串,其实是GB是国标拼音的缩写,我们的国家标准,不过修订过几次,常用的简体汉字对应的编码当然是一样的了
字串:串中任意个连续的字符组成的子序列称为该串的子串。包含字串的串称为该子串的主串。字串在主串中的位置以字串第一个字符在主串的位置来表示。
通过字串的定义我们知道:空串是任何串的子串,一个串是它自身的子串。
a = ‘nihaoniua’, b = ‘ni hao niu a’, c = ‘nihao’, d = ‘niu’
则 c 是 a 的子串,d 是 a 和 b 的子串;c 在 a 中的位置是 1,d 在 a 中的位置是 6,d 在 b 中的位置是 8
如果两个串的值相等,就称这两个串相等,它要求串的长度相等,每个位置上的字符一样。
串这种数据结构和线性表很相似,比如串里面的每个字符也是有前后关系的,区别在于,线性表呢,对里面的元素没有限制,可以是数子,可以是字符,也可以是一种自己定义的结构体,比如学生信息结构体
struct Student {
char name[15];
char sex[5];
int age;
int stu_num;
}
但是串呢,串的数据对象仅限于字符集。在线性表中,大多以“单个元素”作为操作对象,比如查找某元素,删除某元素;而串通常以“串的整体”为操作对象,比如查找一个子串,删除一个子串等。
串的抽象数据类型定义如下
ADT String {
数据对象:D = {ai | ai是字符, i = 1, 2, 3, ..., n, n >= 0}
数据关系:R = {<ai-1, ai> | i = 1, 2, 3, ..., n}
基本操作:
StrAssign(*T, chars) /*生成一个值为chars的串*/
StrCopy(*T, S) /*由串S复制得串T*/
StrEmpty(S) /*若串为空,返回true,不空返回false*/
StrCompare(S, T) /*若串S > T返回值为正,若S == T返回值为0,若S < T返回值为负*/
StrLength(S) /*返回串的元素个数*/
ClearString(*S) /*将S清为空串*/
ConCat(*T, S1, S2) /*用T返回由S1和S2联接成的新串*/
SubString(*Sub, S, pos, len) /*用Sub返回S的pos位置起长度为len的子串*/
Index(S, T, pos)
/*若串S中存在和串T值相等的子串,返回它在主串S中pos位置之后第一次出现的位置,否则返回0*/
Replace(*S, T, V) /*用V替换串S中出现的所有等于T不重叠的子串*/
StrInsert(*S, pos, T) /*在串S的pos位置之前插入串T*/
StrDelete(*S, pos, len) /*删除串S中从pos位置起长度为len的子串*/
DestroyString(*S) /*销毁串S*/
}ADT String
在上述抽象数据类型定义的操作中,串赋值StrAssign、串比较StrCompare、求串长StrLength、串联接Concat、以及求子串SubString这5种操作不可能利用其他操作来实现,构成最小操作子集。反之,除了串清空ClearString、串销毁DestroyString外的操作均可在这个最小操作子集上实现。
例如下面的这个操作:
Index(S, T, pos)
若串S中存在和串T值相等的子串,返回它在主串S中pos位置之后第一次出现的位置,否则返回0
int Index(String S, String T, int pos)
{
int m, n, i; String *Sub;
if (pos > 0) {
n = StrLength(S); m = StrLength(T); i = pos;
/*用到了求串长的操作*/
while (i <= n - m + 1) {
SubString(&Sub, S, i, m);
/*用到了求子串操作*/
if (StrCompare(Sub, T) != 0) ++i;
/*用到了比较串的操作*/
else return i;
}
}
return 0;
}
上面的定位函数用到了求串长、求子串、判等的基本操作。
串在计算机中有三种存储方法,下面分别介绍
1.定长顺序存储表示
类似于线性表的顺序存储结构,用一组地址连续的存储单元存储串值的字符序列。
用定长数组描述如下
#define MAXLEN 100
typedef unsigned char String[MAXLEN + 1];
/*在0号单元存放串的长度,其中unsigned char为0~255,ASCII码值为0~127*/
实际串长在定义的MAXLEN之内,超过的部分会被舍去,这叫“截断”,在C语言中,内置有字符串类型,并且串的数组存储和上述定义有所不同,它是以在末尾添加’\0’表示串值的结束。
在这种存储结构表示下的串操作的实现如下
1.生成串
void StrAssign(String T, const char *S)
/*这里String类型是字符数组类型,数组作为函数参数传递的就是地址,const限定符保证传入的字符串常量不会变化*/
{
int i = 0;
while (S[i]) {
T[i + 1] = S[i];
++i;
}
T[0] = i;
}
2.由串S复制得串T
void StrCopy(String T, String S)
{
int i;
T[0] = S[0];
for (i = 1; i <= S[0]; i++) {
T[i] = S[i];
}
}
3.判断串是否为空
/*包含头文件stdbool.h*/
bool StrEmpty(String S)
{
if (S[0] == 0) return true;
else return false;
}
4.比较串的大小
/*函数会从两个串的第一个字符开始比较,直到S串的某个字符小于T的某个字符返回-1,或者大于某个字符返回1*/
int StrCompare(String S, String T)
{
int i, j;
i = j = 1;
while (i <= S[0] && j <= T[0]) {
if (S[i] < T[i]) return -1;
else if (S[i] > T[i]) return 1;
else {
++i; ++j;
}
}
/*到这里表示没进入while循环,或者while循环结束,这时候只需要执行下面的判断*/
if (S[0] < T[0]) return -1;
else if (S[0] > T[0]) return 1;
else return 0;
}
5.求串长
int StrLength(String S)
{
return S[0];
}
6.清空串
void ClearString(String S)
{
S[0] = 0;
}
7.连接两个串
/*若未截断返回true,截断返回false*/
bool ConCat(String T, String S1, String S2)
{
if (S1[0] + S2[0] <= MAXLEN) {
/*未截断*/
T[0] = S1[0] + S2[0];
for (i = 1; i <= S1[0]; i++) {
T[i] = S1[i];
}
for (i = S1[0] + 1; i <= T[0]; i++) {
T[i] = S2[i - S1[0]];
}
uncut = true;
}
else if(S1[0] < MAXLEN) {
/*截断*/
T[0] = MAXLEN;
for (i = 1; i <= S1[0]; i++) {
T[i] = S1[i];
}
for (i = S1[0] + 1; i <= MAXLEN; i++) {
T[i] = S2[i - S1[0]];
}
uncut = false;
}
else {
T[0] = S1[0]; /*T[0] = S1[0] = MAXLEN*/
for (i = 1; i <= MAXLEN; i++) {
T[i] = S1[i];
}
uncut = false;
}
return uncut;
}
测试结果如下图:
8.求子串
Status SubString(String Sub, String S, int pos, int len)
{
int i;
if (pos > 0 && pos <= S[0] - len + 1) {
/*这个判断语句是判断求子串的位置是否合法*/
Sub[0] = len;
for (i = 1; i <= len; i++) {
Sub[i] = S[pos + i - 1];
}
return OK;
}
return ERROR;
}
9.若主串中包含某子串,确定它从pos开始第一次出现的位置
int Index(String S, String T, int pos)
{
int m, n, i;
String Sub;
if (pos > 0) {
n = S[0]; m = T[0]; i = pos;
/*用到了求串长的操作*/
while (i <= n - m + 1) {
SubString(Sub, S, i, m);
/*用到了求子串操作*/
if (StrCompare(Sub, T) != 0) ++i;
/*用到了比较串的操作*/
else return i;
}
}//if
return 0;
}
显然,上图结果正确,函数无误
10.若S存在子串T,则用串V替换子串S
void Replace(String S, String T, String V)
{
int i, j, delt;
int pos = 1;
/*用V替换串S中出现的所有等于T不重叠的子串*/
while (i = Index(S, T, pos)) {
/*如果T是S的子串,找到S从第一位开始T的位置*/
delt = V[0] - T[0];
S[0] += delt;
/*改变S为替换后的长度*/
if (delt > 0) {
for (j = S[0]; j > i + V[0] - 1 ; j--) {
/*替换位置之后的元素全部后移,留出替换串的长度*/
S[j] = S[j - delt];
}
for (j = i; j <= i + V[0] -1; j++) {
/*执行替换操作*/
S[j] = V[j - i + 1];
}
return OK;
}
else if (delt == 0) {
for (j = i; j <= i + T[0] -1; j++) {
/*执行替换操作*/
S[j] = V[j - i + 1];
}
return OK;
}
else {
/*delt < 0*/
for (j = i + V[0]; j <= S[0]; j++) {
S[j] = S[j - delt];
}
for (j = i; j <= i + V[0] - 1; j++) {
S[j] = V[j - i + 1];
}
return OK;
}
pos = i + V[0];
}//while
}
如上图,函数成功将子串’world’替换为’programmer’!
至于插入子串、删除子串的函数实现较为简单,留给大家自己实现,静态数组由编译器自己在栈空间分配,程序结束后空间会被系统自动收回,因此销毁串的函数不需要自己实现,好了,串的定长数组存储方式就学到这里。