(1)前缀:除最后一个字符以外,字符串的所有头部子串;
(2)后缀:除第一个字符以外,字符串的所有尾部子串;
(3)部分匹配值:字符串的前缀和后缀的最长相等前后缀长度。
串长的两种表示方法:
(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()函数:释放已分配的空间
void StrAssign(String &T, String chars); //赋值操作。把串T赋值为chars
int StrCompare(String S, String T); //比较操作。若S>T,则返回值>0;
// 若S=T,则返回值=0;
// 若S<T,则返回值<0。
int StrLength(Strirng S); //求串长。返回串S的元素个数
String Concat(String &T, String S1, Srting S2); //串联接。用T返回由S1和S2联接而成的新串
_Bool SubString(String &Sub, String S, int pos, int len); //求子串。用Sub返回串S的第pos个字符起长度为len的子串
_Bool StrCopy(String &T,String S); //复制操作。由串S复制得到串T
_Bool StrEmpty(String S); //判空操作。若S为空串,则返回TRUE,否则返回FALSE
int Index(String S,String T); //定位操作【模式匹配】。若主串S中存在与串T值相同的子串,则返回它在主串S中第一次出现的位置;否则函数值为0
//子串(模式串)在主串中的位置
_Bool ClearString(String &S); //清空操作。将S清为空串。
_Bool DestroyString(String &S); //销毁串。将串S销毁。
/*依赖于其他串操作*/
int Index(String S, String T) {
int i = 1;
int n = StrLength(S);
int m = StrLength(T);
while (i <= n - m + 1) {
SubString(sub, S, i, m);
if (StrCompare(sub, T) != 0)
i++;
else
return i; //返回子串在主串中的位置
}
return 0; //S中不存在与T相等的子串
}
/*暴力匹配算法*/
int Index(SString S, SString T) {
int i = 1, j = 1;
while (i <= S.length && j <= T.length) {
if (S.ch[i] == T.ch[i]) {
i++;
j++; //继续比较后继字符
}
else {
/*指针后退,重新开始匹配*/
i = (i - (j - 1)) + 1; //已经有j-1个字符匹配,第j个字符匹配不上
//先回溯i-(j-1)个字符【即本次匹配的起点】,再+1进行下一轮匹配
j = 1;
}
}
if (j > T, length)
return i - T.length;
else
return 0;
}
部 分 匹 配 表 P a r t i a l M a t c h , P M 部分匹配表\space Partial\space Match,PM 部分匹配表 Partial Match,PM
以子串’abcac’为例
部分匹配表为:
编号 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
S | a | b | c | a | c |
PM | 0 | 0 | 0 | 1 | 0 |
分析过程:
子串 | 前缀 | 后缀 | 前缀∩后缀 | 部分匹配值 |
---|---|---|---|---|
a | ϕ \phi ϕ | ϕ \phi ϕ | ϕ \phi ϕ | 0 |
ab | {a} | {b} | ϕ \phi ϕ | 0 |
abc | {a,ab} | {c,bc} | ϕ \phi ϕ | 0 |
abca | {a,ab,abc} | {a,ca,bca} | {a} | 1 |
abcac | {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那一行右移一位
编号 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
S | a | b | c | a | c |
next | -1 | 0 | 0 | 0 | 1 |
M 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数组也可写为:
编号 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
S | a | b | c | a | c |
next | 0 | 1 | 1 | 1 | 2 |
此时,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
解释
(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]有两种情况:
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
/*获取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],循环继续
}
}
/*KMP算法*/
int Index_KMP(String S, String T, int next[]) {
int i = 1, j = 1;
while (i <= S.length && j <= T.length) {
if (j == 0 || S.ch[i] == T.ch[j]) { //当前字符匹配,继续比较后继字符
++i;
++j;
}
else
j = next[j]; //当前字符不匹配,模式串向右移动
}
if (j > T.length)
return i - T.length; //匹配成功,返回子串起始地址
else
return 0;
}
思想:由于可能出现 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];
}
}
王道