字符串匹配算法KMP, 核心思想是尽可能利用已经匹配的结果, 跳过尽可能多的不需要匹配的情况

重点和难点都在next数组的建立上

1. KMP算法的next数组求解

以模式串 a b a c a b 为例

||

w

a b a c a b

0

// 初始, 两个指针都在下标0的位置, 从此开始,  current指针不断后移,

// 并和头指针比较, 不同填0,相同则为前一个值加1

 | |

 v v

 a b a c a b

 0 0

 |   |

 v   v

 a b a c a b

 0 0 1

 |     |

 v     v

 a b a c a b

 0 0 1 0

 |       |

 v       v

 a b a c a b

 0 0 1 0 1

 |         |

 v         v

 a b a c a b

 0 0 1 0 1 2

  其中, 当在模式字符串i位置匹配失败的时候, i 跳转到next[i-1]的值

如上所示, 则当第二个字符a匹配失败的时候, 返回第一个字符b位置对应的next值, 为0.看上去没问题

但是, 当最后一个字符b匹配失败的时候, 根据该算法应返回1.意味着, 我们希望可以像下面这样继续:

abaca?

abacab 进行继续匹配。事实上这是有问题的。根据之前的匹配结果, 已知? <> 'b' ,这次匹配注定是失败的,是没有意义的, 应返回0

2. 自己尝试改写的求next方法

我们希望得到这样的结果:

abacab

000100

并在模式串i位置匹配失败时, 返回next[i]的值

根据kmp指导思想, 希望可以利用已经匹配的结果, 也就是重叠已经匹配的结果和模式串中的前缀部分.

这需要模式串中有跟模式串前缀相同的字符串存在, 但是为了避免匹配失败时, 再度尝试与已知匹配结果的字符进行匹配,先分析如下:

例: abacab

首先满足第一个条件, 就是模式串中存在模式串前缀的内容, 首要保证的是, 模式串的第一个字符在模式串中存在多个

例子中有两处

    1   2
| |
a b a c a b

接下来, 两处中能匹配模式串前缀的最长字符串分别为:

1 : a    2 : ab

第二个条件, 减少已知结果的匹配

1. 当1处的a匹配成功时,但c匹配失败时, 因为模式串前缀ab与ac有重叠但不完全相同

可以重叠匹配像这样:

aba?

abacab  ?虽然与'c'匹配失败, 但不一定与'b'匹配失败, 这样的匹配是有必要的

2. 当2处的a匹配成功, 但b匹配失败时, 因为模式串前缀ab与ab完全相同,所以不可以进行重叠匹配,原因:

abaca?                                                                                       abaca?

abacab 此时已知?与'b'不匹配, 这样的匹配是没有必要的  应该这样:              abacab 即直接跳回最开始的位置重新匹配

注意到两者的差别, 1.处时, 虽然在非模式串前缀匹配失败, 可进行重叠匹配, 2处时在模式串前缀匹配失败, 不可重叠匹配

(模式串并不一定指1之前的,如abcacabcab 这里模式串前缀也可以是abcac, 而不一定是abc)

于是有下面的推导:

最开始next数组全部用0初始化
两个指针在0下表
state 表示是否在前缀状态 state = 0
| |
v v
a b a c a b
0 0 0 0 0 0 state = 1// 此时开启前缀模式
| |
v v
a b a c a b
0 0 0 0 0 0 state = 0//state为1时, 头指针跟着向前走, 当state从1变为0时, next记录此时头指针的位置,头指针回到初始状态
| |
v v
a b a c a b
0 0 0 1 0 0 state = 1
| |
v v
a b a c a b
0 0 0 1 0 0 state = 1
| |
v v
a b a c a b
0 0 0 1 0 0

代码实现:

int* getNext(const char *target) {
int *next;
int head, current, len, state;
len = strlen(target);
// 生产next数组
next = (int *)malloc(sizeof(int) * len);
// 出事化工作
head = ;
current = ;
next[] = ;
memset(next, , len * sizeof(int));
state = ; // 非前缀状态
// 遍历target产生next数组
// 原理, 尽可能利用已经匹配的结果进行错位
while(current < len) {
if(target[head] == target[current]) {
// 如果相等, 进入前缀状态, head 指针跟着current指针后移
state = ;
head++;
current++;
}else {
if(state == ) {
// state 从0变为1, 设置next值
next[current] = head;
// 回到初始状态
head = ;
state = ;
}
else {
current++;
}
}
}
return next;
}

算法实现:

/*
这部分代码的实现可能有点乱, 不太优美, 回头再改改~~
*/
int KMP(const char *source, const char *target) {
int *next;
// 获得next数组
next = getNext(target);
int source_index = ;
int source_len = strlen(source);
int target_index = ;
int target_len = strlen(target);
while(source_index < source_len && target_index < target_len) {
if(source[source_index] == target[target_index]) {
source_index++;
target_index++;
}else {
// 跳过
if(target_index == )
source_index++;
else {
printf("Skip %d to %d for %c where %d\n", target_index, next[target_index - ], source[source_index], source_index);
target_index = next[target_index];
}
}
}
if(source_index == source_len)
return -;
return source_index - target_len;
}

算法实现时的问题:

1. 为处理字符串为空字符串的情况

2. 为考虑传入NULL值的情况

在KMP函数最前面加入一下代码后, 可以过lintcode的strStr题目

if(source == NULL || target == NULL)

    return -;

if(source == "" && target == "")

    return ;

KMP(构建next数组)的更多相关文章

  1. 求最长公共前缀和后缀—基于KMP的next数组

    KMP算法最主要的就是计算next[]算法,但是我们知道next[]求的是当前字符串之前的子字符串的最大前后缀数,但是有的时候我们需要比较字符串中前后缀最大数,比如 LeetCode的shortest ...

  2. 剑指offer(51)构建乘积数组

    题目描述 给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1].不 ...

  3. 剑指Offer 51. 构建乘积数组 (数组)

    题目描述 给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1].不 ...

  4. 【Java】 剑指offer(66) 构建乘积数组

      本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集   题目 给定一个数组A[0, 1, …, n-1],请构建一个数组B[ ...

  5. 《剑指offer》第六十六题(构建乘积数组)

    // 面试题66:构建乘积数组 // 题目:给定一个数组A[0, 1, …, n-1],请构建一个数组B[0, 1, …, n-1],其 // 中B中的元素B[i] =A[0]×A[1]×… ×A[i ...

  6. 剑指offer五十一之构建乘积数组

    一.题目 给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1].不 ...

  7. (剑指Offer)面试题52:构建乘积数组

    题目: 给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1].不能 ...

  8. 剑指Offer——构建乘积数组

    题目描述: 给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]. ...

  9. 【剑指offer】不使用除法,构建乘积数组,C++实现

    # 题目 # 思路 设C[i] = A[0] * A[1] * - * A[i-1],D[i] =  A[i+1] * - * A[n-1],则C[i]按照从上到下的顺序计算,即C[i] = C[i- ...

  10. [剑指Offer] 51.构建乘积数组

    题目描述 给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1].不 ...

随机推荐

  1. C#中各种计时器 Stopwatch、TimeSpan

    1.使用 Stopwatch 类 (System.Diagnostics.Stopwatch)Stopwatch 实例可以测量一个时间间隔的运行时间,也可以测量多个时间间隔的总运行时间.在典型的 St ...

  2. 芝麻HTTP:Python爬虫入门之正则表达式

    1.了解正则表达式 正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符.及这些特定字符的组合,组成一个"规则字符串",这个"规则字符串"用来 ...

  3. 芝麻HTTP:python version 2. required,which was not found in the registry 解决方案

    不能在注册表中识别python2.7 新建一个register.py 文件 import sys from _winreg import * # tweak as necessary version ...

  4. SpringBoot 文件上传临时文件路径问题

    年后放假回来,一向运行OK的项目突然图片上传不了了,后台报错日志如下: java.io.IOException: The temporary upload location [/tmp/tomcat. ...

  5. 【BZOJ3675】序列分割(斜率优化,动态规划)

    [BZOJ3675]序列分割(斜率优化,动态规划) 题面 Description 小H最近迷上了一个分隔序列的游戏.在这个游戏里,小H需要将一个长度为n的非负整数序列分割成k+1个非空的子序列.为了得 ...

  6. 【CJOJ1372】【洛谷2730】【USACO 3.2.5】魔板

    题面 Description 在成功地发明了魔方之后,鲁比克先生发明了它的二维版本,称作魔板.这是一张有8个大小相同的格子的魔板: 1 2 3 4 8 7 6 5 我们知道魔板的每一个方格都有一种颜色 ...

  7. [HNOI2013]切糕

    题目描述 网址:https://daniu.luogu.org/problemnew/show/3227 大意: 平面上有一长方体,目标为将其切割为上下两半. 切割点为\((x,y,z)\)的点,每个 ...

  8. [BZOJ3223] [Tyvj1729] 文艺平衡树 (splay)

    Description 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3  ...

  9. .NET Core使用skiasharp文字头像生成方案(基于docker发布)

    一.问题背景 目前.NET Core下面针对于图像处理的库微软并没有集成,在.NET FrameWork下我们已经习惯使用System.Drawing类库做简单的图像处理,到了.NET Core下一脸 ...

  10. Linux系统中svn服务器设置开机启动

    安装完svn服务器后虽然好用但是因为经常重启Linux服务器,每次重启完就要去手动启动svn服务器,很是麻烦,于是在网上找了一些方法后,自己把svn服务器设置成开机启动 步骤一:安装svn服务器: h ...