数据结构学习之路——————串

1.串类型的定义

串(string)(或字符串)是由零个或多个字符组成的有限序列,一般记为

                                        s='a1a2\cdot \cdot \cdot an'(n>=0)

 n称为串的长度。零个字符的串称为空串,长度为0。串中任意个连续字符组成的子序列称为该串的子串。包含子串的串相应地称为主串。通常称字符在序列中的序号为该字符在串中的位置。称两个串相等时,长度相同,对应位置字符相同。串值需由单引号括起来,但单引号不属于串。

串的逻辑结构和线性表极为相似,区别在于串的数据对象约束为字符集。然而基本操作有很大区别。在线性表的基本操作中,大多以“单个元素”作为操作对象;而串中,通常以“串的整体”为操作对象。

2.串的表示和实现

2.1 定长顺序存储表示

类似线性表的顺序存储结构,用一组连续的存储单元存储串值得字符串序列。按照预定义的大小,每个定义的串将被分配一个固定长度的存储区。

//------------串的定长顺序存储表示-------------
#define MAXSTRLEN 255         //用户可在255以内定义最大串长
typedef unsigned char SString[MAXSTRLEN + 1];//0号单元存放串的长度  

串的实际长度可在预定义长度的范围内随意,超过预定义长度的串值则被舍去,超过预定义长度的串值则被舍去,称之为“截断”。对串长有两种表示方法:一是如上述定义描述那样,以下标为0的数组分量存放串的实际长度;二是在串值后面加一个不记入串长的结束标记字符,如“\0”作为结束标记字符。

2.2 堆分配存储表示

这种存储方式的特点是,仍以一组地址连续的储存单元存放串值字符序列,但他们的空间是在程序执行过程中动态分配而得的。在C语言中,存在一个称之为“堆”的自由储存区,并由C语言的malloc()和free()来管理(C++中新增new)。

//--------------串的堆分配存储表示------------------
typedef struct {
	char *ch;   //若为非空串,则按串长分配储存区,否则ch为NULL
	int length;
}HString;
#include<string>
#include<iostream>
#include <cstdlib>
#include <cstdio>
#include<memory>
//------------串的定长顺序存储表示-------------
#define MAXSTRLEN 255         //用户可在255以内定义最大串长
typedef unsigned char SString[MAXSTRLEN + 1];//0号单元存放串的长度  
//--------------串的堆分配存储表示------------------
typedef struct {
	char *ch;   //若为非空串,则按串长分配储存区,否则ch为NULL
	int length;
}HString;

//--------------------------基本操作的算法描述--------
bool StrAssign(HString &T,char *chars){
	int n;
	//生成一个其值等于串常量chars的串T
	if (T.ch)free(T.ch);//释放T原有空间
	for (int i = 0, char *c = chars; *c; c++, i++) { n = i; }//求chars的长度i
	if (!n) { T.ch = NULL; T.length = 0; }
	else {
		if (T.ch = (char*)malloc(n * sizeof(char)))
			exit(OVERFLOW);
		for (int i = 0; i < n; i++) {
			T.ch[i] = chars[i];
		}
		T.length = n;
	}
	return 1;
}

int StrLength(HString S)
{
	return S.length;
}

bool ClearString(HString &S) {
	//将S清为空串
	if (S.ch) { free(S.ch); S.ch = NULL; }
	return 1;
}

bool Concat(HString &T, HString S1, HString S2) {
	//用T返回由S1和S2联接而成新串
	if (T.ch)free(T.ch);
	if (!(T.ch = (char*)malloc((S1.length + S2.length) * sizeof(char))))
		exit(OVERFLOW);
	for (int i = 0; i < S1.length; i++) {
		T.ch[i] = S1.ch[i];
		T.length = S1.length + S2.length;
		for (int i = S1.length,int j=0; i < T.length,j<S2.length; j++,i++) {
			T.ch[i] = S2.ch[j];
		}
	}
	return 1;
}

bool Subtring(HString &Sub, HString S, int pos, int len) {
	//用sub返回串S的第pos个字符起长度为len的子串
    //其中1<=pos<=StrLength(S)且0<=len<=StrLength(S)-pos+1
	if (pos<1 || pos>S.length || len<0 || len>S.length - pos + 1)
		return 0;
	if (Sub.ch)free(Sub.ch);
	if (!len) {
		Sub.ch = NULL; Sub.length = 0;
	}
	else {
		Sub.ch = (char*)malloc(len * sizeof(char));
		for (int i = 0, int j = pos - 1; i < len, j < pos+len - 1; i++, j++)
		{
			Sub.ch[i] = S.ch[j];
			Sub.length = len;
		}
	}
	return 1;
}

2.3 串的块链显示

和线性表的链式存储结构相类似,结构中的每个数据元素是一个字符,则用链表储存的结点可放一个或多个字符。当结点大小大于1,由于串长不一定是结点的整数倍,则链表中的最后一个结点不一定被串值占满,此时通常不上“#”或其他非串值字符。

为了便于进行串的操作,当以链表储存串值时,除头指针外还可附设一个尾指针指示链表中的最后一个结点,并给出当前串的长度。称如此定义的串储存结构为块链结构。

#define CHUNKSIZE 80   //可由用户定义的块的大小
typedef struct Chunk {
	char ch[CHUNKSIZE];
	struct Chunk *next;
	}Chunk;
