【数据结构】4.3 串的类型定义、存储结构及其运算

4.3.1 串的抽象类型定义

【数据结构】4.3 串的类型定义、存储结构及其运算_第1张图片

字符串的基本操作

基本字符串的操作函数都在 字符函数、字符串函数及内存函数 中,本文只做简单介绍。

【数据结构】4.3 串的类型定义、存储结构及其运算_第2张图片

【数据结构】4.3 串的类型定义、存储结构及其运算_第3张图片

4.3.2 串的存储结构

与线性表类似,串也有两种基本存储结构:顺序存储与链式存储。但考虑到存储效率和算法的方便性,串多采用**顺序存储结构**。

【数据结构】4.3 串的类型定义、存储结构及其运算_第4张图片

串的顺序存储结构

类似于线性表的顺序存储结构,用一组地址连续的存储单元存储串值的字符序列。按照预定义的大小,为每个定义的串变量分配一个固定长度的存储区,则可用固定长度的数组来描述。

#define MAX 255//字符串的最大长度

typedef struct
{
		char ch [MAX + 1];//存储串字符串的一维数组,还应该包含一个最后的\0
		int length;//串的当前长度
}SString;

为了便于理解,后面算法描述当中所用到的顺序存储的串都是从下标为1的数组位置开始存储的,下标为0的位置闲置不用。

串的链式存储结构

优点:操作方便。
缺点:存储密度较低。

  • 每个结点用来存储字符的空间需要1个字节,然而每个结点的指针域却要占4个字节,每个结点就需要占用5个字节,结点的存储密度就很低了。
    在这里插入图片描述

【数据结构】4.3 串的类型定义、存储结构及其运算_第5张图片
为了克服这个缺点,可以将多个字符放在一个结点
这样子每个结点由4个字节的空间可以拿来存储数据,另外4个字节拿来存储地址,这样每个结点的存储密度就变成了4/8 = 50%了
【数据结构】4.3 串的类型定义、存储结构及其运算_第6张图片
通常把这样的一个结点称为,通常这样一个块可以放更多的字符,比如放 50 个字符,这样存储密度就 变成了 92%,但是操作起来任然是很方便的。

通常把这种结构称为——块链结构

【数据结构】4.3 串的类型定义、存储结构及其运算_第7张图片

4.3.3 串的模式匹配算法

算法目的

  • 确定主串中所含子串模式串)第一次出现的位置(定位)。
  • 如果主串中包含子串,则返回子串的第一个字符在主串中的位置,反之返回0。
    • 如:搜索殷勤、拼写检查、语言翻译、数据压缩

算法种类

  • BF 算法(Brute-force,又称古典的、经典的、朴素的、穷举的)
  • KMP 算法(特点:速度快)

BF 算法

最简单直观的模式匹配算法是 BF 算法。
模式匹配算法不一定是从主串的第一个位置开始,可以指定主串中查找的起始位置 pos

让子串在主串中的位置一位一位的往后移,然后和主串中的内容进行比较,知道在主串中找到子串的内容。
【数据结构】4.3 串的类型定义、存储结构及其运算_第8张图片
【数据结构】4.3 串的类型定义、存储结构及其运算_第9张图片

算法的思路是从 S 的每一个字符开始依次与 T 的字符进行匹配。

举个栗子

设目标串 S = “aaaaab”,模式串(子串)T = “aaab”。

  • S 的长度为 n(n = 6),T的长度为 m(m = 4)。
  • BF算法的匹配过程如下:

先从第一个字符开始比较,前面说过,为了方便,将下标为1的位置作为字符存放的第一个位置,下标0的位置闲置。
第一个字符都一样则让i++和j++,用 S.ch[i] 和 T.ch[j] 来进行比较.
【数据结构】4.3 串的类型定义、存储结构及其运算_第10张图片
【数据结构】4.3 串的类型定义、存储结构及其运算_第11张图片

如果比较到不相等字符的位置的时候,接下来要让 i 退到第二个字符的位置,然后从主串的第二个字符的位置开始依次和子串的每个字符开始一一比较
【数据结构】4.3 串的类型定义、存储结构及其运算_第12张图片
i 退到第二个字符的位置,再和子串一一比较
【数据结构】4.3 串的类型定义、存储结构及其运算_第13张图片

等到两个串出现不匹配的字符的时候,则让 i 退到第3个字符的位置,继续重复以上步骤。
【数据结构】4.3 串的类型定义、存储结构及其运算_第14张图片
i 开始的位置 = 结束的位置(i)- 移动的距离(j - 1)
【数据结构】4.3 串的类型定义、存储结构及其运算_第15张图片
直到任何一个串走到尽头,后面没字符可以拿来比较的时候,如果两个串都没有出现不匹配的字符的话,则说明这两个串是主串和子串的关系。

