1. 定义 + 特点
我们讨论的主题是串,无论从抽象数据类型,还是从具体实现的角度来看,串,相当于此前所介绍的数据结构来说都更为简单。因此,会将更多的时间用于讨论串的相关算法,尤其是串匹配的算法。
在接下来的最后两章中,我们将会更多地讨论,不同的数据结构在算法中的应用。在接下来的第一节,就让我们从 ADT 的角度来看看串作为一类数据结构应该提供哪些功能接口。
简而言之,所谓的字符串,也就是由来自于某个字符表中的一系列字符所构成了一个长度有限的序列。 比如这就是由8个英文字母所构成的一个字符串(Tsinghua)。一般地,串中的每个成员都称作一个字符,而字符串就是由若干的字符由前至后所构成的一个线性序列。
当然这里并不要求所有的字符都互异,也就是说有可能会存在雷同的字符,但无论如何,它们的确都是按照一个线性的次序依次排列的。线性次序,我想你很快就会想到。利用此前所学过的线性序列来直接实现串,例如向量或者列表。
是的,的确如此,因此从数据结构的角度来看,串的实现对于我们来说已不再是一件难事。然而,我们之所以还需要花费一章的时间来对它进行讨论,是因为相对于一般的线性序列而言,串结构更具有鲜明的特征。
其中最为突出的一个特点是,组成字符串的字符种类并不见得很多,但参与构成串的字符总数,也就是所谓的串长,却往往相对而言要高出很多个数量级。
以英文文章为例,一篇典型的英文文章,篇幅大致在数千个字符左右,而所有这些字符无非都是大写或小写的英文字母,再加上空格以及为数不多的标点符号。
~
我们所编写的每一段程序,比如典型的 c 或 C++程序,也可以认为是一个字符串,尽管这类串的篇幅通常也可长达数千乃至数万的字符,那组成它们的无非是95个可打印字符以及回车换行符。
~
如果将氨基酸视作字符,那么蛋白质也可以视作为字符串。
事实上,尽管这类字符串的长度很长,但组成它的字符却只有为数不多的可能。
类似地,如果将碱基对视作字符,那么 DNA 或 RNA 也可视作为字符串。
再一次的,尽管这类字符串的长度可能很长,但是正如众所周知的,组成这种字符串的字符种类却屈指可数。
~
实际上,计算机中所存储的任何序列从本质上讲都可视作为是二进制序列,也就是说组成他们的字符非0即1。
是的,所有这些例子都告诉我们,这里所讨论的串,其长度的确都要远远大于字母表的长度。
2. 术语
为了便于接下来的讨论,我们首先来统一关于串的一些术语和记法。
一般地,如果一个名为 S 的字符串,由 n 个字符构成,将所有的字符从前至后编号为 0 至 n-1,并按照我们的惯例,记作 S[0,n)。而串中秩为 k 的字符,也相应地记作 S[k]。
于是我们就可以定义,什么叫做两个字符串相等,有两个条件:
- 首先是它们的长度相等。
- 其次,所有的字符也必须逐对地相等,也就是说二者必须一模一样。
接下来一个重要概念是子串 sub-string。对于任何一个字符串 S 而言,由 i 和 k 所指定的那个子串,也就是从秩为 i 的那个字符开始,连续的 k 个字符。
以上图为例,如果整体为字符串 S,那么这里所指的子串,也就是其中的这样一段[i,i+k),可以看到,其中字符的秩介于 i 与 i + k 之间。
接下来,所谓的前缀 perfix 是子串的一个特例。具体来说,所谓长度为 k 的前缀,也就是起始于首字符的前 k 个字符。
对称地,我们也可定义所谓的后缀 surffix。具体地,所谓长度为 k 的后缀,也就是终止于末元素的最靠后的 k 个字符。
不能验证,所谓起始于 i 长度为 k 的子串,也就是在长度为 i + k 的前缀中,长度为 k 的后缀。当然,这些定义中所涉及的所有参数都是合法的,为了节省时间,我们将不再每次都专门地对此作说明。
当然,有一种边界情况在此需要专门说明,也就是说,串长 n 有可能是0,此时我们也称之为空串。
请注意,空串与由空格组成的串并不是一回事。空转有别于其他各种串的特征,就是它的长度为0。
为了简化后续的讨论,不妨再次统一约定,空串是任何串的子串,也是任何串的前缀与后缀。同时,作为另外一种边界的情况,我们也统一约定,任何串也是它自身的子串,以及前缀和后缀。反过来,长度严格小于原串的子串、前缀与后缀也称作真子串、真前缀与真后缀。
3. ADT
从抽象数据类型的角度,串应该提供哪些功能接口呢?
- 首先,应该能够随时获取它的长度,也就是当前串中所包含的字符总数。
- 另外,对于任意给定的秩,也需要能够直接返回对应的那个字符。也就是说,只要 i 小于串长 n,那么就应该返回其中秩为 i 的那个字符。
- 以下 sub-string、prefix 和 surffix 这三个接口的功能、语义与我们刚才的定义完全吻合。也就是分别取出串中对应的子串、前缀以及后缀。
- 此外,还需要有一个串接的功能。也就是将某个指定的串 T 作为后缀,与当前的字符串 S 连接起来。
- 再接下来是判等接口,它的功能正对应于我们刚才所介绍的相等。对于任意指定的字符串T,这个接口可以判断 T 是否与当前的字符串 S 彼此相等。
作为判等接口的一般化推广,索引接口 indexOf 具有更强的功能。具体来说,对于任意指定的一个长度为 m 的字符串P,这个接口可以告诉我们,在当前的字符串 S 中是否存在某个子串与 P 完全相等。实际上,本章接下来的绝大部分篇幅,都将用于讨论如何高效地实现这个接口。