大话数据结构 第五章 串(附C#实现)

串的定义:

串(string)是由零个或多个字符组成的有限序列,又名叫字符串。

一般记为  s= "a_{1}a_{2}\cdot \cdot \cdot a_{n}"(n>=0),s 是串的名称,用双引号括起来的字符序列是串的值,引号不属于串的内容。

a_{i} (1<= i <= n) 可以是字母、数字或其他字符,i 就是该字符在串中的位置。(C#中 i 是0至n-1)。

串中的字符数目 n 称为串的长度,定义中说道的“有限”是指长度 n 是一个有限的数值。

零个字符的串称为空串,它的长度为零,可以用两个双引号""表示。

所谓的序列,就说明串的相邻字符之间具有前驱和后继的关系。

空格串。是只包含空格的串。注意它与空串的区别,空格串是有内容有长度的,而且可以不止一个空格。

子串与主串,串中任意个数的连续字符组成的子序列称为该串的子串,相应的,包含子串的串称为主串。

子串在主串中的位置就是子串的第一个字符在主串中的序号。

串的比较:

两个字符串的比较,它们在计算大小中的大小其实取决于它们挨个字母的前后顺序。

事实上,串的比较是通过组成的字符之间的编码来进行的,而字符的编码指的是字符在对应字符集中的序号。

计算机中的常用字符是使用标准的ASCⅡ编码,更准确一点,由7位二进制数表示一个字符,总共可以表示128个字符。后来一些特殊符号的出现,128个不够用了,于是拓展ASCⅡ码由8位二进制数表示一个字符,总共可以表示256个字符,这已经足够满足以英语为主的语言和特殊符号进行输入、存储、输出等操作的字符需要了。世界上还有很多语言文字,256字符是不够的,因此后来就有了Unicode编码,比较常用的是由16位的二进制数表示一个字符,中共就可以表示2^{16}个字符,月6.5万多个。为了和ASCⅡ码兼容,Unicode的前256个字符与ASCⅡ码完全相同。

C 语言中比较两个串是否相等,必须是它们串的长度以及它们各个对应地字符都相等时,才算是相等。

两个串不相等时,如何判定它们的大小呢?我们这样定义:

给定两个串:s= "a_{1}a_{2}\cdot \cdot \cdot a_{n}",t="b_{1}b_{2}\cdot \cdot \cdot b_{m}",当满足以下条件之一时,s<t。

1.n<m,且a_{i}=b_{i}(i = 1,2....,n)。

2.存在某个k<=min(m,n),使得a_{i}=b_{i}(i=1,2,.....,k-1),a_{k}<b_{k}

串的存储结构

串的存储结构与线性表相同,分为两种。

串的顺序存储结构

用一组地址连续的存储单元来存储串中的字符序列。按照预定义的大小,为每个定义的串变量分配一个固定长度的存储区。一般是用定长数组来定义。

有一些问题,比如两串的连接以及字符串的替换等操作,都有可能使得串序列的长度超过了数组的长度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]=\left\{\begin{matrix} 0 ,& j=1 \\ Max({k|1<k<j,"p_{1}...p_{k-1}"="p_{j-k+1}...p_{j-1}"}) ,& Not Empty \\ 1 ,& other \end{matrix}\right.

next数组值推导

j123456
模式串Tabcdex
next[j]011111

 

 

 

 

j123456
模式串Tabcabx
next[j]011123

 

 

 

 

我们可以根据经验得到如果前后缀一个字符相等,k值是2,两个字符k值是3,n个相等k值就是n+1。

j123456789
模式串Tababaaaba
next[j]011234223

 

 

 

 

j123456789
模式串Taaaaaaaab
next[j]012345678

 

 

 

 

 

 

KMP算法仅当模式与主串之间存在许多“部分匹配”的情况下才体现出它的优势,否则两者差异并不明显。

KMP模式匹配算法改进

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值