本博客用来记录自己的学习内容,以便复习。内容取材自《大话数据结构》。
串的匹配算法旨在找到子串T在串S中的位置,可能会有限制条件,即匹配的起始位置:pos
串的基本知识:
串的第一个元素表示串的长度。
朴素模式匹配:
暴力循环解法
//返回子串T在主串S中第pos个字符之后的位置。若不存在,则返回0
int Index(char * S, char * T, int pos)
{
int i = pos;
int j = 1;
while(i <= S[0] && j <= T[0])
{
if(S[i] == T[j])
{
i++;
j++;
}
else
{
i = i - j + 2;j
j = 1;
}
}
if(j > T[0])
//易错点:这里不能是>=,应为T[0]代表的T串的长度,在循环当中,最后一次j++导致实际值偏大
{
return i - T[0];
}
else
{
return 0;
}
}
这种匹配方法容易写也容易想,但是时间复杂度最坏可以达到(n-m+1)*m!
于是我们就有了KMP模式匹配。
KMP模式匹配:
在朴素模式匹配当中,我们回溯的是i值,也就是回溯的主串S,但是在KMP模式匹配当中,我们通过回溯j值,即回溯T串来实现对一些特殊情况的优化。
重点!重点!重点!
在需要查找字符串前,先对要查找的字符串做一个分析,这样可以大大减少我们查找的难度,提高查找的速度。
next数组值的推导:
{0, 当j=1时
next[j] = {Max{k|1<k<j, 且'p1...pk-1' = 'pj-k+1...pj-1'} 当此集合不为空时
{1 其他情况
void get_next(char *T, int *next)
{
int i, k;
i = 1;
k = 0;
next[1] = 0;
while(i < T[0])
{
if(k == 0 || T[i] == T[k])
{
++i;
++k;
//k从0开始,只要相等就加1,所以k(next[i])是前后相等的最大个数。
next[i] = k;
}
else
{
k = next[k]; //k值回溯的结果很重要,是next推导的关键
}
}
}
匹配算法部分:
//返回子串T在主串S中第pos个字符之后的位置。若不存在,则返回0
int Index_KMP(char * S, char * T, int pos)
{
int i = pos;
int j = 1;
int next[255];
get_next(T, next);
while(i <= S[0] && j <= T[0])
{
//注意这里多了j==0的判断
if(j == 0 || S[i] == T[j])
{
i++;
j++;
}
else
{
//i = i - j + 2;j
//j = 1;
j = next[j];
}
}
if(j > T[0])
//易错点:这里不能是>=,应为T[0]代表的T串的长度,在循环当中,最后一次j++导致实际值偏大
{
return i - T[0];
}
else
{
return 0;
}
}
j = next[j];回溯原理:
当S[i] != T[i]时匹配失败,j指针前移,直到匹配成功,如果一直匹配失败j会归0。
KMP算法仅当模式与主串之间存在许多“部分匹配”的情况下才体现出他的优势,否则两者差异并不明显。
KMP模式匹配升级:
为了解决部分特殊情况,对next数组的推导做出了优化:
void get_nextval(char *T, int *nextval)
{
int i, k;
i = 1;
k = 0;
nextval[1] = 0;
while(i < T[0])
{
if(k == 0 || T[i] == T[k])
{
++i;
++k;
//next[i] = k;
if(T[i] != T[k])
{
nextval[i] = k;
}
else
{
nextval[i] = nextval[k];
}
}
else
{
k = nextval[k]; //k值回溯的结果很重要,是next推导的关键
}
}
}
刚学的时候理解可能比较很困难,只能考记忆,复习多了就能理解了。