串
定长顺序存储
#define MAXLEN 255 //预定义最大串长为255
typedef struct {
char ch[MAXLEN];//每个分量存储一个字符
int length;
} SString;
堆分配存储
typedef struct {
char *ch;//按串长分配存储区,ch指向串的基地址
int length;//串的长度
} HString;
暴力匹配算法
int Index(SString S, SString T) {
int i = 0, j = 0;//i为主串指针,j为模式串指针
while (i <= S.length && j <= S.length) {
if (S.ch[i] == T.ch[j]) {//若字符相同,继续比较下一个字符
i++;
j++;
} else {//指针后退重新开始匹配
i = i - j + 1;
j = 0;
}
}
if (j > T.length) {
return i - T.length;
} else {
return 0;
}
}
KMP
部分匹配值计算
- 前缀:除最后一个字符以外,字符串的所有头部子串。
- 后缀:除第一个字符以外,字符串的所有尾部子串。
- 部分匹配值:字符串的前缀和后缀的最长相等前后缀长度。
'a’最长相等前后缀长度为0
'ab’最长相等前后缀长度为0
'aba’最长相等前后缀长度为1
'abab’最长相等前后缀长度为2
'ababa’最长相等前后缀长度为3
字符串’ababa’的部分匹配值为00123
子串’abcac’的部分匹配值为00010
写成数组形式,得到部分匹配值(PM)的表:
编号 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
S | a | b | c | a | c |
PM | 0 | 0 | 0 | 1 | 0 |
子串需要移动位数公式:
移动位数 = 已匹配的字符数 - 对应的部分匹配值
对算法的改进方法:
已知:右移位数 = 已匹配的字符数 - 对应的部分匹配值
写成:Move = (j-1) - PM[j-1]
next数组
使用部分匹配值时,每当匹配失败,就去找它前一个元素的部分匹配值,这样使用起来有些不方便,所以将PM表右移一位,这样哪个元素匹配失败,直接看自己的部分匹配值即可。
字符’abcac’的PM表右移一位,得到next数组:
编号 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
S | a | b | c | a | c |
next | -1 | 0 | 0 | 0 | 1 |
注意:
- 第一个元素右移以后空缺的用-1填充,因为若是第一个元素匹配失败,则需要将子串向右移动一位,而不需要计算子串移动的位数。
- 最后一个元素在右移的过程中溢出,因为原来的子串中,最后一个元素的部分匹配值是其下一个元素使用的,但显然已没有下一个元素,故可以舍去。
上式就改写为:
Move = (j-1) - next[j]
相当于将子串的比较指针j回退到
j = j - Move = j - ((j-1)-next[j]) = next[j] + 1
为了使公式简洁、计算简单,将next数组整体+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]位置重新与主串当前位置进行比较。
- 根据《最大长度表》,失配时,模式串向右移动的位数 = 已经匹配的字符数 - 失配字符的上一位字符的最大长度值
- 而根据《next 数组》,失配时,模式串向右移动的位数 = 失配字符的位置 - 失配字符对应的next 值
- 其中,从0开始计数时,失配字符的位置 = 已经匹配的字符数(失配字符不计数),而失配字符对应的next 值 = 失配字符的上一位字符的最大长度值,两相比较,结果必然完全一致。
求next值
/**
* 求next值
*/
void get_next(SString 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 {
next[j] = i;//否则j=next[j],循环继续
}
}
}
KMP算法:
int Index_KMP(SString S, SString 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;
}
}
}