1. 暴力匹配 BF 算法 (C 语言实现)
/* Brute-Force 算法的实现(暴力匹配) */
/* 注意该算法基于自定义的 String 类型 */
#define MAXSIZE 40 /* 存储空间初始分配量 */
typedef char String[MAXSIZE + 1]; /* 0号单元存放串的长度 */
/* 返回子串 T 在主串 S 中第 pos 个字符之后的位置,若不存在则返回 0 */
/* 其中要求,T 非空,且 1≤pos≤StrLength(S) */
int index(String S, String T, int pos)
{
int i = pos; // i 表示主串中当前匹配字符的下标,初始为 pos,表示从 pos 位置开始匹配
int j = 1; // j 表示子串中当前匹配字符的下标,初始为 1
/*
算法终止的条件
1. 匹配成功 => j 遍历到子串的最后一个字符且成功匹配(T[j] == S[i]),出循环时有 j == T[0] + 1
2. 匹配失败 => i 遍历到最后一个字符开启最后一个大循环,此时 j 开始的小循环遍历也没有全部成功,
故而遍历失败,出循环时有 j <= T[0] and i == S[0]
*/
while (i <= S[0] && j <= T[0])
{
if (S[i] == T[j]) // 字符匹配相等则继续匹配
{
i++;
j++;
}
else // 字符匹配不相等,指针后退重新匹配
{
i = i - j + 2; // i 指针后退到上次匹配首位的下一位
j = 1; // j 退回到子串首位
}
}
if (j > T[0]) // 匹配成功,返回子串在主串 S 第 pos 个字符之后出现的位置
return i - T[0];
else
return 0;
}
2. KMP 算法(Python 实现)
def build_next(patt):
''' 根据传入的模板串 patt 生成 next 数组 '''
''' 注意 patt[0] 代表第一个字符 '''
next = [0] # next 数组初始化
prefix_len = 0 # 当前位置对应子串中前后缀相似度
i = 1 # i 是指向字符串的对应字符的指针
''' 算法实现:逆推求解 next 数组对应的值 '''
'''
算法实现思路 (以下角标可能存在错误,注重思路理解)
if patt[j] == patt[next[j - 1]] 即,在上一个字符串前后缀基础上,再进一位的两个字符如果相同
then next[j] = next[j - 1] + 1
else
then 在上一个字符串前后缀的基础上寻找更小的字符串
if patt[j] == patt[next[next[j - 1]]]
then next[j] = next[next[j - 1]] + 1
else
then 在上一个字符串前后缀的基础上寻找更小的字符串
··· 到 0 为止
注意:这里的 prefix_len 可以理解为相似前后缀的前缀的下一个字符的索引
eg. 如下所示,patt[6] 对应的 next = 3,即 patt[2 + 1],即相似前缀的后一个字符的索引位置
0 1 2 3 4 5 6 7
A B A C A B A B
0 0 1 0 1 2 3 2
'''
while i < len(patt):
if patt[prefix_len] == patt[i]: # 在已知信息的基础上,相似度加一
prefix_len += 1
next.append(prefix_len)
i += 1
else:
if prefix_len == 0:
next.append(0)
i += 1
else:
prefix_len = next[prefix_len - 1]
return next
def kmp_search(str, patt):
''' KMP 算法的实现 '''
'''
算法实现思路
while 主串指针 i 在合法范围内
if i,j 指向的字符相同
then i,j 同时更新
else
if j=0 指向子串第一个字符
then i 更新
else
then j 通过 next 数组进行更新,next 数组对应的值就是包括当前字符的字符串的前后缀最大相似度,也是 j 即将跳转的位置
'''
next = build_next(patt)
i = 0 # 主串中的指针
j = 0 # 子串中的指针
while i < len(str):
if str[i] == patt[j]:
i += 1
j += 1
elif j == 0:
i += 1
else:
j = next[j - 1] # 根据上一个匹配成功的字符对应的 next 值进行更新
if j == len(patt): # j 对应的是 patt 的长度,就说明匹配成功(j = len - 1 代表 j 指向最后一个字符)
return i - j
return -1
3. KMP 算法 (C语言实现)
#define MAXSIZE 100 /* 存储空间初始分配量 */
typedef char String[MAXSIZE + 1]; /* 0号单元存放串的长度 */
// 注意,第一个字符对应的索引时 1!
/* 通过计算返回子串T的next数组。 */
void get_next(String T, int *next)
{
int i = 1; // 指向模板字符串字符的指针
int k = 0; // 当前字符前的字符串对应的前后缀相似度
next[1] = k;
/*
next 数组算法思路(注意,next 算法得到的值,是要更新的下标的位置)
if j = 1
then next[j] = 0,起到标记作用
elif 1~j-1 组成的字符串最大前后缀相似度不为0
then next[j] = 该最大前后缀相似度
else
then next[j] = 1 因为此时 1~j-1 对应的字符串全都不相同,因此要更新的话,也不过是需要将 j = 1
上述代码已经处理了 j = 1 时 next = 0 的情形,因此我们只需要考虑上述条件分支结构的第二第三点
*/
while (i < T[0]) /* 此处T[0]表示串T的长度 */
{
if (k == 0 || T[i] == T[k])
{
++i;
++k;
next[i] = k;
}
else
k = next[k]; /* 若字符不相同,则k值回溯 */
}
}
/* 返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回值为0。 */
/* T非空,1≤pos≤StrLength(S)。 */
int Index_KMP(String S, String T, int pos)
{
int i = pos; /* i用于主串S中当前位置下标值,若pos不为1,则从pos位置开始匹配 */
int j = 1; /* j用于子串T中当前位置下标值 */
int next[255]; /* 定义一next数组 */
get_next(T, next); /* 对串T作分析,得到next数组 */
while (i <= S[0] && j <= T[0]) /* 若i小于S的长度并且j小于T的长度时,循环继续 */
{
if (j == 0 || S[i] == T[j]) /* 两字母相等则继续,与朴素算法增加了j=0判断 */
{
++i;
++j;
}
else /* 指针后退重新开始匹配 */
j = next[j]; /* j退回合适的位置,i值不变 */
}
if (j > T[0])
return i - T[0];
else
return 0;
}
4. KMP 改进算法(C 语言实现)
#define MAXSIZE 100 /* 存储空间初始分配量 */
typedef char String[MAXSIZE + 1]; /* 0号单元存放串的长度 */
// 注意,第一个字符对应的索引时 1!
/* 通过计算返回子串T的next数组。 */
void get_next(String T, int *next)
{
int i = 1; // 指向模板字符串字符的指针
int k = 0; // 当前字符前的字符串对应的前后缀相似度
next[1] = k;
/*
next 数组算法思路(注意,next 算法得到的值,是要更新的下标的位置)
if j = 1
then next[j] = 0,起到标记作用
elif 1~j-1 组成的字符串最大前后缀相似度不为0
then next[j] = 该最大前后缀相似度
else
then next[j] = 1 因为此时 1~j-1 对应的字符串全都不相同,因此要更新的话,也不过是需要将 j = 1
上述代码已经处理了 j = 1 时 next = 0 的情形,因此我们只需要考虑上述条件分支结构的第二第三点
*/
while (i < T[0]) /* 此处T[0]表示串T的长度 */
{
if (k == 0 || T[i] == T[k])
{
++i;
++k;
next[i] = k;
}
else
k = next[k]; /* 若字符不相同,则k值回溯 */
}
}
/* 返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回值为0。 */
/* T非空,1≤pos≤StrLength(S)。 */
int Index_KMP(String S, String T, int pos)
{
int i = pos; /* i用于主串S中当前位置下标值,若pos不为1,则从pos位置开始匹配 */
int j = 1; /* j用于子串T中当前位置下标值 */
int next[255]; /* 定义一next数组 */
get_next(T, next); /* 对串T作分析,得到next数组 */
while (i <= S[0] && j <= T[0]) /* 若i小于S的长度并且j小于T的长度时,循环继续 */
{
if (j == 0 || S[i] == T[j]) /* 两字母相等则继续,与朴素算法增加了j=0判断 */
{
++i;
++j;
}
else /* 指针后退重新开始匹配 */
j = next[j]; /* j退回合适的位置,i值不变 */
}
if (j > T[0])
return i - T[0];
else
return 0;
}
说明
- C 语言代码基于 《大话数据结构》 学习整理而成
- Python 代码基于 哔哩哔哩 up 奇乐编程学院 相关视频学习整理而成