字符串匹配
1.BF算法
给一个while循环,(当i和j都没有走出主串和子串的自身范围)比较i和j指向的字符,如果i和j指向的字符相同,则i++,j++;如果不同,j退回到0,i退回到这一趟匹配失败开始的下一个位置(即开始位置+1)
int BF(const char* str,const char* sub)
{
//对两个指针断言
assert(str != NULL);
assert(sub != NULL);
int i = 0, j = 0;
while(i < strlen(str) && j < strlen(sub))
{
int k = i;//记录i的开始位置
if(str[i] == sub[i])
{
i++;
j++;
}
else
{
i = k + 1;
j = 0;
}
}
if(j < strlen(sub)) return -1;//循环结束如果j还小于子串(模式串)的长度,说明j没遍历完子串,那么while循环是因为i越界而推出的,也就说明没有匹配到字符串
else return i - j;
}
2.KMP算法
在每次匹配失败后,主串指针不退回,而是让子串退回到一个合适的位置,这个位置由next数组记录,所以就要找next数组。
int KMP(const char* text, const char* pattern)
{
//txt_idx为主串遍历索引,ptn_idx为子串遍历索引
//index是最终返回的匹配位置,初始化-1(未找到)
int index = -1,txt_idx = 0,ptn_idx = 0;
int txt_len = strlen(text);
int ptn_len = strlen(pattern);
int* next = (int*)malloc(sizeof(int)*ptn_len);//分配next数组大小
while((txt_inx < txt_len) && (ptn_idx < ptn_len))
{
if(text[txt_idx] == pattern[ptn_idx] || ptn_idx == -1)//该索引位置字符相等或者模式串索引退回到-1,则对比下一个位置,当ptn_idx == -1,说明模式串只能重头开始重新匹配了,故ptn_idx++ == 0
{
ptn_idx++;
txt_idx++;
}
else
{
ptn_idx = next[ptn_idx];//匹配失败了,使用next数组退回模式串索引ptn_idx
}
}
if(ptn_idx >= ptn_len)//匹配成功
{
index = txt_idx - ptn_len;
}
free(next);
return index;//index初始为-1,所以如果匹配失败了,就不需要改值
}
1.next数组
next数组是KMP算法的核心,用于在模式串匹配失败时,快速确定模式串退回的位置,从而避免主串指针回溯。它的本质是记录模式串每个位置的最长公共前后缀长度。
最长公共前后缀定义
前缀:不包含最后一个字符的所有连续子串
后缀:不包含第一个字符的所有连续子串
最长公共前后缀:前缀和后缀中完全相同的最大长度
2.next数组函数实现
void get_next(const char* str,int* next,int len)
{
int j = 0;//指向模式串中当前正在处理的字符位置
int k = -1;//指向模式串中当前最长公共前缀的末尾索引
//next[0]索引的字符的前面再没有字符了,所以如果next[0]还没有找到的话,-1就表示应该重新遍历模式串了。
next[0] = -1;
while(j < len - 1)
{
if(k == -1 || str[j] == str[k])
{
//k == -1说明没有找到,那么next[j]=k++也就是0的位置,说明退回到字符串开始位置
k++;
j++;
next[j] = k;//记录当前位置的最长公共前后缀长度
}
else
{
k = next[k];
}
}
}
j和k指向的位置是当前正在比较的两个字符。在第三步发现 j和k所指向的字符不同,则k回退到next[0]的位置,若还不相同,则继续执行回退操作。最长公共前缀的区间在[0,j),也就是j之前的字符。注意到j每次自增时都伴随着k的自增,此时要记录下k的值,也就是更新next数组,所以next[j]的值也随之确定。
3.next数组优化
在原本的next数组中,如果退回后的字符等于当前字符,那么就会导致冗余比较,所以在每次更新next数组前,先检查退回后的字符是否与当前字符相等,如果相等则当前的next值赋予更早的next值。
void get_nextval(const char* str, int* nextval, int len)
{
int j = 0;
int k = - 1;
nextval[0] = -1;
while(j < len - 1)
{
if(k == -1 || str[j] == str[k])
{
k++;
j++;
//优化:检查回退后的字符是否与当前字符相同
if(str[j] == str[k])
nextval[j] = nextval[k];//如果相同,则跳回更早的位置,避免了多余的比较
else
nextval[j] = k;
}
else
{
k = nextval[k];
}
}
}
利用上述代码比较主串"aabaabaaf"和子串"aabaaf",关键流程图如下:
这是KMP代码while循环里的if条件判断:
在第六步发现字符不匹配,于是触发else调用next数组,使子串退回。aabaaf的next数组为{-1 ,0,1,0,1,2},所以退回到b的位置继续比较。