首先我们要了解KMP算法首先要找到一个next数组来表示主串中每一个字符的回退的下标(这个下标是对于真子串而言的,主串不需要回退)(这可能看的很懵逼,但是不要紧,接着往下看你肯定明白了)
例:寻找字符串”abcaefabcdabsd“的子串abcd
寻找字符串”a b c a e f a b c d a b s d“的子串abcd(这里我把题目字符串拉的很开便于大家理解)
主串p[i] a b c a e f a b c d a b s d
主串不回退,子串回退
0 1 2 3 4 5 6 7 8 9 10 11 12 13
a b c a e f a b c d a b s d
(所有从字符串必须从a开始p[i-1]字符结束的相同字符串,而且两个子字符串必须相同。
我们记a的回退下标为-1,那么从字符b开始,准备开始寻找从a字符开始,从p[i]字符前一个字符结束的相同的字符串是否有两个,单一的字符也算
我们可以看到从b字符下标应该是0,因为从a字符开始,从a字符结束的字符串只有1个,那就是a字符,所以b下标是0
同理我们看第三个字符c就是从a字符开始,b字符结束的字符串是否存在两个,很显然是没有的,所以c的下标是0
同理第四个字符也是0
从第五个字符就不一样了,从a开始从e结束相同的字符串没有
我们看第八个字符,前面一个字符以a开始b结束的相同字符串是否有两个,我们可以找到两个(我做红色标记的),(必须a为首,b为尾巴,找到两个相同的字符串),而且这两个相同的字符串的长度为2所以我们把字符c的下标记为2,后面就同理了
0 1 2 3 4 5 6 7 8 9 10 11 12 13
a b c a e f a b c d a b s d主串 p[i] 0 1 2 3 4 5 6 7 8 9 10 11 12 13
a b c a e f a b c d a b s d
i从0开始遍历,j也从0开始,第一个都是a,都往后走i++,j++,然后到第4个字符发现不一样,那么怎么办呢?这个时候就开始用到了next数组了,next[j]赋给j,也就是回退到子串的最开始的字符s[0],因为主串中的第四个字符a对于的next数组下标为0,然后此时的j=-1,怎么办数组越界了,这个时候我们需要把-1回正为0,又可以从子串的第一个字符开始遍历了,直到主串中第7个字符,对于的next数组下标为1 ,我们对于的子串需要回退到下标为1的字符,也就是s[1],然后继续进行到主串第9个字符对于的next数组下标为3,这时把子串回退到s[3],也就是字符d,又开始遍历到第10个字符a,发现前面一个字符9正好就是对应子串最后一个字符,所以我们要返回找到的子串在主串的下标位置,也就是i-j,为什么呢?你看这时的i是10,j是4,对应的返回值是6不就是主串中第6个字符a开始,到第9个字符d就是子串吗?
首先如果我们已经直到next[0]=-1,next[1]=0,怎么推next[2]呢?
主串 p[i] 0 1 2 3 4 5 6 7 8 9 10 11 12 13
a b c a e f a b c d a b s d回退数组next -1 0 0 0 0 0 0 1 2 3 0 1 0 0
我们先假设主串遍历到了第九个字符,但是我不知道第9个字符对应的next数组的数字,我 们的第8个字符的上标为8,然后对应的k是2,这里的p[2]是c,说明p[2]和p[8]相等啊,那么
p[9]的下标怎么求呢?next[9]=2+1,你们看对不对
如果我们要找p[10]的下标呢?同上,先看p[9]的下标为3,对吧,我们看p[9]和p[3],发现根本不相等,怎么办呢?那么我们就从p[3]对应的next值入手,p[3]对应的next值为0,我们再回退到0,发现第p[0]与p[10]相等,这里p[0]的next的值就是对应的p[10]next值+1啦!
下面总结:
//str代表主串
//sub代表子串
//pos代表主串开始移动的位置
//我们定义主串第一个字符下标为-1,第二个就为0
//len是主串的长度
void Getnext(char* str, int* next, int len)
{
next[0] = -1;;//我们定义主串第一个字符对应的next数组第一个元素为-1
next[1] = 0;//第二个就为0
//从第三个开始找next数组的规律
int i = 2;//第三个元素的标号
int k = 0;
while (i < len)
{
if (k==-1||str[i - 1] == str[k])
{
next[i] = k + 1;
i++;
k++;
}
else
{
k = next[k];
}
}
}
核心知识点就上面这些了,如果看明白的话,大家先根据自己的理解设计一下,下面就是源码了,有注释
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
//str代表主串
//sub代表子串
//pos代表主串开始移动的位置
//我们定义主串第一个字符下标为-1,第二个就为0
void Getnext(char* str, int* next, int len)
{
next[0] = -1;;//我们定义主串第一个字符对应的next数组第一个元素为-1
next[1] = 0;//第二个就为0
//从第三个开始找next数组的规律
int i = 2;//第三个元素的标号
int k = 0;
while (i < len)
{
if (k==-1||str[i - 1] == str[k])
{
next[i] = k + 1;
i++;
k++;
}
else
{
k = next[k];
}
}
}
int KMP(char* str,char* sub,int pos)
{
assert(str&&sub);
int i = pos;//遍历主串
int j = 0;//遍历子串
int lenstr = strlen(str);
int lensub = strlen(sub);
//此时都空的字符串
if (lenstr == 0 || lenstr == 0) return -1;
//此时如果遍历主串的pos<0或者大于等于主串长度,也是返回-1
if (pos < 0 || pos >= lenstr) return -1;
int* next = (int*)malloc(lenstr * sizeof(int));//主串的元素个数和next数组相同
assert(next);
Getnext(str,next,lenstr);
while(i<lenstr&&j<lenstr)
{
if (j==-1||str[i] == sub[j])
{
i++;
j++;
}
else
{
j = next[j];
}
}
//遍历完之后
if (j >= lensub)
return i - j;//返回主串找到子串第一个字符的下标
//如果没有找到返回0
return 0;
}
int main()
{
printf("%d", KMP("abdaabc","abc",0));
return 0;
}
下面我们来设计nextval数组
void Getnext(char* str, int* next, int* nextval,int len)
{
next[0] = -1;;//我们定义主串第一个字符对应的next数组第一个元素为-1
next[1] = 0;//第二个就为0
//从第三个开始找next数组的规律
int i = 2;//第三个元素的标号
int k = 0;
while (i < len)
{
if (k==-1||str[i - 1] == str[k])
{
next[i] = k + 1;
i++;
k++;
}
else
{
k = next[k];
}
}
//开始设计nextval数组
i = 2;
nextval[0] = -1;
nextval[1] = 0;
while (i < len)
{
//相同的话就回退到那一个nextval的值
if (str[i] == str[next[i]])
{
nextval[i] = next[next[i]];
i++;
}
//不同的话,就等于next的值
else
{
nextval[i] = next[i];
i++;
}
}
}
原文地址:https://blog.csdn.net/2301_79811170/article/details/134793579
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_41806.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!