(1)前缀:除最后一个字符以外,字符串的所有头部子串; (2)后缀:除第一个字符以外,字符串的所有尾部子串; (3)部分匹配值:字符串的前缀和后缀的最长相等前后缀长度。
主串:包含子串的串空串 ϕ \phi ϕ:长度为0的串空格串:由1个或多个空格组成的串,不是空串串的长度 n n n:串中字符的个数串长的两种表示方法: (1)用一个额外的变量len来存放串的长度; (2)在串值后面加一个不计入串长的结束标记字符“\0”,此时串长为隐含值。
字符在串中的位置:某个字符在串中的序号两个串相等:长度相等且对应位置的字符相等【静态】用一组地址连续的存储单元存储串值的字符序列
/*定长数组*/ #efine MAXLEN 255 //预定义最大串长为255 typedef struct{ char ch[MAXLEN]; //每个分量存储一个字符 int length; //串的实际长度 }SString;截断:串的实际长度只能 ≤ MAXLEN,超过预定义长度的串值会被舍去 克服截断:动态分配
【动态】用一组地址连续的存储单元存储串值的字符序列
/*动态分配*/ typedef struct{ char *ch; //按串长分配存储区,ch指向串的基地址 int length; //串的长度 }HString;自由存储区——“堆” malloc()函数:申请分配一块存储空间 若分配成功,则返回一个指向起始地址的指针 若分配失败,则返回NULL free()函数:释放已分配的空间
部 分 匹 配 表 P a r t i a l M a t c h , P M 部分匹配表\space Partial\space Match,PM 部分匹配表 Partial Match,PM 以子串’abcac’为例 部分匹配表为:
编号12345SabcacPM00010分析过程:
子串前缀后缀前缀∩后缀部分匹配值a ϕ \phi ϕ ϕ \phi ϕ ϕ \phi ϕ0ab{a}{b} ϕ \phi ϕ0abc{a,ab}{c,bc} ϕ \phi ϕ0abca{a,ab,abc}{a,ca,bca}{a}1abcac{a,ab,abc,abca}{c,ac,cac,bcac} ϕ \phi ϕ0子串右移位数 = 已匹配的字符数 - 最后一个匹配字符对应的部分匹配值
M o v e = ( j − 1 ) − P M [ j − 1 ] Move=(j-1)-PM[j-1] Move=(j−1)−PM[j−1]
n e x t 数 组 : P M 表 P M 那 一 行 右 移 一 位 next数组:PM表PM那一行右移一位 next数组:PM表PM那一行右移一位
编号12345Sabcacnext-10001M o v e = ( j − 1 ) − n e x t [ j ] Move=(j-1)-next[j] Move=(j−1)−next[j]
注意
(1)第一个元素右移以后的空缺用-1填充 因为若是第一个元素匹配失败,则需要将子串向右移动一位,而不需要计算子串移动的位数。 (2)最后一个元素在右移的过程中溢出 因为原来的子串中,最后一个元素的部分匹配值是其下一个元素使用的,但显然已没有下一个元素,故可以舍去
此时,相当于将子串的比较指针j回退到 j = j − M o v e = n e x t [ j ] + 1 j=j-Move=next[j]+1 j=j−Move=next[j]+1
因此next数组也可写为:
编号12345Sabcacnext01112 此时,j=next[j]next[j]含义:在子串的第j个字符与主串发生失配时,则跳到子串的next[j]位置重新与主串当前位置进行比较
n e x t [ j ] = { 0 , j=1 m a x { k ∣ 1 < k < j 且 ‘ p 1 ⋅ ⋅ ⋅ p k − 1 ’ = ‘ p j − k + 1 ⋅ ⋅ ⋅ p j − 1 ’ } , 当此集合不空时 1 , 其他情况 next[j]=\begin{cases} 0,&\text{j=1}\\ max\{k|1<k<j且‘p_1···p_{k-1}’=‘p_{j-k+1}···p_{j-1}’\},&\text{当此集合不空时}\\ 1,&\text{其他情况} \end{cases} next[j]=⎩⎪⎨⎪⎧0,max{k∣1<k<j且‘p1⋅⋅⋅pk−1’=‘pj−k+1⋅⋅⋅pj−1’},1,j=1当此集合不空时其他情况
解释
(1) n e x t [ 1 ] = 0 next[1]=0 next[1]=0:当模式串中的第一个字符与主串的当前字符比较不相等时,模式串应右移一位,主串当前指针后移一位,再和模式串的第一字符进行比较。 (2) n e x t [ j ] = m a x { k } next[j]=max\{k\} next[j]=max{k}:当主串的第i个字符与模式串的第j个字符失配时,主串 i i i不回溯,则假定模式串的第 k k k个字符与主串的第 i i i个字符比较,即 k k k为模式串的下次比较位置。 由于 k k k值可能有多个,为了不使向右移动丢失可能的匹配,右移距离应该取最小,由于 j − k j-k j−k表示右移距离,所以取 m a x { k } max\{k\} max{k}。 (3) n e x t [ 其 他 情 况 ] = 1 next[其他情况]=1 next[其他情况]=1:除上面两种情况外,发生失配时,主串指针i不回溯,在最坏情况下,模式串从第 1 1 1个字符开始与主串的第 i i i个字符比较。
n e x t [ 1 ] = 0 next[1] = 0 next[1]=0 设 n e x t [ j ] = k next[j] = k next[j]=k
n e x t [ j + 1 ] next[j+1] next[j+1]有两种情况:
若 p k = p j p_k=p_j pk=pj: n e x t [ j + 1 ] = n e x t [ j ] + 1 next[j+1]=next[j]+1 next[j+1]=next[j]+1若 p k ≠ p j p_k≠p_j pk=pj :n e x t [ j + 1 ] = { k ′ + 1 且 k ′ = n e x t [ n e x t . . . [ k ] ] , 1 < k ′ < k < j 1 ,不存在 k ′ ,即不存在长度更短的相等前缀后缀 next[j+1]=\begin{cases} k'+1且k'=next[next...[k]]&\text{,$1<k'<k<j$}\\ 1&\text{,不存在$k'$,即不存在长度更短的相等前缀后缀} \end{cases} next[j+1]={k′+1且k′=next[next...[k]]1,1<k′<k<j,不存在k′,即不存在长度更短的相等前缀后缀
/*获取next*/ void get_next(String T, int next[]) { int i = 1, j = 0; next[1] = 0; while (i < T.length) { if (j == 0 || T.ch[i] == T.ch[j]) { ++i; ++j; next[i] = j; //若pi=pj,则next[j+1]=next[j]+1 } else j = next[j]; //否则令j=next[j],循环继续 } }思想:由于可能出现 p j = p n e x t [ j ] p_j=p_{next[j]} pj=pnext[j]的情况(会出现多余的无意义的比较),将 n e x t [ j ] next[j] next[j]修正为 n e x t [ n e x t [ j ] ] next[next[j]] next[next[j]],直到两者不相等为止。
void get_nextval(String T, int nextval[]) { int i = 1, j = 0; nextval[1] = 0; while (i < T.length) { if (j == 0 || T.ch[i] == T.ch[j]) { ++i; ++j; if (T.ch[i] != T.ch[j]) nextval[i] = j; else nextval[i] = nextval[j]; } else j = nextval[j]; } }王道