KMP算法

KMP算法(Knuth-Morris-Pratt算法)是一种用于字符串匹配的经典算法,其目的是在一个主文本串(text)中高效地查找一个模式串(pattern)的出现位置。相较于朴素的字符串匹配算法,KMP算法具有更高的效率,特别是在处理大文本和长模式串时。

引出KMP算法之前,我们必须了解朴素的字符串匹配算法(暴力算法)的实现
假设主文本串 text = “ababcabcababcab”,模式串 pattern = “ababcab”

朴素算法(暴力算法的实现)

假设文本串为"ababcaccababcab",模式串为"ababcab";
朴素算法的实现过程:
在这里插入图片描述
两个指针分别指向两个字符串的首字母,逐个匹配。
在这里插入图片描述
像这样,如果我们遇到不匹配的情况我们要进行回溯,重新进行匹配。
在这里插入图片描述
文本串回溯到到开始匹配位置的下一个位置,模式串回溯到起始位置,重新匹配
在这里插入图片描述
按照上面的过程以此类推,最后成功匹配字符串。那么判断成功匹配的标志是什么呢?
判断成功的标志就是:指向文本串的指针刚好指向末尾或者还没指向末尾,但是模式串指针已经指向末尾了,就算匹配成功。
在这里插入图片描述

int substr(char* arr1.char* arr2) {
	int i = 0;//指向文本串
	int j = 0;//指向模式串
	for (int i = 0; i < strlen(arr1); i++) {
		//for循环记录当前从哪个位置开始匹配
		int cur = i;//cur记录文本串当前匹配的位置
		while (cur < strlen(arr1) && j < strlen(arr2) && arr1[cur] == arr2[j]) {
			//只要cur和j都在各自字符串范围内,并且字符匹配,指针就指向下一个字母
			cur++; j++;
		}
		//循环中断有三种情况,分别处理:
		//1.cur指向的字符串结束,j指向的字符串还未找到子串,说明找不到子串,返回-1.
		if (cur == strlen(arr1) && j < strlen(arr2)) return -1;
		//2.j指向的字符串到结尾,说明模式串的所有字符匹配成功 返回文本串开始匹配的位置
 		if (j == strlen(arr2)) return i;
		//3.由于字符不匹配导致的循环中断,模式串指针回到起点
		j = 0;
	}
 }
}

朴素算法的弊端:
字符串如果匹配失败,就会在文本串开始匹配的位置的下一个字母再匹配,如果是非常长的字符串,这样的时间复杂度是非常高的。能不能在这匹配的基础上加点优化呢?

KMP算法的原理

在这里插入图片描述
假设是这样一个字符串,我们希望找到一些有效信息来避免一些重复的比较。
我们不难发现文本串的5号a和模式串的5号a匹配,然后0号也是a。
那么他们三者相等,我们可不可以直接从文本串的第6位开始匹配,模式串的第2位开始匹配呢?
在这里插入图片描述
像这样,就会省略了很多重复的过程,加大了匹配效率。
那我们就希望,在匹配不成功的时候,找到匹配不成功前一个位置的串中相同的部分,

比如:ababcab 最后字母b的前字符串:ababca 中相同的部分第一个a 和 最后一个 a
由于,两个a都已经和文本串匹配,内部之间的a又已经匹配,那么下一次匹配的时候第一个a就不用匹配了,直接匹配第一个a的下一个字母,省略重复的步骤。

那么怎么获取字符串相同的部分呢,我们用一个数组存储这个相同的部分,称为next数组

next数组的获取

next数组其实就是一个字符串内部的匹配过程
在这里插入图片描述
next[i]表示第i个字母前相同的部分最大的字符数
第一个字母只有a,最起码有两部分才能有相同的部分,所以next[1]=0;
第一个字母和第二个字母进行匹配,无法匹配,next[2]=0;
在这里插入图片描述
第一个字母和第三个字母比较,匹配成功,next[3]=1;,指针加一。
在这里插入图片描述
第二个字母和第四个字母比较,匹配成功,next[4]=2; 相同部分又2个字符
在这里插入图片描述在这里插入图片描述

第三个字母和第五个字母比较,匹配失败,这时候和上面机制是一样的,先找第三个字母前有相同的部分,回到前面那个字母的下标,比如aba的第一个a无法匹配,那么这个位置的相同部分为0,next[5]=0;

在这里插入图片描述
匹配成功,next[6]=1;
在这里插入图片描述
匹配成功,next[6]=2;

这样next数组就找到了
我们来看看代码

for(int j=0,i=2;i<=n;i++){
		//j=0,方便找到j前的字符串相同的部分
        while(j && p[j+1]!=p[i]) j=ne[j];
        if(p[j+1]==p[i]) j++;
        ne[i]=j;//i位置相同的部分就是j长度
    }

如果了解了next数组怎么来,主函数部分就简单了

//和next数组的匹配差不多
 for(int j=0,i=1;i<=m;i++){
        while(j && p[j+1]!=s[i]) j=ne[j];
        //如果不匹配,让j回到第一个相同的部分的下标
        if(s[i]==p[j+1]) j++;
        
        if(j==n){
            cout << i-j << " ";
        }
        
    }

以上是KMP我对KMP算法的理解,但是我觉得理解的不是很好,如果有错误请指出,感谢!创作不易,如果您们有收获的话,可以点下赞吗,谢谢啦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值