最近在刷数据结构,看到了字符串匹配算法KMP,BM,KP等,感觉是面试中应该要会的知识点,就先记录下来了,方便之后的复习查看:
1.KMP算法
KMP算法是在暴力算法之上做了一些改进,不会重复的比对当前比对失败的前缀,即利用了匹配串本身的信息来构造一个查询表next,该表能够指导当次匹配失败下,如何对匹配串进行移动。可以省略掉一些重复比较的无用操作。
1.1next表构造
因为next表的构造其实本质也是字符串的匹配,故其整体的框架和KMP相类似:
int* buildnext(const char* p)
{
size_t m = strlen(p), j = 0;
int* N = new int[m];
int t = N[0] = -1;
while (j < m - 1)
{
if (t < 0 || p[j] == p[t])
{
j++; t++;
N[j] = t;
}
else
t = N[t]; //把t往小的方向拉,匹配更小的真前缀
}
return N;
}
1.2.KMP算法构造:
KMP的核心主要是1.1中的查询表,当次匹配失败时,能够让匹配串跳转到适当的位置,从新开始匹配:
int KMPMatch(const char* p, const char* T)
{
int* next = buildnext(p);
int n = strlen(T), i = 0;
int m = strlen(p), j = 0;
while (j < m && i < n)
if (j < 0 || p[j] == T[i])
{
i++; j++;
}
else
j = next[j];
delete[] next;
return i - j;
}
1.3 next表改进
int* buildnext(const char* p)
{
size_t m = strlen(p), j = 0;
int* N = new int[m];
int t = N[0] = -1;
while (j < m - 1)
{
if (t < 0 || p[j] == p[t])
{
j++; t++;
N[j] = (p[j]!=p[t] ? t : N[t]);
}
else
t = N[t]; //把t往小的方向拉,匹配更小的真前缀
}
return N;
}
2.BM算法
进一步的优化,采用坏字符和好后缀的策略。坏字符是利用字符匹配中的 “教训” 来进行优化,好后缀是利用字符匹配的先验知识 “经验”来进行优化。两者可以结合也可以分开实施优化。接下来分别的介绍坏字符BC和好后缀GS优化策略。
2.1坏字符表构造
坏字符就是匹配串p自右向左与T匹配时,出现不匹配T中的那个字符。根据该不匹配的教训,p要与之匹配的时候,必须在左侧找到这个坏字符,才能进行下一次的匹配:
int* buildBC(const char* p)
{
int* bc = new int[256]; //和字符表等长
for (size_t j = 0; j < 256; j++) bc[j] = -1; //先初始化,假设字符都未出现在p中
for (size_t m = strlen(p), j = 0; j < m; j++)
bc[p[j]] = j; // 记录出现字符在p中的最大Rank
return bc;
}
2.2基于坏字符的匹配算法
既然确定了坏字符的下一次跳转,则可以推出下次匹配的相对位置关系:
int BMMatch(const char* p, const char* T)
{
int* bc = buildBC(p);
size_t i = 0; //T的头指针
while (strlen(T) >= i + strlen(p)) { //从T字符串开始搜索
int j = strlen(p) - 1;
while (p[j] == T[i + j]) //p与T自右向左的比对
if (0 > --j) break;
if (0 > j)
break;
else
i += j - bc[T[i + j]]; //根据bc表来找到p中对应的坏字符,并以此进行移动
}
delete[] bc;
return i;
}