第11章 串
串或字符串 (string) 属于线性结构,自然地可直接利用向量或列表等序列结构加以实现,但字符串作为数据结构,特点也极其鲜明,这可归纳为:结构简单,规模庞大,元素重复率高。
以字符串形式表示的海量文本数据的高效处理技术,一直都是相关领域的研究重点。
11.1 串及串匹配
11.1.1 串
-
字符串
一般由n个字符构成的串记作:S = "a1a2 ... an-1",其中,a1 ∈ Σ,0 ≤ i ≤ n
这里的Σ是所有可用字符的合集,称作字符表 (alphabet) 。例如ASCII字符集、组成蛋白质所有氨基酸等等。
字符串S所含字符的总数n,称作S的长度,记|S| = n。长度为0的串称作空串 (null string) 。
-
子串
字符串中任一连续的片段,称作其字串 (substring) 。
由字符串 S 中起始于位置 i 的连续 k 个字符组成的子串记作:
S.substr(i, k) = "ai ai+1 ... ai+k-1" = S[i, i + k)
起始于位置0、长度为 k 的子串称为前缀(prefix):
prefix(S, k) = S.substr(0, k) = S[0, k)
终止于位置n - 1、长度为k的字串称为后缀 (suffix):
suffix(S, k) = S.substr(n - k, k) = S[n - k, n)
空串是任何字符串的子串,也是任何字符串的前缀和后缀;任何字符串都是自己的子串,也是自己的前缀和后缀。
-
判等
字符串S[0, n)和T[0, m)称作相等,当且仅当二者长度相等(n = m),且对应的字符分别相同:S[i] = T[i]
-
ADT
11.1.2 串匹配
-
应用与问题
在涉及字符串的众多实际应用中,模式匹配是最常用的一项基本技能。 比如UNIX Shell的grep工具 ( General Regular Expression Parser, 通用正则表达式解析器) 和DOS(这里指磁盘操作系统)的find命令,基本功能都是在指定的字符串中查找特定模式的字符串。
这类操作都属于串模式匹配 ( string pattern matching ) 范畴,简称串匹配。
一般地,即:
对基于同一字符表的任何文本串T( |T| = n )和模式串P ( |P| = m ) :
判定T中是否存在某一子串与P相同
若存在(匹配),则报告该子串在T中的起始位置。
-
问题分类
Ctrl + F的查找就是模式枚举。
11.1.3 测评标准与策略
如何对任一串匹配算法的性能作出客观的测量和评估?
本章采用的评价标准:随机选取文本串T,并从T中随机取出长度为m的子串作为模式串P。
11.2 蛮力算法
11.2.1 算法描述
初始状态下,T的前m个字符将与P的m个字符两两对齐。
只有在某一轮的m次比对全部成功之后才成功返回。
11.2.2 算法实现
蛮力串匹配算法(版本一):
0001 /******************************************************************************************
0002 * Text : 0 1 2 . . . i-j . . . . i . . n-1
0003 * ------------------------|-------------------|------------
0004 * Pattern : 0 . . . . j . .
0005 * |-------------------|
0006 ******************************************************************************************/
0007 int match ( char* P, char* T ) { //串匹配算法(Brute-force-1)
0008 size_t n = strlen ( T ), i = 0; //文本串长度、当前接受比对字符的位置
0009 size_t m = strlen ( P ), j = 0; //模式串长度、当前接受比对字符的位置
0010 while ( j < m && i < n ) //自左向右逐个比对字符
0011 if ( T[i] == P[j] ) //若匹配
0012 { i ++; j ++; } //则转到下一对字符
0013 else //否则
0014 { i -= j - 1; j = 0; } //文本串回退、模式串复位
0015 return i - j; //如何通过返回值,判断匹配结果?
0016 }
蛮力串匹配算法(版本一):
0001 /******************************************************************************************
0002 * Text : 0 1 2 . . . i i+1 . . . i+j . . n-1
0003 * ------------------------|-------------------|------------
0004 * Pattern : 0 1 . . . j . .
0005 * |-------------------|
0006 ******************************************************************************************/
0007 int match ( char* P, char* T ) { //串匹配算法(Brute-force-2)
0008 size_t n = strlen ( T ), i = 0; //文本串长度、与模式串首字符的对齐位置
0009 size_t m = strlen ( P ), j; //模式串长度、当前接受比对字符的位置
0010 for ( i = 0; i < n - m + 1; i++ ) { //文本串从第i个字符起,与
0011 for ( j = 0; j < m; j++ ) //模式串中对应的字符逐个比对
0012 if ( T[i + j] != P[j] ) break; //若失配,模式串整体右移一个字符,再做一轮比对
0013 if ( j >= m ) break; //找到匹配子串
0014 }
0015 return i; //如何通过返回值,判断匹配结果?
0016 }
11.2.3 时间复杂度
蛮力算法至多迭代n - m + 1轮,每轮至多需进行m次比对。
故总共做不超过(n - m + 1) · m次比对。因m << n,渐近时间复杂度应为O(n·m)。
“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 19 天,点击查看活动详情”