KMP算法

看博客好多人都说这本来应该在数据结构里边学,奈何我一点印象都没有,估计当时老师也跳过了。然后今天从9点看到现在,好像才大致懂了

参考的这篇博客,真的很nice!!https://blog.csdn.net/v_JULY_v/article/details/7041827?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2

emmm,然后,我在这个算法上边遇到的第一个坎,竟然是分不清楚文本串(test)和模式串(pattern)??算法里边没说清楚next是对谁求得。这里明确一下:

  • 模式串应该是短的那个,求next数组的对象是模式串

目录

字符串的前缀和后缀:

暴力求解算法

 KMP算法

1、求最大长度表

2、求next数组

3、完善KMP算法

Number Sequence


字符串的前缀和后缀:

先理解字符串的前缀和后缀

  • 前缀:除字符串的最后一个字符外,字符串的所有头部子串
  • 后缀:除字符串的第一个字符外,字符串的所有尾部子串

举一个例子来看

字符串abcjkdabc

  • 前缀有[abcjkdab,abcjkda,abcjkd,abcjk,abcj,abc,ab,a]

  • 后缀有[bcjkdabc,cjkdabc,jkdabc,kdabc,dabc,abc,bc,c]

该字符串前缀与后缀相同的最大长度为3,即abc。

这一概念必须理解,在下边的next求解中会用到

暴力求解算法

#include<iostream>
#include<string.h>
#include<string>
using namespace std;

int ViolentMatch(char* s,char* p){
	int slen=strlen(s);
	int plen=strlen(p);
	int i=0,j=0;
	while(i<strlen(s)&& j<strlen(p)){
		if(s[i]==p[j]){   //成功匹配 
			i++; 
			j++;
		}
		else{  //未成功匹配,i回溯,j=0 
			i=i-j+1;
			j=0; 
		}
	}
	if(j==strlen(p)){  //说明成功匹配 
		return i-j; 
	} 
	else{   //未成功匹配返回-1 
		return -1;
	}
}
int main(){
	char* s="BBC ABCDAB ABCDABCDABDE";char* p="ABCDABD";
	//cout<<s<<p;
	cout<<ViolentMatch(s,p);
	return 0;
} 

 KMP算法

1、求最大长度表

即寻找最长前缀后缀

    如果给定的模式串是:“ABCDABD”,从左至右遍历整个模式串,其各个子串的前缀后缀分别如下表格所示:

    也就是说,原模式串子串对应的各个前缀后缀的公共元素的最大长度表为(下简称《最大长度表》):

那么·最大长度表有什么用呢?

最大长度表中每个元素long_length(j-1)=k的含义

  • 定义:原模式串子串对应的各个前缀后缀的公共元素的最大长度。
  • 在p[0]~p[j-1]中,p[0],p[1]……p[k-1]=p[j-k],……p[j-1],即这两个字符串是相等的,长度为k。这两个字符串以下记为p1、p2
  • 看个图:如下,有蓝底的两块是完全相等的,每块长度为k

  • k-1:与s2相同的字符串s1最后一个字符(即p[k-1])的下标,
  • 如果  t[i]!=p[j]  接下来 j 应该移动到k,因为可以看出,由于p[j-k]~p[j-1]已经和t中对应位置成功匹配了,所以p[0]~p[k-1]也可以和刚刚t串的对应位置相匹配

总结一下我的理解:

前缀后缀和 t[i]!=p[j]  时 j 的移动是如何关联起来的?:

前缀必定从头(p[0])开始,后缀必定以最后一个字符(p[j-1])结束,如果前缀和后缀有长为k的公共元素,就是从 p[j-1] 往前的k个元素(p[j-k]~p[j-1]),与从字符串头开始的k个元素(p[0],p[1]……p[k-1])。此时如果  t[i]!=p[j] ,j往前移动 j-k 就可以到p[k],这样p[k]之前的k个元素就不用再去重新匹配了。而此处的 j-k 中的k,正是long_length(j-1)也是next(j)

这样就可以把next数组与j的移动联系起来啦

2、求next数组

他和long_length的关系红字已经解释了

最大长度表整体右移一位得到next数组,开头补-1,可以当成一个特殊标记,表示不存在

已知next [0, ..., j],如何求出next [j + 1]呢?

对于P的前j+1个序列字符:

  • 若p[k] == p[j],则next[j + 1 ] = next [j] + 1 = k + 1;
  • 若p[k ] ≠ p[j],如果此时p[ next[k] ] == p[j ],则next[ j + 1 ] =  next[k] + 1,否则继续递归前缀索引k = next[k],而后重复此过程。 相当于在字符p[j+1]之前不存在长度为k+1的前缀"p0 p1, …, pk-1 pk"跟后缀“pj-k pj-k+1, …, pj-1 pj"相等,那么是否可能存在另一个值t+1 < k+1,使得长度更小的前缀 “p0 p1, …, pt-1 pt” 等于长度更小的后缀 “pj-t pj-t+1, …, pj-1 pj” 呢?如果存在,那么这个t+1 便是next[ j+1]的值,此相当于利用已经求得的next 数组(next [0, ..., k, ..., j])进行P串前缀跟P串后缀的匹配。

