字符串匹配
字符串匹配是在主串中找字串所处主串的起始位置,是各大搜索引擎进行关键字查找的重要步骤之一。
1.字符串的存储结构
typedef struct {
char ch[MaxSize]; //字符数组
int length; //字符串长度
}String;
2.BF暴力匹配算法
暴力匹配不名思意,就是挨个进行比较,如果全部匹配则返回字串在主串中的位置;如果查找失败,则子串指针回退到起始位置,主串指针回退到前一步对比起始位的后一步位置,重新开始匹配,其时间复杂度最坏可以达到O(m*n)。
int Index_BF(String S, String T){
/*暴力匹配*/
int i = 1, j = 1;
while (i <= S.length && j <= T.length){
if (S.ch[i] == T.ch[j]){ //若字符匹配
i++; j++; //子串指针和主串指针同时后移
}else{ //若不匹配
i = i - j + 2; //主串指针回到此次对比起始位的后一位置
j = 1; //子串指针回到起始位置
}
}
if (j > T.length) return i - T.length;
return 0;
}
3.KMP匹配算法
首先获取next数组,若匹配失败的话则子串指针用next数组下对应的位置进行回退,因此可以使得时间复杂度达到O(m+n)。
(1)获取next数组
next数组的获取类似于子串自身对自身匹配,字串的首个字符匹配失败所回退的位置,对应的next[1],所以可将next[1]设成0,并在判断字符串是否相等的 if 语句中添加 j == 0 用来使当 j 等于 0 时可以让 i 后移以为,同时 j 也后移一位。
后续对next数组的设置,可以在子串与主串子针后移后,使用next[i] = j 来设置后续的next值。因此next[1] 为0 ,next[2] 为1。
void get_next(String S, int next[]){
/*获取next数组*/
int i = 1, j = 0;
next[1] = 0;
while (i < S.length){
if (S.ch[i] == S.ch[j] || j == 0){
i++; j++;
next[i] = j;
}else{
j = next[j];
}
}
}
(2)获取nextval数组
当子串中存在连续的字符,如:“aaaab”,若主串的字符为“aaaacaaaab”,则在第5位会匹配失败,但是前面匹配的字符串全部相同,按照next数组的话,则子串指针会回退到next[5]所存储的位置,而 next[5] 等于 4,子串指针会移动到4,主串继续在第5位,依次类推,则会重复很多次不必要的对比,因此引入nextval数组。
nextval数组是next数组的升级版本,若子串中存在连续的字符,则后一个字符的nextval值存储前一个相同字符的索引,这样就可以跳过连续字符的不必要对比。
void get_nextval(String S, int nextval[]){
/*获取nextval数组*/
int i = 1, j = 0;
nextval[1] = 0;
while (i < S.length){
if (S.ch[i] == S.ch[j] || j == 0){
i++; j++;
if (S.ch[i] != S.ch[j]){ //若两个字符不相等,则照常进行存储
nextval[i] = j;
}else{
nextval[i] = nextval[j];//若两个字符相等,则设成前个索引
}
}else{
j = nextval[j];
}
}
}
(3)KMP匹配
KMP匹配使用next作为辅助,当主串与子串对匹配失败时,其子串指针回退的位置值可直接取nextval数组中的索引值,从而达到有效回退的作用,大大节约运算时间,使其时间复杂度达到O(m+n)。
int Index_KMP(String S, String T, int next[]){
/*KMP算法*/
int i = 1, j = 1;
while (i <= S.length && j <= T.length){
if (S.ch[i] == T.ch[j] || j == 0){
i++; j++;
}else{
j = next[j];
}
}
if (j > T.length) return i - T.length;
return 0;
}