目录
1.BF算法(Brute Force)
(1)BF算法简介
(2)BF算法思想(实现思想)
(3)BF算法实现代码(C语言)
(4)BF算法的改进
2.KMP算法
(1)next数组的求法(默认模式串下标从0开始)
(2)求next数组算法思想(代码实现)
(3)KMP算法代码实现
(4)KMP算法的优化(引入nextval数组)
BF算法即暴力算法。它的思想就是将模式串和主串的字符进行一一比较:模式串和子串进行比较,一致时继续比较主串和模式串该趟的下一个字符,直到比较完整的整个模式串;不一致时模式串后移一位,以主串的下一个字符为起始,模式串从首位重新开始比较。重复上面的步骤。(如下图)
申请两个变量i、j分别指向主串和模式串开始。只要i、j均在主串和模式串长度内:如果i、j指向的字符相同则两个变量均++,比对下一个字符;否则i回退到主串下一个字符(i-j+1),模式串回退到0重新开始比较。 若任一方超出长度范围:判断若为j超范围则已找到,返回i-j。
#include
#include
#include
#include
int BF_Search(const char* str, const char* sub)
{
assert(str != NULL && sub != NULL);//断言
int len_str = strlen(str);
int len_sub = strlen(sub);
int i = 0;
int j = 0;
while (i < len_str && j < len_sub)//i、j均在长度范围内~循环判断
{
if (str[i] == sub[j])//字符比对相等~看下一个字符
{
i++;
j++;
}
else
{
i = i - j + 1;//i回退到以下一个字符为起始重新开始比较
j = 0;//j每次回退到0
}
}
if (j >= len_sub) return i - j;//退出循环是j超出范围~已找到~返回i-j
return -1;
}
int main()
{
const char* str = "abcabcdabcde";
const char* sub = "abcd";
int index = BF_Search(str, sub);
printf("模式串在主串中第一次出现的起始位置下标为:%d\n", index);
return 0;
}
其实改进只是规定了模式串匹配是从主串的下标为pos的位置开始的。
可以给BF_Search()函数加参数int pos,再将i的初始值改为pos即可。(如图)
KMP算法的最主要思想:i坚决不回退,j回退到合适位置(next数组/nextval数组)。
其实next数组就是看最长公共前后缀(next数组前两个初值默认-1,0)。
算法思想:【根据已知求未知】【next数组只与模式串有关】
定义变量i保存当前已知next数组值的最后一个字符下标(初值1),变量j保存i对应的回退位置下标(next[i],初值0)。求下标[i+1~len-1]的next数组值:只要i、j对应模式串字符相同或j==-1(j回退到不能再回退),则j++,next[++i]=j; 开始判断下一个;否则j按照next数组值回退继续循环判断(j=next[j];)。
代码实现:
int* Get_Next(const char* sub)
{
assert(sub != NULL);
int len = strlen(sub);
int* next = (int*)malloc(sizeof(int) * len);
assert(next != NULL);
next[0] = -1;//默认模式串下标从0开始
next[1] = 0;
int i = 1;//变量i保存当前已知next数组值的最后一个字符下标(初值1)变量j保存i对应的回退位置下标(next[i],初值0)
int j = 0;//变量j保存i对应的回退位置下标(next[i],初值0)
while (i + 1 < len)
{
if (j == -1 || sub[i] == sub[j])//i、j对应模式串字符相同或j==-1
{
j++;
next[++i] = j;
}
else j = next[j];//j按照next数组值回退继续循环判断(j=next[j];)
}
return next;
}
KMP只是对BF算法的改进:i坚决不回退,j按照next数组对应值回退。
代码如下:
#include
#include
#include
#include
#include
int* Get_Next(const char* sub)
{
assert(sub != NULL);
int len = strlen(sub);
int* next = (int*)malloc(sizeof(int) * len);
assert(next != NULL);
next[0] = -1;//默认模式串下标从0开始
next[1] = 0;
int i = 1;//变量i保存当前已知next数组值的最后一个字符下标(初值1)
int j = 0;//变量j保存i对应的回退位置下标(next[i],初值0)
while (i + 1 < len)
{
if (j == -1 || sub[i] == sub[j])//i、j对应模式串字符相同或j==-1
{
j++;
next[++i] = j;
}
else j = next[j];//j按照next数组值回退继续循环判断(j=next[j];)
}
return next;
}
int KMP_Search(const char* str, const char* sub,int pos)
{
assert(str != NULL && sub != NULL);//断言
int len_str = strlen(str);
int len_sub = strlen(sub);
assert(pos >= 0 && pos < len_str);//pos合法
int i = pos;//i指向主串开始位置
int j = 0;//j指模式串开始位置
int* next = Get_Next(sub);
while (i < len_str && j < len_sub)//i、j均在长度范围内~循环判断
{
if (j==-1||str[i] == sub[j])//字符比对相等或j==-1无法回退~i++、j++
{
i++;
j++;
}
else
{
j = next[j];//i不回退,j按照next数组回退
}
}
if (j >= len_sub) return i - j;//退出循环是j超出范围~已找到~返回i-j
return -1;
}
int main()
{
const char* str = "abcabcdabcde";
const char* sub = "abcd";
int index = KMP_Search(str, sub,0);
printf("模式串在主串(以0为主串起始匹配下标)中出现的起始位置下标为:%d\n", index);
index = KMP_Search(str, sub, 5);
printf("模式串在主串(以5为主串起始匹配下标)中出现的起始位置下标为:%d\n", index);
return 0;
}
nextval[]:其实是对next数组的改进(在求出next数组的基础上)。当模式串i指向的位置字符==其对应next数组回退位置的对应字符时:nextval [ i ] =nextval [ next [ i ] ] ; 否则直接赋值:nextval[i]=next[i];
代码如下:
int* Get_Nextval(const char* sub)
{
assert(sub != NULL);
int len = strlen(sub);
int* next = (int*)malloc(sizeof(int) * len);
int* nextval = (int*)malloc(sizeof(int) * len);
assert(next != NULL && nextval != NULL);
next[0] = -1;//默认模式串下标从0开始
next[1] = 0;
int i = 1;//变量i保存当前已知next数组值的最后一个字符下标(初值1)
int j = 0;//变量j保存i对应的回退位置下标(next[i],初值0)
while (i + 1 < len)
{
if (j == -1 || sub[i] == sub[j])
{
j++;
next[++i] = j;
}
else j = next[j];
}
//next数组已求出
nextval[0] = -1;//固定
for (int m = 1; m < len; ++m)
{
if (sub[m] == sub[next[m]])//m对应字符==m的回退位置对应字符 ~ nextval的值=回退位置已经赋的nextval数组的值
{
nextval[m] = nextval[next[m]];
}
nextval[m] = next[m];//否则直接用next数组的值赋值
}
free(next);
return nextval;
}