关于KMP算法思想及过程实现[个人理解]

KMP算法简要概述

KMP缘由

在现有程序中,字符串当之无愧是门庭要塞,而字符串匹配也使用频率也相当的高,以往的
做法是使用暴力破解,通过枚举所有字符串匹配状态进行判定,但那也造成时间复杂度
O(n*m)相当高,为此提出KMP算法,有效降低时间复杂度至O(n+m)

暴力算法:

#include<bits/stdc++.h>
using namespace std;

void Massacre(){
	char str[1024],match_str[1024];
	int	size_str,size_match_str;
	scanf("%s\n%s",str,match_str);
	size_str = strlen(str);
	match_str[1024] = strlen(match_str);
	for(int i=0;i<size_str;i++){
		for(int j=0;j<size_match_str;j++){
			if(str[i] == str[j]){
				++i;
				++j;
			}else{
				i = i-j+1;
				j = 0;
			}
		}
	}
}

int main(){
	Massacre();
	return 0;
}

暴力算法每次匹配失败都将 i 和 j 进行回溯,j 回溯至0,i 回溯至 i-j+1位,则最坏情况为:主串循环m次,每次循环遍历n次模式串,故时间复杂度O(n*m),空间复杂度O(1)。

KMP思路

可想而知,暴力法时间复杂度较大原因在于需要循环遍历m次模式串,有什么办法能避免这m次循环呢?
为此我们可以联想如果一个模式串如图1 abd 每个字符各不相同,那么当其中某个字符失配时,前面n位匹配字符将不能在i+n的范围再度匹配 ,如何利用这特殊条件简化算法就是KMP的关键
图1:ab两位将在i+2范围内不能在匹配
既然要求前n位模式串不同能够跳过i+n位,即跳过i+n位后便是当前i需要回溯的距离,这样不就保证 i 能够不回溯而只回溯 j,那么问题便转化为如何让前n位模式串不相同?

这里我们便需要利用到数学的换元思想,当在一个集合内存在我们不想要看到的某些数值,我们能够将它重新归纳一个子集合,并附上代号x。联想字符串要想前面n个数值不相同,那么我们便将前n个数值中前缀和后缀相同的字符归纳为一个子集合(声明下,因为是从模式串头部开始匹配,所以该子集合一定是从头开始的x个字符),那么当这个子集合最大,我们是否便可认为该字符串后缀x前无重复字符。
在这里插入图片描述
既然x前的字符不重复,那我们直接令j回溯到x字符串那位置,主串i不就无需回溯,然后问题进一步变成 讨论如何让j回溯到x?

这样问题不就明朗了,给x个标记,表明前缀x和后缀x距离,让j回溯这段距离不久可以了,这就是next[]数组的由来

#include<bits/stdc++.h>
using namespace std;

int main(){
int next[100];		//next数组表示x尾元素到模式串头部的距离
int backPot = -1,loopIndex = 0;
char match_str[100] = {0};
scanf("%s",match_str);
while(match_str[loopIndex] != '\0'){
        if(backPot==-1 || match_str[backPot] == match_str[loopIndex]){
            ++backPot;
            ++loopIndex;
            next[loopIndex] = backPot;
        }else{
            backPot = next[backPot];
        }
    }
}

首先backPot表示回溯点的位置,loopIndex用来遍历整个模式串,当如果backPot为模式串头部或者存在前后缀字符相等,就将其+1,并记入到 next[]数组中,如果不匹配,证明当前字符不在重复,backPot回溯到前缀x点重新匹配。

next[]求出,那么KMP算法也就算完成了,如下代码:

#include<bits/stdc++.h>
using namespace std;

void KMP_(){
    int next[100];
    int backPot = -1,loopIndex = 0;
    char str[100] = {0},match_str[100] = {0};
    int i,j;
    scanf("%s\n%s",str,match_str);

    next[0] = -1;
    //寻找模式串前后缀子串的匹配next域
    while(match_str[loopIndex] != '\0'){
        if(backPot==-1 || match_str[backPot] == match_str[loopIndex]){
            ++backPot;
            ++loopIndex;
            next[loopIndex] = backPot;
        }else{
            backPot = next[backPot];
        }
    }
    for(i=0,j=0;str[i]!='\0'&&match_str[j]!='\0';){
        if(j == -1 || str[i] == match_str[j]){
            ++i;
            ++j;
        }
        //优化算法,如果前缀子串的后一个字符仍不相等,则继续向前回溯
        else if(str[i] != match_str[next[j]] && j != 0){            
            j = next[next[j]];
        }
        else{
            j = next[j];
        }
    }
    int loc = i-strlen(match_str);
    char ans[100] = {0};
    strncpy(ans,str+loc,strlen(match_str));
    printf("%s location in %d",ans,loc+1);
}

int main(){
	KMP_();
	return 0;
}

输入:
abcdadealkagoasda
ago
输出:
ago location in 11

以上内容表述可能有些抽象,可能也存在错误,有何误点还请大家指教,谢谢

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值