KMP算法是数据结构串中重要的知识点,本人凭借b站大佬正月点灯笼和有关博文总结加上个人理解来写出此帖。
KMP算法的主要作用是用作在一个已知的字符串去查找子串的位置,例如在“ABAACABABCAC”中搜索查找“ABABC”,用传统的暴力拆解的方法‘需要经过多次回溯,时间复杂度最好的情况下也是O(n),简单的尚且可以,但是稍作复杂便效率太低下。
而KMP算法采用了用空间来换取时间的方式来优化,具体的思维方式如下(先说思维,后面再谈代码的实现方式):
已知的字符串T: ABAACABABC
要比较的字符串P:ABABC
首先我们要做的是找到要比较的字符串P的前缀;
对前缀的名词通俗解释:例如AA的前缀为1,而ABA的前缀也为1(因为首尾的字母相同 ),还有例如像ABAB的前缀为2(因为首尾可以分为两部分AB,因此前缀为2)
我们对要比较的字符串P建立前缀表,建立的前缀表如下:
-1
0 A
0 AB
1 ABA
2 ABAB
0 ABABC
让我们在前缀表的A上的0之前补齐-1(具体原因下面会讲)
将建立好的前缀表prefix匹配在字符串P的下面,建立以下的表;
让我们开始匹配:
首先对P的前三位进行匹配,都能够完全匹配,但是到上标3的B时显示与T无法进行匹配
这时我们应该怎么做呢?这时应该找到B对应的前缀,然后以此去对应上标。具体实现如下:
如果无法匹配,重复这一步骤,直到最后匹配成功(此处解释为什么要用-1,因为当第一个字母也无法匹配时就要往前移动一格,为了方便起见,将第一个定为-1,方便向后移动)
以上就是KMP匹配算法的基本思路,下面是具体的实现过程:
分为三点:
1.建立要匹配字符的前缀表(此处的前缀表按照一开始的前缀表去排列,未加入-1)
2.将前缀表往后移动一格(为了在前缀表中加入-1)
3.KMP函数
最后用主函数实现调用;
建立匹配字符的前缀表:难点在于如何找到相同的前后位,以ABAB为例,如下图解释:
void prefix_table(char pattern[], int prefix[], int n)
{
prefix[0] = 0;
int len = 0;//长度
int i = 1;//检测i个字母
while(i<n)
{
if (pattern[i] == pattern[len])
{
len++;
prefix[i] = len;
i++;
}
else {
if (len > 0) {//定义界限
len = prefix[len - 1];
}
else{
prefix[i] = len;
i++;
}
}
}
}
对于此段代码的解释如下:if语句表明如果pattern(P)的第i项等于第len项那么它的前缀就+1;
第一个else语句是为了确保具有界限,为0时可以运行;
第二个else语句len=prefix[len-1]对此引用b站用户的解释:
当两个字符串不适配的时候,回溯回退的位置始终是模式串的不适配字符的上一位字符前后缀相同的位置,求next数组,前缀与后缀不适配时,同样是移动模式串,只不过这个模式串是自身的前缀部分.如下图解释所示:
下面进行第二步,将前缀表向后移动一格,具体的代码如下:
void move_prefix_table(int prefix[], int n)
{
int i;
for (i = n - 1; i > 0; i--) {
prefix[i] = prefix[i - 1];//逆序排列
}
prefix[0] = -1;
}
此时完成了将前缀表往后移动一格以及将第一位更改为-1,实现了KMP算法所要求的前缀表。
下面进行kmp函数的概述:
void kmp_search(char text[], char pattern[])
{
int i, j, m, n;
int n = strlen(pattern);
int *prefix = malloc(sizeof(int)*n);
prefix_table(pattern, prefix, n);
move_prefix_table(prefix, n);
//text[i] len(text)=m;
//pattern[j] len(pattern)=n;
//一开始j指向pattern第一位 i指向text第一 ;
while (i < m)
{
if (j == n - 1 && text[i] == pattern[j])
{
printf("found pattern at%d\n", i-j);
j = prefix[j];
}
if (text[i] == pattern[j])
{
i++; j++;
}
else {
j = prefix[j];
if (j == -1)
{
i++;
j++;
}
}
}
}
此处解释:j=prefix[j];是为了确保匹配到已知的字符串之后,还能继续匹配,而赋予j是为了找到最大的匹配值。
再使用main函数实现调用即可;
int main()
{
char pattern[] = "ABABC";
char text[] = "ABAACABABC";
kmp_search(text, pattern);
return 0;
}
以上就是有关KMP算法的全部内容。