typedef struct {
	Chunk *head, *tail; //串的头指针和尾指针
	int curlen; //串的当前长度
}LString;

一般情况下,对串进行操作时,只需从头向尾顺序扫描即可,则对串值不必建立双向链表。设尾指针是为了便于进行联结操作,但应注意联结时需处理第一个串尾的无效字符。

存储密度=串值所占的存储位/实际分配的存储位

2.4 串的模式匹配算法

子串的定位操作通常被称为串的模式匹配(其中T称为模式串)。

int Index(SString &S, SString T, int pos) {
	//返回子串T在主串S中第pos个字符之后的位置。
	int i = pos; int j = 1;
	if (i <= S[0] && j <= T[0]) {
		if (S[i] == T[j]) { i++; j++; }
		else {
			i = i - j + 2; j = 1;
		}//如不等指针后退重新开始匹配,是退到下一个字符上
	}
	if (j > T[0]) return i - T[0]; //i为匹配的子串在主串的最后位置,减去T长度即为位置
	else return 0;
}

模式匹配的一种算法:如上述代码所示。其最坏复杂度为O(n*m),n,m分别为主串和模式串的长度。

模式匹配的一种改进算法:KMP算法

此算法可以在O(n+m)的时间数量级上完成串的模式匹配操作。其改进在于:每当一趟匹配过程中出现的字符比较不等时,不需回溯i指针,而是利用所得的“部分匹配”的结果将模式串尽可能远地向右滑动一段距离后,再进行比较。

改进算法解决下述问题:当匹配过程中产生失配,模式串“向右滑动”的距离有多远,即当主串中第i个字符与模式中第j个字符失配时,主串中第i个字符(i指针不回溯)应与模式中的哪个字符比较。

现在讨论一般情况:假设此时与模式中第K个字符继续比较,则前k-1个字符要相匹配,即满足下列关系式

'{p_{1}}{p_{2}}\cdot \cdot \cdot {p_{k-1}}'='{s_{i-k+1}}{s_{i-k+2}}\cdot \cdot \cdot {s_{i-1}}'

而已得到的匹配结果是:'{p_{j-k+1}}{p_{j-k+2}}\cdot \cdot \cdot {p_{j-1}}'='{s_{i-k+1}}{s_{i-k+2}}\cdot \cdot \cdot {s_{i-1}}'(?先比完整段模式串?)

由上式推得:'{p_{1}}{p_{2}}\cdot \cdot \cdot {p_{k-1}}'='{p_{j-k+1}}{p_{j-k+2}}\cdot \cdot \cdot {p_{j-1}}'

反之,若模式串存在满足上式的两个子串,则当匹配过程中,主串中的第i个字符与模式串中的第j个字符比较不等时,仅需将模式向右滑行到模式中第k个字符和主串中第i个字符对齐,此时,模式中头k-1个字符的子串必定与主串中第i个字符之前长度为k-1的子串相等,由此仅需从模式第k个字符与主串第i个字符比较起继续进行。(翻译一下,在失配字符前即模式串中包含两端相同的子串且与主串匹配,下次移动时就可直接把前一段串移动到后面相等的串的位置上)。

  若令next[j]=k,则next[j]=k表明当模式中第j个字符与主串“失配”时,在模式中需要重新和主串中该字符进行比较的字符的位置。

             0      当j=1 

next[j]=MAX{k|1<k<j且'{p_{1}}{p_{2}}\cdot \cdot \cdot {p_{k-1}}'='{p_{j-k+1}}{p_{j-k+2}}\cdot \cdot \cdot {p_{j-1}}'}

            1     其他情况

上式可求出next[j] 数组的值。求得该函数之后,匹配可如下进行:假设以指针 i 和 j 分别指示主串和模式中正待比较的字符,令i的初值为pos,j 的初值为1。若在匹配过程s_{_{i}}=p_{_{j}},则 i 和 j 分别增1,否则 i 不变,j 再退到下一个next值的位置,以此类推,直至下列两种可能:一种是 j 退到某个next值时字符比较相等,则指针各自增1,继续进行匹配;另一种是 j 退到0,则此时需将模式继续向右滑动一个位置。

int Index_KMP(SString S, SString T, int pos,int next[]) {
	int i = pos;int j = 1;
	while (i <= S[0] && j <= T[0]) {
		if (j == 0 || S[i] == T[i]) { i++; j++; }
		else j = next[j];
	}
	if (j > T[0])return i - T[0];
	else return 0;
}
int get_next(SString T, int next[]) {
	//求模式串T的next函数值并存入
	int i = 1; next[1] = 0;
	int j = 0;
	while (i < T[0]) {
		if (j == 0 || T[i] == T[j]) { ++i; ++j; next[i] = j; }
		else j = next[j];
	}
}
void get_nextval(SString T, int nextval[]) {
	//求模式串T的next函数修正值并存入nextval中
	int i = 1; nextval[1] = 0;; int j = 0;
	while (i < T[0]) {
		if (j == 0 || T[i] == T[j]) {
			++j; ++i;
			if (T[i] != T[j])nextval[i] = j;//增加了一个判别条件,相等时nextval为前值
			else nextval[i] = nextval[j];
		}
		else j = nextval[j];
	}
}

 

  • 9
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值