串的定义:
串(string)是由零个或多个字符组成的有限序列,又名叫字符串。
一般记为 s= ""(n>=0),s 是串的名称,用双引号括起来的字符序列是串的值,引号不属于串的内容。
(1<= i <= n) 可以是字母、数字或其他字符,i 就是该字符在串中的位置。(C#中 i 是0至n-1)。
串中的字符数目 n 称为串的长度,定义中说道的“有限”是指长度 n 是一个有限的数值。
零个字符的串称为空串,它的长度为零,可以用两个双引号""表示。
所谓的序列,就说明串的相邻字符之间具有前驱和后继的关系。
空格串。是只包含空格的串。注意它与空串的区别,空格串是有内容有长度的,而且可以不止一个空格。
子串与主串,串中任意个数的连续字符组成的子序列称为该串的子串,相应的,包含子串的串称为主串。
子串在主串中的位置就是子串的第一个字符在主串中的序号。
串的比较:
两个字符串的比较,它们在计算大小中的大小其实取决于它们挨个字母的前后顺序。
事实上,串的比较是通过组成的字符之间的编码来进行的,而字符的编码指的是字符在对应字符集中的序号。
计算机中的常用字符是使用标准的ASCⅡ编码,更准确一点,由7位二进制数表示一个字符,总共可以表示128个字符。后来一些特殊符号的出现,128个不够用了,于是拓展ASCⅡ码由8位二进制数表示一个字符,总共可以表示256个字符,这已经足够满足以英语为主的语言和特殊符号进行输入、存储、输出等操作的字符需要了。世界上还有很多语言文字,256字符是不够的,因此后来就有了Unicode编码,比较常用的是由16位的二进制数表示一个字符,中共就可以表示个字符,月6.5万多个。为了和ASCⅡ码兼容,Unicode的前256个字符与ASCⅡ码完全相同。
C 语言中比较两个串是否相等,必须是它们串的长度以及它们各个对应地字符都相等时,才算是相等。
两个串不相等时,如何判定它们的大小呢?我们这样定义:
给定两个串:s= "",t="",当满足以下条件之一时,s<t。
1.n<m,且(i = 1,2....,n)。
2.存在某个k<=min(m,n),使得(i=1,2,.....,k-1),。
串的存储结构
串的存储结构与线性表相同,分为两种。
串的顺序存储结构
用一组地址连续的存储单元来存储串中的字符序列。按照预定义的大小,为每个定义的串变量分配一个固定长度的存储区。一般是用定长数组来定义。
有一些问题,比如两串的连接以及字符串的替换等操作,都有可能使得串序列的长度超过了数组的长度MaxSize。
于是对于串的顺序存储,有一些变化,串值的存储空间可在程序执行过程中动态分配而得。
串的链式存储结构
与线性表相似,但是由于串结构的特殊性,结构中的每个元素数据是一个字符,如果也简单地应用链表存储串值,一个结点对应一个字符,机会存在很大的空间浪费。因此,一个结点可以存放一个字符,也可以考虑存放多个字符,最后一个结点若是未被占满,可以用“#”或其他非串值字符补全。
一个结点存放多少个字符才合适就变得很重要,这会直接影响着串处理的效率,需要根据实际情况作出选择。
串的链式存储结构除了在连接串与串操作时有一定方便之外,总的来说不如顺序存储灵活,性能也不如顺序存储结构好。
朴素的模式匹配算法
子串的定位操作通常称做串的模式匹配。
分析一下复杂度
假如一开始就匹配成功。时间复杂度为O(1)。
假如每次匹配都是首字母就不匹配,那么对 t 串的循环就不必进行了。根据等概率原则,平均是(n+m)/2次查找。时间复杂度为O(n+m)。
假如每次匹配都是 t 串的最后一个字母才不匹配,每次 t 串都要循环。时间复杂度为O((n-m+1)*m)。
KMP模式匹配算法
一个模式匹配算法,可以大大避免重复遍历的情况,我们把它称之为克努特-莫里斯-普拉特算法,简称KMP算法。
假设,s=“abcdefg.....”,t = "abcdex"。
按照朴素模式匹配算法,主串s中当i=2,3,4,5,6时,首字符与子串的首字符均不等。
仔细观察,对于要匹配的子串t来说,“abcdex”首字母“a”与后面的串“bcdex”中任意一个字符后都不相等。也就是说,既然“a”不与自己后面的子串中任何一字符相等,那么对于朴素模式匹配算法中第一步来说,前五位字符分别相等,意味着子串t的首字母“a”不可能与s串的第2位到第5位的字符相等。所以,对于朴素模式匹配算法,第2,3,4,5步判断都是多余的。
这是理解KMP算法的关键。
之所以保留第6步判断,是因为在第1步中,我们知道 s[6]不等于t[6],t[1]不等于t[6],我们也不能判定t[1]一定不等于s[6]。
我们在朴素的模式匹配算法中,主串的i值是不断地回溯来完成的。(退到上次匹配首位的下一位)而我们分析发现,这种回溯其实是可以不需要的。KMP模式匹配算法就是为了让这没必要的回溯不发生。
既然主串的i值不回溯,也就是不可以变小,那么要考虑的变化就是子串的j值了。通过观察也可发现,我们屡屡提到t串的首字符与自身后面字符的比较,如果有相等字符,j值的变化就会不同。
也就是说,这个j值的变化与主串其实没什么关系,关键就取决于t串的结构中是否有重复的问题。
我们可以得出规律,j值的多少取决于当前字符之前的串的前后缀的相似度。
我们把T串各个位置的j值的变化定义为一个数组next,那么next的长度就是T串的长度。于是我们得到下面的函数定义:
next数组值推导
j | 123456 |
模式串T | abcdex |
next[j] | 011111 |
j | 123456 |
模式串T | abcabx |
next[j] | 011123 |
我们可以根据经验得到如果前后缀一个字符相等,k值是2,两个字符k值是3,n个相等k值就是n+1。
j | 123456789 |
模式串T | ababaaaba |
next[j] | 011234223 |
j | 123456789 |
模式串T | aaaaaaaab |
next[j] | 012345678 |
KMP算法仅当模式与主串之间存在许多“部分匹配”的情况下才体现出它的优势,否则两者差异并不明显。
KMP模式匹配算法改进