一、暴力匹配算法(BF)
BF全称为Brute-Force,最简单直观的模式匹配算法。
1.算法思想
两个字符串进行匹配时,一个主串和一个模式串,就是按照我们最容易想到的算法来进行匹配。用两个变量i,j分别记录主串和模式串的匹配位置,如果两者在某个字符不匹配,则将记录主串匹配位置的变量i回退到匹配前i的位置的后一个位置,将j回头到模式串的第一个字符位置;如果i和j指向的主串和模式串字符匹配,则将两者同时向后走一个位置,继续比较;最后,如果j的位置已经走过了模式串的所有字符,则说明此时匹配成功。
这个算法很简单,也容易理解,下面直接给出代码。
2.代码实现
#include<stdio.h>
#include <string.h>
int BF(const char *MainStr, const char *PatternStr)
{
int i = 0; //主串的匹配起始位置
int j = 0; //模式串的匹配起始位置
int iMainLen = strlen(MainStr); //主串长度
int iPatLen = strlen(PatternStr); //模式串长度
while (i < iMainLen && j < iPatLen)
{
if (MainStr[i] == PatternStr[j])
{
++i; //当前字符匹配成功,两者均往后走一个位置
++j;
}
else
{
i = i - j + 1; //每次未匹配成功时i回退到原来i的后一位置
j = 0; //模式串每次未匹配成功时回退到0位置
}
}
if (j >= iPatLen) //条件满足说明j走过了模式串的所有字符
{
return i - j;
}
return -1;
}
int main(void)
{
const char *MainStr = "abcdabddabc";
const char *PatternStr = "dabc";
int iPos = BF(MainStr, PatternStr);
printf("iPos = %d\n", iPos);
return 0;
}
二、KMP算法
KMP算法的高明之处在于当主串和模式串在某个字符不匹配时,指示主串匹配位置的变量不需要回退,而直接回退指示模式串匹配位置的变量,而且该变量回退时也不需要像BF算法中回退到起始位置,而是基于原来已匹配过的结果来回退。
关于KMP算法推导的过程很多博客中都有,而且很多书上也讲的很清楚,大家可以参考严奶奶的《数据结构》,里面有很清楚的推导,我就不写推导过程了。
下面就我在理解KMP算法中遇到的问题加以说明,并给出KMP算法的实现代码。
问题1.next数组的本质
找到匹配成功部分的两个尽可能长的相等的真子串,一个子串以0下标开始,另一个真子串以j-1下标结尾。(这里的j表示模式串的第j个位置,从0开始计数)
问题2.next[j]=k的含义
next [j] = k,代表j 之前的字符串中有最大长度为k 的相同前缀后缀
问题3.解释next[0]=-1,next[1]=0
假设i指示主串的当前匹配位置,j指示模式串的当前匹配位置。从问题2可以知道next[0]=-1的含义就是当
了。但是我们知道下一次匹配的情况,那就是主串的匹配位置往后走一个(即i+1位置),继续和模式串的0号位置匹配,所以此时应该将模式串的匹配位置也向后走一个(即从-1加1为0,从0号位置开始匹配,便于写代码)。
同理next[1]=0,这个更容易理解,它表示的含义是当模式串的1号位置和主串不匹配时,模式串应该回退到0号位置进行匹配,这是理所当然的做法,也是退到无路可退。
问题4.解释if(j==-1 || sub[j] ==s[i])中j==-1这个条件
当j为-1时,说明此时模式串的第一个字符和主串不匹配,此时模式串第一个字符应该和主串的下一个字符继续比较,即应该要sub[0]和s[i+1]比较,这两种状态切换也是i++和j++的一个过程,所以可以将两者合在一起来写。
代码实现
#include<stdio.h>
#include <string.h>
#include <stdlib.h>
void GetNext(const char *PatternStr, int *next)
{
int i = 1;
int j = 0;
next[0] = -1;
next[1] = 0;
int iPatLen = strlen(PatternStr);
while (i < iPatLen)
{
if (j == -1 || PatternStr[i] == PatternStr[j])
{
++i;
++j;
next[i] = j;
}
else
{
j = next[j];
}
}
}
int KMP(const char *MainStr, const char *PatternStr)
{
int i = 0;
int j = 0;
int iMainLen = strlen(MainStr); //主串长度
int iPatLen = strlen(PatternStr); //模式串长度
int *next = (int*)malloc(sizeof(int) * iPatLen);
GetNext(PatternStr, next);
while (i < iMainLen && j < iPatLen)
{
if (j == -1 || MainStr[i] == PatternStr[j])
{
++i;
++j;
}
else
{
j = next[j];
}
}
if (j >= iPatLen)
{
return i - j;
}
return -1;
}
int main(void)
{
const char *MainStr = "abcdabddabc";
const char *PatternStr = "dabc";
int iPos = KMP(MainStr, PatternStr);
printf("iPos = %d\n", iPos);
return 0;
}
在最后给出一点提醒,大家仔细看KMP()和GetNext()这两个函数,可以发现其实两者很相似,想想为什么,可以更好帮助你理解KMP算法。