数据结构与算法学习笔记——串

数据结构与算法学习笔记(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;
}

测试结果如下图:
测试结果
main.c
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’!

至于插入子串、删除子串的函数实现较为简单,留给大家自己实现,静态数组由编译器自己在栈空间分配,程序结束后空间会被系统自动收回,因此销毁串的函数不需要自己实现,好了,串的定长数组存储方式就学到这里。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值