一、前言
什么叫字符串模式匹配?在主串中找到与模式串相同的子串,并返回其所在位置。
子串——主串的一部分,一定存在
模式串——不一定能在主串中找到
字符串匹配模式有两种算法,第一种是:朴素模式匹配算法,第二种是:KMP算法
二、朴素模式算法
主串长度为n,模式串长度为m
朴素模式匹配算法:将主串中所有长度为m的子串依次与模式串对比,直到找到一个完全匹配的子串,或所有的子串都不匹配为止。
最多对比 n-m+1 个子串
当对比到主串第十个字符时,主串中的子串与模式串完全相同,返回是在第几个字符相同的数值。
相关代码:
typedef struct String
{
char* ch;
int length;
}SString;
int Index(SString S, SString T)
{
int i = 1, j = 1;
while (i <= S.length && j <= T.length)//i没有把主串走完,j没有把模式串走完
{
if (S.ch[i] == T.ch[j])
{
i++;
j++;
//如果相等,则向后继续比较字符
}
else
{
i = i - j + 2;//i= i - (j-1) + 1 回到主串上次开始的下一个位置
j = 1;//返回到模式串开头
}
}
if (j > T.length)//说明在主串找到与该模式串相同的子串
{
return i - T.length;//返回模式串在主串中第一次出现的位置
}
else
{
return 0;
}
}
int main()
{
SString S = { "abaabaabaabaabc",15 };
SString T = { "abaabc",6 };
int ret = Index(S, T);
printf("%d ", ret);
return 0;
}
运行结果:
运行的结果与上述分析的结果一致。
三、KMP算法
KMP算法是朴素模式算法的优化,改进思路是:主串指针不回溯,只有模式串指针回溯
那么主串指针不回溯,模式串指针回溯是什么意思呢?这里用流程图来解释
当我们模式串于主串进行匹配的时候,当比较到最后一个字符时才发现才发现是不匹配的。
那么我们可以确定,主串的前五个字符是与模式串是一致的,那我们就不再需要比较主串的前五个字符,同时我们因为知道主串的前五个字符,而模式串中开头也是已知的,因此我们可以从 k=3 的位置开始进行比较,如图:
主串指针 i 保持不变,模式串指针 j=3 ,跳过中间的几个字串,同时模式串也跳过开头的两个元素。这就是KMP算法的思想,利用模式串与主串匹配过程中发生失配的现象,保持主串中的指向不变,对模式串的指向进行调整。
当最后一个字符再次不匹配的时候,再将主串指针i保持不变,模式串指针j=3。
那么,当我们在第五个字符不匹配的时候该怎么办呢?
当第五个字符不匹配的时候,模式串的前四个字符就是已知的,那我们可以保持i的位置不变,j=2
那么第四个字符开始不匹配呢?这是我们可以总结出规律:
当第6个字符匹配失败时,可令主串指针 i 不变,模式串指针 j = 3
当第5个字符匹配失败时,可令主串指针 i 不变,模式串指针 j = 2
当第4个字符匹配失败时,可令主串指针 i 不变,模式串指针 j = 2
当第3个字符匹配失败时,可令主串指针 i 不变,模式串指针 j = 1
当第2个字符匹配失败时,可令主串指针 i 不变,模式串指针 j = 1
当第1个字符匹配失败时,匹配下一个相邻的子串,模式串指针 j = 0
我们可以用一个next数组来对字符失配时,表示 j 该改变的位置
next[0] | next[1] | next[2] | next[3] | next[4] | next[5] | next[6] |
0 | 1 | 1 | 2 | 2 | 3 |
当模式串中的字符改变时,next数组中的内容也会改变,那我们怎么求得next数组呢?
next[1]都无脑写0,next[2]都无脑写1
其他next:在不匹配的位置前,划一根分界线,模式串一步一步往后退,直到分界线之前“能对上”,或模式串完全跨过分界线位置。此时j指向哪,next数组值就是多少。
相关代码:
typedef struct String
{
char* ch;
int length;
}SString;
//朴素模式算法
int Index_KMP(SString S, SString T,int next[])
{
int i = 1, j = 1;
while (i <= S.length && j <= T.length)//i没有把主串走完,j没有把子串走完
{
if (S.ch[i] == T.ch[j] || j==0)//当j=0,也要i++,j++
{
i++;
j++;
//如果相等,则向后继续比较字符
}
else
{
j = next[j];//返回到子串开头
}
}
if (j > T.length)//说明找到子串
{
return i - T.length;//返回子串在主串中第一次出现的位置
}
else
{
return 0;
}
}
int main()
{
SString S = { "abaabaabaabaabc",15 };
SString T = { "abaabc",6 };
int next[7] = { 0,0,1,1,2,2,3 };
int ret = Index_KMP(S, T,next);
printf("%d ", ret);
return 0;
}
运行结果: