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]

KMP算法_第1张图片

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

KMP算法_第2张图片

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

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

暴力求解算法

#include
#include
#include
using namespace std;

int ViolentMatch(char* s,char* p){
	int slen=strlen(s);
	int plen=strlen(p);
	int i=0,j=0;
	while(i

 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

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

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

Number Sequence

HDU - 1711

#include

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

 

 

 

 

 

 

 

你可能感兴趣的:(考研机试题,算法设计编程实验)