算法/题解-KMP

题目链接:

luogu

题目描述:

给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置。然后输出next数组

题解:

这篇题解鸽了好久,因为我一直都没有完全理解看猫片,这东西是真的难。KMP的用处就是在母串里面找子串。
朴素做法是一位一位地匹配,判断子串和母串的字符是否相同,所以时间复杂度是O(mn)。(m、n是母串和字串的长度)
KMP就是通过一些神奇的操作跳过一些字符,以达到优化时间复杂度的效果。
具体的做法理解了其实并不复杂,一共就是3步。
1.找出子串内最长相同前后缀
举个例子:子串是AABAABAA
那么A和AA都是相同前后缀,但由于AA比较长,所以我们就选择它

很简单吧。。。
2.在后缀的最后设置一个指针,指向其最长相同前缀的最后
后缀的最后其实就是字符串的最后啦。。。
这个指针就是所谓的next数组

我们依次对子串的每一位进行这两步操作,第i位的指针就是next[i]

3.看代码。。。


int KMP(int len1,int len2){ //母串的长度为len1,子串的长度为len2
//s1时母串,s2是子串
    k=0;  //字串指针
    for(int i=0;i<len1;i++){    //和朴素算法一样,依次匹配母串的每一位
        while(k>0&&s1[i]!=s2[k]) k=next[k];   
        if(s1[i]==s2[k]) k++;   //如果匹配成功,匹配下一位
        if(k==len2) cout<<i-len2+2<<endl;  //如果全部匹配成功,输出答案
    }
 }

重点解释一下没加注释的那行代码,在朴素算法中如果匹配失败我们要把字串的指针归零(下面有朴素算法可以自行比较一下),但在KMP算法中我们直接把指针返回到next[i]记录的点,也就是我上面说的"相同前缀的最后“,因为既然这一段后缀和前缀是相同的,那么也就没有匹配再次匹配这一段的必要了,可以直接从相同部分的后一位开始匹配,这样就能达到节省时间的目的。
再举个例子:

A B C B C B C A A
i
B C B C A
next[k] k

这里母串是ABCBCBCAA,子串是BCBCA
匹配到第6位时k>0&&s1[i]!=s2[k]
朴素算法这时就会i++,k=1重新开始匹配
但是我们已经匹配了4位,就差一位了,这么好的成功机会,怎么能放过呢?
所以我们让k=next[k]=3,这样做是对的,因为字串的1、2位和3、4位是完全一样的,所以可以直接把第3位移到第5位的位置匹配

朴素:

A B C B C B C A A
i
B
k

KMP:

A B C B C B C A A
i
B C B
k

朴素算法:

bool solved; 
int solve(int len1,int len2){
	solved=0;
	for(int i=0;i<len1;i++){
		for(int k=1;k<=len2;k++){
			if(s1[i+k-1]!=s2[k]){
				break;
			}
			if(k==len2) solved++;
		}
		if(solved) return i;
	}
}

其实我也不知道这段写的对不对…


那么,就只剩下最后一个问题了
怎么求next数组?

好问题。
可惜我也不会

每当发现失配时,就要往前跳,如果实在找不到,就跳到0,表示没有一个字符能够匹配。这时有字符匹配就将next设为当前位置+1,没有就是0

其实光看代码挺简单的,所以背下来就行了

void find_next(int len2){
	k=0;
	for(int i=1;i<len2;i++){
		while(k>0&&s2[i]!=s2[k]){
			k=next[k];
		}
		if(s2[i]==s2[k]){
			next[i+1]=++k;
		}
		else{
			next[i+1]=0;
		}
	}
}

首先解释一下前面可能没有说清楚,next表示的是字符串后缀的最后,也就是目前的长度i+1的指针,而不是i,同样它指向的点也是++k而不是k,因为只有相同前后缀的后面那个点才是我们真正想要的那个点。

这是字符串ABAABCABAAC的next值

s2 A B A A B C A B A A C
next 0 0 1 1 1 2 0 1 2 3 4

注意: 这里next表示的位置比实际位置大1,也就是说next[1]表示的数是0的next值,next[len2]表示的数是len2-1的next的值,因为输入时是0~len2-1,输出时却是1~len2

呼,终于写完了


刚学完KMP时,我有一个问题:如果子串的所有字符都不相同,那么字符串每一项的next值就都是0了,那么此时用KMP不就和朴素还有什么区别?

答案是:没有区别

是的,如果所有字符都不同,KMP确实一点用都没有QwQ

所以,对于KMP,重复度越高的字符串,越能节省时间。如果重复度基本为0,还是乖乖打暴力吧!

完整代码:
#include 
using namespace std;
const int maxn=1e6+10;
char s1[maxn],s2[maxn];
int next[maxn];
int k=0;
void find_next(int len2){
	k=0;
	for(int i=1;i<len2;i++){
		while(k>0&&s2[i]!=s2[k]){
			k=next[k];
		}
		if(s2[i]==s2[k]){
			next[i+1]=++k;
		}
		else{
			next[i+1]=0;
		}
	}
}
int KMP(int len1,int len2){
	k=0;
	for(int i=0;i<len1;i++){
		while(k>0&&s1[i]!=s2[k]) k=next[k];
		if(s1[i]==s2[k]) k++;
		if(k==len2) cout<<i-len2+2<<endl;
	}
}
int main(){
	cin>>s1>>s2;
	int len1=strlen(s1),len2=strlen(s2);
	find_next(len2);
	KMP(len1,len2);
	for(int i=1;i<=len2;i++){
		cout<<next[i]<<" ";
	}
	return 0;
}

写作时间:

2019-8-11

你可能感兴趣的:(2019暑,Hello,World,题解,算法,模板,KMP)