蓝桥杯——KMP算法

  • 问题
    给定一个模式串p,和一个长文本t,求p是否为t的一个子串,如果是则返回子串的首地址
  • 暴力解法
    逐位对比模式串p和长文本t,如果不匹配,则回溯指向t和指向p的指针,再从头开始比对t和p。时间复杂度为O(nm)
  • KMP算法
    KMP是一种时间复杂度为O(n)的算法。他的核心思想是当p[j]和t[i]失配时,我们不回溯指针i,只回溯指针j,然后再重新开始比对。KMP的核心思想是确定模式串中p[j]如果失配则j应该移动的位置next[j]
  • next[j]的定义
    n e x t [ j ] = k   m a x ( p [ 0... k − 1 ] = = p [ j − k . . . j − 1 ] ) next[j]=k \ max(p[0...k-1]==p[j-k...j-1]) next[j]=k max(p[0...k1]==p[jk...j1])
    我们一般称k位字符串p[0...j-1]的最长公共前后缀,即p[0.. .j-1]中最多有最后k个字符和最前k个字符是一一对应的
  • 数学证明
	当p[j] !=t[i]时 
	显然有p[0...j-1]==t[i-j...i-1] 
	那么对于p[0...j-1]的最大公共前后缀p[0...k-1]==p[j-k...j-1] 
	由于p[0...j-1]==t[i-j...i-1]
	所以有p[0...k-1]==t[i-k...i-1]
	考虑到如果模式串p在t中存在,那么p要不包含t[i-1]要不在t[i-1]的右边,
	所以当k==0时说明不存在最长公共前后缀,那么p肯定在t[i-1]右边,此时j
	回退到0,继续比较t[i]和p[j]。如果k>0说明存在最长公共前后缀,
	即在t[i-k...i-1]与p[0...k-1]匹配,此时由于k是最大的,所以不会有遗漏的
	匹配情况,之后我们令j=k,继续匹配p[j]=t[i]
  • 核心代码
    KMP的核心代码是求解每个p[j]失配时的回退位置next[j],即p[0…j-1]的最长公共前后缀
vector<int> nextDp(1000000,0);
void initNext(string &p){
    
    
			//初始化next
			//第0位默认是-1 
			nextDp[0]=-1; 
			int j=0;
			int k=-1;
			//自底向上逐位确认最长公共前后缀 
			while(j<p.size()){
    
    
				if(k==-1||p[j]==p[k]){
    
    
					//对于第1位对应的最长公共前后缀默认为0
					//如果p[j]和对应的p[k]相等则j+1的最长公共前后缀就是next[j]+1=k+1 
					nextDp[++j]=++k;
				}else{
    
    
					k=nextDp[k];
				} 
			} 
		}
  • 匹配代码
int searchStr(string &t,string &p){
    
    
			//指向t 
			int i=0;
			//指向p 
			int j=0;
			while(i<n){
    
    
				if(t[i]==p[j]||j==-1){
    
    
					//t[i]和p[j]匹配时i和j加1 
					//当不存在任何一个与t[i]匹配的公共前缀时j==-1
					//j变为0,i移向下一位
					i++;
					j++; 
				}else{
    
    
					//t[i]和p[j]不匹配时,j回退
					j=nextDp[j]; 
				}
				if(j==p.size()){
    
    
				//找到子串
				return i-m;
				}
			}
		}
  • KMP的模板
#include <iostream> 
#include <algorithm>
#include <bits/stdc++.h>
#include <map>
#include <queue>
using namespace std;
string s1;
string s2;
int n;
int m;
vector<int> nextDp(1000008,0);
class KMP{
    
    
	public:
		void initNext(string &p){
    
    
			//初始化next
			//第0位默认是-1 
			nextDp[0]=-1; 
			int j=0;
			int k=-1;
			//自底向上逐位确认最长公共前后缀 
			while(j<p.size()){
    
    
				if(k==-1||p[j]==p[k]){
    
    
					//对于第1位对应的最长公共前后缀默认为0
					//如果p[j]和对应的p[k]相等则j+1的最长公共前后缀就是next[j]+1=k+1 
					nextDp[++j]=++k;
				}else{
    
    
					k=nextDp[k];
				} 
			} 
		}
		int searchStr(string &t,string &p){
    
    
			//指向t 
			int i=0;
			//指向p 
			int j=0;
			while(i<n){
    
    
				if(t[i]==p[j]||j==-1){
    
    
					//t[i]和p[j]匹配时i和j加1 
					//当不存在任何一个与t[i]匹配的公共前缀时j==-1
					//j变为0,i移向下一位
					i++;
					j++; 
				}else{
    
    
					//t[i]和p[j]不匹配时,j回退
					j=nextDp[j]; 
				}
				if(j==p.size()){
    
    
					//找到一个出现的位置
					return i-m;
				}	
			}
		}
		
};
int main(){
    
    
	cin>>s1>>s2;
	n=s1.size();
	m=s2.size();
	//初始化next,即模式串中每个位p[j]的最长公共子序列next[j]
	KMP kmp=KMP();
	kmp.initNext(s2);
	//查询出现的位置
	int res=kmp.searchStr(s1,s2);
	cout<<res<<endl;
	return 0;
} 

猜你喜欢

转载自blog.csdn.net/qq_33880925/article/details/129302316