优化:

nextTable[j]=k;

变为

if(p[j]!=p[k]) {
	nextTable[j]=k;  //next[j + 1 ] = next [j] + 1 = k + 1
} else {
	//p[j+1]=p[k+1]
	//如果p[j]=p[next[j]], 应该继续递归, k=next[k]=next[next[k]]
	nextTable[j]=next[k];
}

如果 p[j]==p[k]时,还要进行 nextTable[j]=k;,最后就会导致p[j]=p[nextTable[j]]。p[j]匹配失败时,与他相同的p[nextTable[j]]必然匹配失败,这一步是非常多余的,所以 当p[j]==p[k]时,不进行 nextTable[j]=k;而是继续递归,直到找到和 p[j]不同的p[nextTable[j]]才停止。

//char p[100]; 模式串
//m为p的长度
void GetNextTable(int m){   //m是模式串的长度 
	int j=0;
	nextTable[0]=-1;   //初始值赋值-1 
	int k=-1;
	while(j<m){
		if(k==-1 || p[j]==p[k]){
			k++;
			j++;
			if(p[j]!=p[k]){ 
				nextTable[j]=k;  //next[j + 1 ] = next [j] + 1 = k + 1
			}
			else{  
			//p[j+1]=p[k+1]
			//如果p[j]=p[next[j]], 应该继续递归, k=next[k]=next[next[k]] 
				nextTable[j]=next[k];  
			}
		} 
		else{  
		//k回溯,直到找到长度为t的前缀“p0 p1, …, pt-1 pt” 等于 长度为t的后缀“pj-t pj-t+1, …, pj-1 pj”
		//t<=k  
			k=nextTable[k];
		}
	}
} 

3、完善KMP算法

//char s[100] 文本串 长为n
//char p[100] 模式串 长为m
int KMP(int n,int m){
	GetNextTable(m);
	int i=0;
	int j=0;
	while(i<n && j<m){
		//如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++ 
		if(j==-1 || s[i] == p[j]) {  
			i++;
			j++;
		}
		else{
			//如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]    
			//next[j]即为j所对应的next值,根据上边的分析,即为p[j]不能匹配时,j应该移动到的位置
			j=next[j];
		}
	} 
	if(j==m){
		return i-j;
	}
	else{
		return -1;
	}
}

最后是一个KMP模板题,趁热打铁,练一练

Number Sequence

HDU - 1711

#include<iostream>

using namespace std;

int s[1000005]; 
int p[10005];
int nextTable[10005]; 

void GetNextTable(int m){   //m是模式串的长度 
	int j=0;
	nextTable[0]=-1;   //初始值赋值-1 
	int k=-1;
	while(j<m){
		if(k==-1 || p[j]==p[k]){
			k++;
			j++;
			if(p[j]!=p[k]){ 
				nextTable[j]=k;  //next[j + 1 ] = next [j] + 1 = k + 1
			}
			else{  
			//p[j+1]=p[k+1]
			//如果p[j]=p[next[j]], 应该继续递归, k=next[k]=next[next[k]] 
				nextTable[j]=nextTable[k];  
			}
		} 
		else{  
		//k回溯,直到找到长度为t的前缀“p0 p1, …, pt-1 pt” 等于 长度为t的后缀“pj-t pj-t+1, …, pj-1 pj”
		//t<=k  
			k=nextTable[k];
		}
	}
} 


int KMP(int n,int m){
	GetNextTable(m);
	int i=0;
	int j=0;
	while(i<n && j<m){
		//如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++ 
		if(j==-1 || s[i] == p[j]) {  
			i++;
			j++;
		}
		else{
			//如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]    
			//next[j]即为j所对应的next值  
			j=nextTable[j];
		}
	} 
	if(j==m){
		return i-j+1;   //注意这块有个+1,看题目的例子就相当于下标从1开始,而我们一直都是从0 
	}
	else{
		return -1;
	}
}

int main(){
	int number;
	int n,m;
	scanf("%d",&number);
	while(number--){
		scanf("%d%d",&n,&m);
		for(int i=0;i<n;i++){
			scanf("%d",&s[i]); 
		} 
		for(int j=0;j<m;j++){
			scanf("%d",&p[j]);
		}
		printf("%d\n",KMP(n,m));
	}	
	return 0;
}

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值