KMP算法

KMP算法

参考https://www.bilibili.com/video/BV1Er421K7kF/

计算前缀函数

pi_i的定义:第i个前缀的最长匹配真前后缀的长度。

int len = pi[i-1];//pi[i-1]代表第i-1位上最长相等前后缀的《长度》!
《下标》为len:落在了这些长度的下一个元素。
所以要考察:第len位字符和当前的第i位字符是否相等,而不是第(len+1)位。
for(int i=0;i<s.size();i++){
	int len = pi[i-1];//前一位的前缀函数(最长相同前后缀的长度)
	if(str[i]==str[len]){//如果这位也相等
        pi[i]=len+1;//这意味着这一位的最长相同前后缀的长度等于前一位的长度+1。
	}//相等情况
	//if(str[i!=str[len]])//不等,找第二长,第三长.....第n长的前缀
	//可以用while来代替。
	while(len>0&&str[i]!=str[len]){
	len = pi_next(i-1)=pi[len-1];//找下一长的pi(最长相等前后缀长度)。长的不行,找短一些的试试。
	//一直找。。。直到len=0。(没有最长相等前后缀,即长度为零。)
	}
}

问题是:找到i-1这个位置上的第二长的相等前后缀(除了橙色大括号,没有比它更长的了)

橙色=绿色(不知道多少,末尾x个)(两个橙色大括号的内容完全相同,所以末尾x个元素也相同)

橙色=蓝色(定义,要求的第二长的相等前后缀)

故:绿色=蓝色,发现:是橙色大括号的最长相等前后缀,没有比它更长的了,若有,要求的就不是第二长的相等前后缀了。

求橙色括号的最长相等前后缀,如何求?括号部分的就是0-len-1,之前定义过的:pi[len-1]。

所以:pi_next[i-1](下一长的第i-1这个位置上的最长公共前后缀长度)=pi[len-1];

求前缀函数:

vector<int> calc_pi(string s){
	vector<int> pi;
	pi[0]=0;
    for(int i=1;i<s.size();i++){
        int len=pi[i-1];
        while(len>0&&s[len]!=s[i]){
            len=pi[len-1];
        }
        if(s[len]==s[i])
            pi[i]=len+1;
    }
    return pi;
}

求出了前缀函数,接下来就要进行匹配。

匹配

此时有两种方法。

1.即为传统的:求子串的前缀函数,放入主串中匹配。

2.是上述视频中提出的,将子串和主串拼接在一起,中间用一个子串,主串中都不会出现的字符来隔开,求这个新串的前缀函数,如果在字符后找到了pi(i)=子串长度的i,则说明找到了匹配串,再减去偏移量,就得到了匹配子串的首地址。

先实现1:

int findstr(string main,string pattern,vector<int> pi){
    int index=0;//子串开始
    for(int i=0;i<main.size();i++){
        while(index>0&&main[i]!=pattern[index]){//在非开头位置失配
            index=pi[index-1];//回想三个区间,找到第二长的前缀
        }
        if(main[i]==pattern[index]){//匹配,与上面求pi不同
            index++;
            if(index==pattern.size()){
                return i-pattern.size()+1;
            }
        }
    }
    return -1;//没有在里面return,只能是没找到。
}

实现2:(从头到尾)

int findstr(string main,string pattern){
    string s = pattern+'#'+main;
    vector<int> pi(s.size());
    pi[0]=0;
    for(int i=1;i<s.size();i++){
        int len =pi[i-1];//pi的定义是长度!下标=长度-1,所以s[len]是长度为len的子串的下一个元素
        while(len>0&&s[len]!=s[i]){//失配
            len=pi[len-1];//三个区间,找到第二长的前缀长度。
        }
        //这里的顺序不能颠倒,一定是先处理失配情况,再处理匹配情况。
        //如果先处理了匹配情况,未匹配,失配后修改了len,就有可能匹配却未处理。
        if(s[len]==s[i]){//匹配
            pi[i]=len+1;
        }
        
    }
    for(int i=0;i<pi.size();i++){
        if(pi[i]==pattern.size()){
            return i-2*pattern.size();//一个是新串中插入了pattern,一个是因为找到的地方在pattern的末尾。
        }
    }
    return -1;
}
posted @ 2025-02-05 22:47  yukino_Yui  阅读(79)  评论(0)    收藏  举报