【数据结构】4.3 串的类型定义、存储结构及其运算_第16张图片
此时 i 和 j 的位置分别是7和5,此时直接用 i (7)减掉匹配的串的长度aaab(4),就可以得出子串在主串中的位置3了。

【数据结构】4.3 串的类型定义、存储结构及其运算_第17张图片

算法步骤

Indes(S,T,pos)

  • 将主串的第 pos 个字符和模式串的第一个字符进行比较。
  • 若相等,继续主格比较后续字符。
  • 若不等,则从主串的下一个字符起,重新与模式串的第一个字符进行比较。
    • 直到主串的一个连续子串字符序列与模式串相等。返回值为 S 中与 T 匹配的子序列的第一个字符的序号,即匹配成功。
    • 反之匹配失败,返回值为 0 。

算法描述

//返回模式T在主串S中第pos个字符开始第一次出现的位置,若不存在则返回0。
//其中T非空,1 <= pos <= S.length
int Index_BF(SString S,SString T,iny pos)
{
		i = pos;j = 1;//初始化
		
		//两个串均未达到串尾
		while(i <= S.length && j <= T.length)
		{
				//主子串对应位置字符如果相等则比较后续字符
				if(S.ch[i] == T.ch[j])
				{
						i++;
						j++;
				}
				else//主串、子串指针回溯重新开始下一次匹配
				{		
						i = i -j + 2;
						j = 1;
				}
		}
		if(j > T.length)//匹配成功
		{
				return i - T.length;
		}
		else//匹配失败
		{
				return 0;
		}
}

BF算法的时间复杂度

最坏情况

【数据结构】4.3 串的类型定义、存储结构及其运算_第18张图片
如果是平均的情况下时间复杂度为O(n/2 * m),又因为在算时间复杂度的时候系数可以去掉,所以平均时间复杂度还是O(n * m)

BF算法效率底下的原因

  1. 让主串和模式串挨个字符进行比较

【数据结构】4.3 串的类型定义、存储结构及其运算_第19张图片

  1. 如果遇到了不相等的字符则让模式串后移一位,并且让指针重新指向模式串的第一个字符,然后继续继续进行比较。

【数据结构】4.3 串的类型定义、存储结构及其运算_第20张图片

这里让比较指针 回溯 就是造成整个算法效率比较低下的原因。

KMP 算法

KMP 算法的做法就是仅仅后移模式串就能让两个串的每一个字符进行比较,从而找出子串。

例1

  1. 回到一开始发现第一个出现字符不匹配的位置,可以发现,不匹配的字符(箭头)左边的字符主串和模式串是完全匹配的。

【数据结构】4.3 串的类型定义、存储结构及其运算_第21张图片

  1. 模式串左右两端都有一个相等AB两个子串,这两个相等的子串称为模式串的公共前后缀

【数据结构】4.3 串的类型定义、存储结构及其运算_第22张图片

  1. 核心:接下来直接移动模式串,将公共前后缀里的前缀直接移动到了原来的后缀处,
    【数据结构】4.3 串的类型定义、存储结构及其运算_第23张图片
  2. 这样移动了之后就会让比较指针左边的主串和模式串中的内容一致,因为公共前后缀的值匹配的,移动之前左边主串和模式串的内容是匹配的,移动之后当然也是匹配的。
    【数据结构】4.3 串的类型定义、存储结构及其运算_第24张图片
    只要记录下出现不匹配的字符的位置,然后在字符指针前面的字符内容中找出模式串的公共前后缀,最后再直接将前缀移动到后缀,这样就可以继续往下比较了,省时省力。
  • 注意:如果模式串中出现了多对公共前后缀,要取最长的那对
  • 后缀的位置一定是与不匹配的位置相邻的
  1. 当走到下一个不匹配的位置时,继续重复以上步骤,寻找最长公共前后缀,然后将前缀移动到后缀的位置。
  • 与后缀对称相等的字符(或字符串)就称为前缀

【数据结构】4.3 串的类型定义、存储结构及其运算_第25张图片
【数据结构】4.3 串的类型定义、存储结构及其运算_第26张图片
【数据结构】4.3 串的类型定义、存储结构及其运算_第27张图片

这个时候发现,模式串已经超出了主串的范围,已经可以判定匹配失败,主串中不含有和模式串相同的子串。
【数据结构】4.3 串的类型定义、存储结构及其运算_第28张图片

例2:

  • 最长公共前后缀应该要小于标价指针左边的子串,如果前后缀取下图这样子的那就没有意义了。
    【数据结构】4.3 串的类型定义、存储结构及其运算_第29张图片
  • 所以模式串的最长公共前后缀应该是这样的。

