字符串
基本概念:主串、子串、串长
存储结构:定长顺序存储、堆分配存储(动态存储)、块链存储
模式匹配算法:暴力匹配法、KMP算法(部分匹配值PM表、next数组、next函数的推理过程)、KMP算法的进一步改进——nextval数组
大纲要求:
掌握字符串模式匹配,
重点掌握KMP模式匹配算法的原理、next数组的推理过程,
手工求next数组(先计算出部分匹配值PM表,再做变形右移一位,+1),
了解nextval数组的求解方法
一、串的定义和实现
串的定义
字符串是由零个或多个字符组成的有限序列,一般记为S=‘a1a2…an’
s是串名,单引号括起来的字符序列是串的值,ai可以是字母/数字/其他字符,串中字符个数n为串的长度
子串在主串中的位置:以子串的第一个字符在主串中的位置来表示。
由一个或多个空格(空格是特殊字符)组成的串称为空格串,注意空格串不是空串!
串的数据元素之间呈现线性关系。串的逻辑结构和线性表极为相似,只不过串的数据对象限定为字符集。
但是基本操作上有很大区别:线性表的基本操作中主要以单个元素作为操作对象,字符串的基本操作主要以子串作为操作对象。
串的存储结构
1、定长顺序存储
类似于线性表的顺序存储结构,用一组地址连续的存储单元 存储字符序列。
为每个串分配一个固定长度的存储区:定长数组。
优点:可随机访问
缺点:存储空间扩展和收缩不便
2、堆分配存储
仍然以一组地址连续的存储单元 存放字符序列,但存储空间在程序执行过程中动态分配得到。
C语言中,存在一个称为"堆"的自由存储区,用malloc()和free()函数完成动态存储管理。
利用malloc()函数为新产生的字符串分配所需的存储空间(与实际串长度相等),分配成功则返回一个指向起始地址的指针ch,作为字符串的基地址,分配失败则返回null。
3、块链存储(仅作了解)
类似于线性表的链式存储。采用链表方式存储字符串。每个节点(称为"块")可以存放一个或者多个字符。整个链表称为"块链"。
比较字符大小:其实就是比较二进制数的大小。
一个字符与一个二进制数(编码)对应。每个英文字母只占一个字节1B。
串的基本操作
串的最小操作子集(五种操作):赋值操作、比较操作、求串长、求子串、串联接
其他操作(除了串清除和串销毁)均可在该最小操作子集上实现:复制操作、判空操作
串的模式匹配
简单的匹配模式算法——朴素模式匹配算法
串的模式匹配:子串的定位操作,求子串(模式串)在主串中的位置(子串第一个字符在主串中的位置)。注意:主串中不一定存在模式串。
暴力匹配算法,采用定长顺序存储:
简单模式匹配算法:最坏时间复杂度为O(nm)。n和m分别为主串和模式串的长度。
简单模式匹配算法-缺点:暴力匹配中,每次匹配失败都要模式串后移一位,再从头开始和主串比较(主串的扫描指针i经常回溯导致时间开销大)
改进的模式匹配算法——KMP算法
改进思路:主串的指针i不回溯,只有模式串的指针j回溯。——KMP算法
如果在已经匹配相等的前缀序列中,有某个后缀刚好是模式串的前缀,就可以将模式串向后滑动到与这些相等字符对齐的位置,主串指针i无需回溯,并从该指针i位置继续比较。
概念:字符串的前缀、后缀、部分匹配值
前缀:除掉最后一个字符以外的部分 的所有子串
后缀:除掉第一个字符以外的部分 的所有子串
部分匹配值:字符串的前缀和后缀的 最长相等前后缀长度
★以'ababa'为例子说明:手动挨个查看,得到字符串的部分匹配值
将部分匹配值写成数组形式——部分匹配值表(PM表)
用PM表进行匹配的过程:
KMP算法:时间复杂度为O(n+m)。n和m分别为主串和模式串的长度。与简单模式匹配算法相比,大大提高了匹配效率。
KMP算法的原理
???????
对KMP算法的改进——next数组(两种表示方式)
PM表:
将PM表右移一位,第一个位置空缺用-1填充。——得到next数组
有时为了公式更加简洁,再将next数组整体+1。
使得next[0]=0
★最终得到子串指针变化公式:j = next[ j ]
next[ j ]的含义:在子串的第 j 个字符与主串的第 i 个字符发生失配时,主串指针i不动,子串指针 j 跳到子串的next[ j ]位置,重新与主串当前位置进行比较。
实际匹配过程中,子串在内存中是不会动的,是指针在变化。
特别的,当j=1时——当模式串的第一个字符与主串第 i 个字符发生失配时,从主串的下一个位置(i+1)和模式串的第一个字符继续匹配。
考研求next数组一般用手算,不用代码。
KMP的匹配算法(代码)在形式上与简单的模式匹配算法很相似。
不同之处仅在于:当失配时,主串指针 i 不变(主串指针不回溯),模式串指针 j 退回到next[ j ]的位置,并与主串当前位置重新进行比较。若主串的第 i 个位置与模式串的第一个字符不匹配,应从主串的第i+1个位置继续匹配。
KMP算法的进一步优化——nextval数组
next数组在某些情况下还有缺陷(还会进行重复的比较),进一步优化。
s代表主串,p代表模式串:当pj=pnext[ j ]时,其实就没必要再拿pnext[ j ]和主串再次比较了,结果肯定是继续失配。
如果出现pj=pnext[ j ]时,该怎么处理?——将next[ j ]修正为next[ next[ j ] ],直至两者(pj和p…)不相等为止。
更新后的数组命名为nextval数组:
题型:求nextval数组
1、先手算写出next数组(注意使next[0]=0)
2、先令nextval[0]=0,再从左到右 依次求出后面元素的nextval。
例题演示求nextval数组: