m数据结构 day7 串(即 字符串,string)(一)线性表的扩展,朴素模式匹配(遍历)


串,也叫字符串(计算机里,说串就是说字符串,没有数字串噻),是0或多个字符组成的有限 序列(相邻字符具有前驱后继关系)。

相同:听着是不是很像线性表,串的逻辑结构确实和线性表相似,一对一,前驱后继,只不过串只存字符,就算串中存的是“123”,里面的每一个元素也都是字符,而并不是数字。

不同:就因为存储的是字符,所以串和线性表的基本操作差距非常大,线性表基本操作是添删改查,关注的是单个元素。但是串关注的却是子串(非单个字符)的应用,比如查找子串位置,得到指定位置子串,替换子串等操作。

没错,字符串也是一种数据结构,字符的存储形式嘛。

很多高级语言都有串的函数可以直接调用。

串相关的数据结构,最喜欢研究的就是回文,中国古代就已经有回文诗了:
在这里插入图片描述
倒过来
在这里插入图片描述

计算机最开始被发明时只用作数值计算,但是后来逐渐徐亚偶作越来越多的非数值处理,所以必须引入字符串的概念。

搜索引擎的字符串查找匹配:
在这里插入图片描述

串的比较:串的大小取决于挨个字母的先后顺序

比如silly和stupid,i在t前面(本质上是因为串的比较是通过比较字符之间的编码来进行的),所以silly < stupid 。字符编码就是字符在对应字符集的序号。

用英语的国家,用ASCII字符集就足够了。但是我们中国光汉字就大几千个,还有多种少数民族语言,所以有了Unicode字符集,用16位编码表示一个字符,所以总共可以表示2的16次方个字符,约65万多个。但是为了和ASCII兼容,所以Unicode字符集的前256个字符和ascii一样

其实用字典查单词就需要比较字符串的大小,前面的单词更小。如果不用纸质字典,其实电子词典查找单词的实现原理,还是用了字符串这种数据结构

两串相等n=m

在这里插入图片描述

n小于m (分两种)

在这里插入图片描述

串的抽象数据类型

基本操作比如:查找子串位置,得到指定位置子串,替换子串等操作

ADT string
Data //串的元素是单个字符,相邻元素具有前驱后继关系
Operation
	StrAssign(T, *chars);//生成一个值等于字符串常量chars的串T
	StrCopy(T, S);//串S存在,由串S复制得串T
	ClearString(S);//串S存在,清空它
	StringEmpty(S);
	StrLength(S);
	StrCompare(S, T);//若S>T,返回值大于0;S=T,返回0;S<T,返回值小于0
	Concat(T, S1, S2);//用T返回S1和S2连接而得的子串
	SubString(Sub, S, pos, len);//1<=pos<=StrLength(S),用Sub返回串S的第pos个字符起长度为len的子串
	Index(S, T, pos);//返回T在S中第pos之后第一次出现的位置,如果S没有子串T则返回0
	StrInsert(S, pos, T);//在串S的第pos个字符之前插入串T
	StrDelete(S, pos, len);//从串S中删去第pos个字符起长度为len的子串
endADT
/*T是非空串,若主串S中第pos个字符之后存在与T相等的子串,则返回第一个这样的子串在S中的位置,否则返回0*/
int Index(String S, String T, int pos)
{
	int m, n, i;
	String sub;
	if (pos > 0)
	{
		m = StrLength(S);
		n = StrLength(T);
		i = pos;
		while (i <= m-n+1)
		{
			SubString(sub, S, i, m);
			if (StrCompare(sub, T) != 0)
			{
				++i;//两串不相等
			}
			else
			{
				return i;
			}
		}
	}
	return 0;
}

串的存储结构

和线性表一样,分为两种。各有千秋,没有一个是完美的。

顺序存储结构,用定长数组实现

用一组地址连续的存储单元
要申请一个长度为n的数组,然后把串存在里面,但是还需要知道串的结束,有两个办法,C语言喜欢在字符串末尾加一个\0字符,如果要知道串的长度,则需要遍历一遍串;还有的语言倾向于把串的长度专门存在一个位置,占用一个字符的位置。两种办法都要使用一个字符位置以确定长度,但个人觉得C语言的方式不如后者,因为不用遍历。

顺序存储由于使用数组,所以很不灵活,这个不灵活总会遇到各种问题,比如Concat, StrInsert等方法都很有可能使得串序列的长度超出了数组大小,造成内存越界,很不安全。如果想要坚决杜绝内存越界,针对这种上溢,可以要么给出错误提示,要么直接把超出的部分截断扔掉,但是很明显,这两种解决办法都不是釜底抽薪,根本矛盾还是存在。

可是字符串的操作中,几乎到处都要用到Concat, StrInsert等方法啊,所以顺序存储这么死板的存储方式在需要用这些方法时,驾驭不了字符串多变的灵魂。

但是顺序结构也有好处,因为串的基本元素是一个个字符,链式结构需要让一个结点存储一个字符或者接个字符,同时需要耗费多一些内存来存指针。并且方法们的代码也要不好写一点点。

动态链式存储结构,用堆内存

所以我们对字符串,要用动态分配内存的方式存储。计算机有一个内存区叫做自由存储区,也叫做堆,C的malloc和free,C++的new和delete管理的就是这个堆内存区的存储单元。

一般让一个结点只放一个字符有点浪费空间了,毕竟每一个结点都需要一个指针。所以一般会让每一个结点存储多个字符,但是每一个结点的长度是一样的,相当于每一个结点是一个小数组,最后一个结点如果占用不完就补充#号占位置。
在这里插入图片描述

其实,在方法的代码编写上,串的链式结构并不是很香,他的顺序结构更香一点,但是链式结构确实可以避免内存越界这种严重问题。所以两种存储方式对于串来说真的是各有千秋,分庭抗礼。说不出谁压谁一头。

串的模式匹配算法:子串的定位操作

即找一个单词在一篇文章中的定位。

串的模式匹配算法是串中最重要的操作之一。

朴素版,纯粹遍历

遍历是大多数人首先想到的办法,对于这个问题,基本也可能是普通人能想到的唯一办法了。但是聪明的科学家研究出了后面要说的KMP算法,大幅提高了子串定位的效率。
在这里插入图片描述
这其实就是上面的Index函数,但是上面用了很多函数,这里纯粹自己写遍历

/*返回子串T在主串S中第pos个字符之后的位置,若不存在,则函数返回值为0*/
int Index(String S, String T, int pos)
{
	int i = pos;
	int j = 1;
	while (i <= S[0] && j <= T[0])
	{
		if (S[i] == T[j])
		{
			++i;
			++j;
		}
		else
		{
			i = i-j+2;//i退回到上次匹配首位的下一位
			j = 1;//j退回到子串T的首位
		}
	}
	if (j > T[0])
	{
		return i - T[0];
	}
	else
		return 0;
}

时间复杂度

  • 最好:O(1),比如GoogleGood中找Google,一下就找到了,一上来就匹配成功
  • 最坏:O((n-m+1)*m),即每一次不成功的匹配都要遍历到串T的最后一个字符才知道,比如
    在这里插入图片描述
    在这里插入图片描述
  • 比最好情况略差的:每次不匹配都在首字母就发现了,因此不用对T串循环,比如在abcdefgood中找good,时间复杂度是O(n+m),n是主串长度,m是子串长度。根据等概率原则??平均需要(n+m)/2次查找,所以时间复杂度是O(n+m)

可以看到,这种通过直接遍历的方式,是非常低效的,所以还是需要动动脑筋,想出更高明的解决办法。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值