【数据结构】4.3 串的类型定义、存储结构及其运算_第30张图片

  • 让前缀移动到后缀的位置。

【数据结构】4.3 串的类型定义、存储结构及其运算_第31张图片

  • 移动完了之后继续比较,找到不相等的字符的话,就继续找 最长公共前后缀,然后移动。

【数据结构】4.3 串的类型定义、存储结构及其运算_第32张图片

  • 移动好了之后再继续比较模式串,比较指针一直移动到最后都没有找到不匹配的字符,这时候就可以判断主串中有包含模式串的子串。

【数据结构】4.3 串的类型定义、存储结构及其运算_第33张图片

挖掘模式串

使用 KMP 算法的过程中可以发现,在移动模式串的时候压根不需要用到主串,把主串扔掉照样能将前缀移动到后缀。

【数据结构】4.3 串的类型定义、存储结构及其运算_第34张图片

这样将模式串的内容挖出来之后就可以和任何的主串进行比较了,前面说过,模式串从数组下标为1的位置开始存储比较方便观看。

【数据结构】4.3 串的类型定义、存储结构及其运算_第35张图片

  1. 假设模式串和子串第一位就出现了不匹配,因为标记指针左边的子串的内容直接等于公共前后缀,前面说过,指针左侧的长度不能等于公共前后缀的长度,所以此处模式串指针只能移动1位。

【数据结构】4.3 串的类型定义、存储结构及其运算_第36张图片

  1. 让模式串中1号位的字符与主串中二号位的字符进行比较,如果还不相等,则让模式串中的第1位字符和主串的第三位字符比较。公共前后缀长度为0的时候,模式串只能移动1位。

【数据结构】4.3 串的类型定义、存储结构及其运算_第37张图片
【数据结构】4.3 串的类型定义、存储结构及其运算_第38张图片

  1. 在比较指针移动到主串的4号位的时候发现,公共前后缀长度终于不为0了,操作还是一样,前缀——>后缀。

【数据结构】4.3 串的类型定义、存储结构及其运算_第39张图片
【数据结构】4.3 串的类型定义、存储结构及其运算_第40张图片
然后让模式串的 2 号位与主串当前位置(4号位)的字符进行比较
【数据结构】4.3 串的类型定义、存储结构及其运算_第41张图片

  1. 找主串的 5 号位对应的模式串左边的最长公共前后缀,然后移动模式串,让模式串当前的 3 号位与主串当前位置(5号位)的字符进行比较。

【数据结构】4.3 串的类型定义、存储结构及其运算_第42张图片
【数据结构】4.3 串的类型定义、存储结构及其运算_第43张图片

  1. 然后看对应主串 6 号位的情况,操作依然不变,找公共前后缀,移动位置,模式串 4 号位与主串当前位(6号位)比较。

【数据结构】4.3 串的类型定义、存储结构及其运算_第44张图片
【数据结构】4.3 串的类型定义、存储结构及其运算_第45张图片

总结

  • 如果公共前后缀长度为 n 。
  • 我们就得到将模式串的 n + 1号位与主串当前位开始比较。
  • 每次开始比较的编号就是最大公共前后缀长度+1
    【数据结构】4.3 串的类型定义、存储结构及其运算_第46张图片

比如:此时比较指针到了模式串的 7 号位置,左边的最大公共前后缀长度是1,移动之后就是将模式串的2号位置的字符与主串的当前位置进行比较。

【数据结构】4.3 串的类型定义、存储结构及其运算_第47张图片
【数据结构】4.3 串的类型定义、存储结构及其运算_第48张图片

噼里啪啦一顿操作之后

【数据结构】4.3 串的类型定义、存储结构及其运算_第49张图片

我们将第一句话的内容给他标记为 0,当看到这个 0 的时候就按照第一句话描述的内容(1号位与主串下一位比较)来处理,if(0){则按照第一句话的内容来做}

【数据结构】4.3 串的类型定义、存储结构及其运算_第50张图片

将后面每句话对应的数字都拿出来,作为每句话的内容所要执行的代号。

【数据结构】4.3 串的类型定义、存储结构及其运算_第51张图片

然后再结合上面的数组下标,将他们放到一个数组中,这样一来根据数据中所提供的信息,任何一个位置发生了不匹配,自动就知道下一步该怎么做了。
比如模式串的0号位置(对应数组的1号位)不匹配,自然知道该干啥。这样一个数组就称为 next 数组

【数据结构】4.3 串的类型定义、存储结构及其运算_第52张图片

你可能感兴趣的:(数据结构,算法,链表)