本文根据《大话数据结构》一书,实现了Java版的串的朴素模式匹配算法、KMP模式匹配算法、KMP模式匹配算法的改进算法

1.朴素的模式匹配算法

  为主串和子串分别定义指针i,j。

    (1)当 i 和 j 位置上的字母相同时,两个指针都指向下一个位置继续比较;

    (2)当 i 和 j 位置上的字母不同时,i 退回上次匹配首位的下一位,j 则返回子串的首位。

(注:该图从下标为1开始 )

  实现程序:

/**
* 朴素的模式匹配算法
* 说明:下标从0开始,与书稍有不同,但原理一样
* @author Yongh
*
*/
public class BruteForce { /*
* 返回子串t在主串s中第pos个字符后的位置。若不存在返回-1
*/
int index(String s,String t,int pos) {
int i=pos; //i为主串位置下标
int j=0; //j为子串位置下标
while(i<s.length()&&j<t.length()) {
if(s.charAt(i)==t.charAt(j)) {
i++;
j++; //i和j指向下一个位置继续比较
}else { /*重新匹配*/
i=i-j+1; //退回上次匹配首位的下一位
j=0; //返回子串的首位
}
}
if(j==t.length()) {
return i-j;
}else {
return -1;
}
} public static void main(String[] args) {
BruteForce sample =new BruteForce();
int a= sample.index("goodgoogle", "google", 0);
System.out.println(a);
}
}

  


BruteForce

2.KMP模式匹配算法

2.1 KMP模式匹配算法的主体思路

  

  在上图的比较中,当 i 和 j 等于5时,两字符不匹配。在朴素匹配算法中,会令i=1,j=0,然后进行下一步比较;但是,我们其实已经知道了i=1到4的主串情况了,没有必要重复进行i=2到4的比较,且我们观察ABCABB”B前面的ABCAB,其前缀与后缀(黄色部分)相同,所以可以直接进行上图中的第三步比较(令 i 不变,令 j 从5变成2,继续进行比较)。这就是KMP模式匹配算法的大概思路。这当中的 j 从5跳转到了2,2通过一个函数next(5)求得,next(5)即代表j=5位置不匹配时要跳转的下一个进行比较的位置。

  KMP模式匹配算法:

  为主串和子串分别定义指针 i 和 j 。

  (1)当 i 和 j 位置上的字母相同时,两个指针都指向下一个位置继续比较;

  (2)当 i 和 j 位置上的字母不同时,i 不变,j 则返回到next[j]位置重新比较。(暂时先不管next[]的求法,只要记得定义有next[0]=-1)

  (3)当 j 返回到下标为0时,若当 i 和 j 位置上的字母仍然不同,根据(2),有 j = next[0]=-1,这时只能令 i 和 j 都继续往后移一位进行比较 (同步骤(1))。

  上述内容可结合下图说明:

  (1)i 和 j 从下标为0开始比较,该位置两字母相同,i 和 j 往后移继续比较;

  (2)一直比较到 i 和 j 等于5时,两字母不同, i 不变,j 返回到 next[j]的位置重新比较,该子串的next[5]=2,所以 j 返回到下标为2的位置继续与 i=5的主串字母比较。

  (3)在下图情况下,当j=0时,两字母不同,子串只能与主串的下一个元素比较了(即i=1与j=0比较)。根据(2),会使 j=next[j]=next[0]=-1,所以现在的i=0,j=next[0]=-1了,要下一步比较的话两个指针都要加一。

  

   根据上述说明可以写出如下代码(代码中的next[]暂时假设已知,之后会讲):

	/*
* 返回子串t在主串s中第pos个字符后的位置(包含pos位置)。若不存在返回-1
*/
public int index_KMP(String s, String t, int pos) {
int i = pos; //主串的指针
int j = 0; //子串的指针
int[] next = getNext(t); //获取子串的next数组
while (i < s.length() && j < t.length()) {
if (j == -1 || s.charAt(i) == t.charAt(j)) {
// j==-1说明了子串首位也不匹配,它是由上一步j=next[0]=-1得到的。
i++;
j++;
} else {
j = next[j];
}
}
if (j == t.length())
return i - j;
return -1;
}

  

2.2 next[]的定义与求解

  根据上述内容可知,next[j] 的含义为:当下标为 j 的元素在不匹配时,j 要跳转的下一个位置下标。

  继续结合下图说明:

  当j=5时,元素不匹配,j跳转到next[5]=2的位置重新比较。

  那为什么next[5]的值为2呢?即,为什么j=5不匹配时要跳转到2位置呢?

  观察 ABCABB 这个字符串,下标为5的字符为B,它前面的字符 ABCAB 与主串完全相同,而ABCAB的前缀与后缀(黄色部分)相同,,所以前缀AB不用再进行比较了,直接比较C这个字符,即下标为2的字符,所以next[5]=2。

  那么该如何求解跳转位置next[]呢?通过刚才的讨论,我们可以发现next[j]的值等于 j 位置前面字符串的相同前后缀的最大长度,上面例子就是等于AB的长度2。

  next[]的公式如下:

  

  公式说明:

    1.在j=0时,0位置之前没有字符串,next[0]定义为-1 ;

    2. 在 j 位置之前的字符串中,如果有出现前后缀相等的情况,令 j 变为相等部分的最大长度,即刚刚所说的相同前后缀的最大长度。如上述的ABCABB字符串中,j=5时,前面相等部分AB长度为2,所以next[5]=2;

    3.其余情况下,next[j]=0。其他情况,没有出现字符的前后缀相等,相同前后缀的最大长度自然就是0。

  那求解next[]的代码如何实现呢?以下是代码的分析过程:

    1.定义两个指针 i=0 和 j=-1,分别指向前缀和后缀( j 值始终要比 i 值小),用于确定相同前后缀的最大长度;(因为 i 是后缀,所以我们求的都是 i+1位置的next值next[i+1])

    2.根据定义有:next[0]=-1;

    3.当前缀中 j 位置的字符和后缀中 i 位置的字符相等时,说明 i+1 位置的next值为 j+1 (因为 j+1 为相同前后缀的最大长度,可结合下面两种情况思考)(即next[i+1]=j+1 )

 

    4.j==-1时,说明前缀没有与后缀相同的地方,最大长度为0,则 i+1 位置的next值只能为0,此时也可以表示为next[i+1]=j+1

    5.当 j 位置的字符和 i 位置的字符不相等时,说明前缀在第 j 个位置无法与后缀匹配,令 j 跳转到下一个匹配的位置,即 j= next[j]

  以下是实现求解next[]的程序:

	/*
* 返回字符串的next数组
*/
public int[] getNext(String str) {
int length = str.length();
int[] next = new int[length]; //别忘了初始化
int i = 0; //i为后缀的指针
int j = -1; //j为前缀的指针
next[0] = -1;
while (i < length - 1) { // 因为后面有next[i++],所以不是i<length
if (j == -1 || str.charAt(i) == str.charAt(j)) { // j == -1代表前后缀没有相等的部分,i+1位置的next值为0
next[++i] = ++j; //等于前缀的长度
} else {
j = next[j];
}
}
return next;
}

  

2.3 KMP完整代码

  结合next数组的求解和KMP算法,完整代码如下:

  

import java.util.Arrays;

/**
* KMP模式匹配算法
* 返回子串t在主串s中第pos个字符后的位置。若不存在返回-1 要注意i不变,只改变j
*
* @author Yongh
*
*/
public class KMP {
/*
* 返回字符串的next数组
*/
public int[] getNext(String str) {
int length = str.length();
int[] next = new int[length]; //别忘了初始化
int i = 0; //i为后缀的指针
int j = -1; //j为前缀的指针
next[0] = -1;
while (i < length - 1) { // 因为后面有next[i++],所以不是i<length
if (j == -1 || str.charAt(i) == str.charAt(j)) { // j == -1代表前后缀没有相等的部分,i+1位置的next值为0
next[++i] = ++j; //等于前缀的长度
} else {
j = next[j];
}
}
return next;
} /*
* 返回子串t在主串s中第pos个字符后的位置(包含pos位置)。若不存在返回-1
*/
public int index_KMP(String s, String t, int pos) {
int i = pos; //主串的指针
int j = 0; //子串的指针
int[] next = getNext(t); //获取子串的next数组
while (i < s.length() && j < t.length()) {
if (j == -1 || s.charAt(i) == t.charAt(j)) {
// j==-1说明了子串首位也不匹配,它是由j=next[0]=-1得到的。
i++;
j++;
} else {
j = next[j];
}
}
if (j == t.length())
return i - j;
return -1;
} public static void main(String[] args) {
KMP aKmp = new KMP();
System.out.println(Arrays.toString(aKmp.getNext("BBC")));
System.out.println(Arrays.toString(aKmp.getNext("ABDABC")));
System.out.println(Arrays.toString(aKmp.getNext("ababaaaba")));
System.out.println(aKmp.index_KMP("goodgoogle", "google", 0));
}
}

  

[-, , ]
[-, , , , , ]
[-, , , , , , , , ]

KMP

2.4 一道题目

已知字符串S为abaabaabacacaabaabcc,模式串P为abaabc。采用KMP算法进行匹配,第一次出现“失配”(S[i]≠P[j])时,i=j=,则下次开始匹配时,i和j的值分别是:C。
A. i = , j =
B. i = , j =
C.i = , j =
D. i = , j =

  分析:模式串就是之前所说的子串,i 和 j 是之前所说的指针。根据刚刚的分析中,出现失配时,指针 i 是不会变动的,只会变 j,j=next[j]。next[j]的物理意义是 j 位置前面字符串的相同前后缀的最大长度,我们可以发现abaabcc前面的字符串中相同前后缀为ab,长度为2,所以直接可以选出答案为C。

推荐阅读:
  从头到尾彻底理解KMP(2014年8月22日版)

  字符串匹配的KMP算法

  超详细理解:kmp算法next数组求解过程和回溯的含义

3.KMP模式匹配算法改进

  对于如下字符串,j=3时,next[j]=1,根据next的定义,即当 j=3位置不匹配时,j跳转到1位置重新比较,但可以发现,j=2位置和j=1位置其实是同一个字母,没有必要重复比较。

  举个例子,在KMP算法下的比较过程如下(按图依次进行):

    

  因为有next[3]=1,所以会出现中间这个其实可以省略掉的过程。实际上我们是可以直接跳到j=0那一步进行比较的,这就需要修改next数组,我们把新的数组记为nextval数组。

  中间那步可以省略是因为,j=3和 j=1位置上的字符是完全相同的,因此没有必要再进行比较了。因此只需要在原有的next程序中加上一个字符是否相等的判断,如果要跳转的nextval位置上的字符于当前字符相等,令当前字符的nextval值等于要跳转位置上的nextval值。

  KMP模式匹配算法的改进程序如下:

import java.util.Arrays;

/**
* KMP模式匹配算法 的改进算法
* 返回子串t在主串s中第pos个字符后的位置。若不存在返回-1 要注意i不变,只改变j
*
* @author Yongh
*
*/
public class KMP2 {
/*
* 返回字符串的next数组
*/
public int[] getNextval(String str) {
int length = str.length();
int[] nextval = new int[length];
int i = 0; //i为后缀的指针
int j = -1; //j为前缀的指针
nextval[0] = -1;
while (i < length - 1) {
if (j == -1 || str.charAt(i) == str.charAt(j)) {
i++;
j++;
if(str.charAt(i)!=str.charAt(j)) { //多了一个字符是否相等的判断
nextval[i] = j; //等于前缀的长度
}else {
nextval[i]=nextval[j];
}
} else {
j = nextval[j];
}
}
return nextval;
} /*
* 返回子串t在主串s中第pos个字符后的位置(包含pos位置)。若不存在返回-1
*/
public int index_KMP(String s, String t, int pos) {
int i = pos; //主串的指针
int j = 0; //子串的指针
int[] next = getNextval(t); //获取子串的next数组
while (i < s.length() && j < t.length()) {
if (j == -1 || s.charAt(i) == t.charAt(j)) {
// j==-1说明了子串首位也不匹配,它是由j=next[0]=-1得到的。
i++;
j++;
} else {
j = next[j];
}
}
if (j == t.length())
return i - j;
return -1;
} public static void main(String[] args) {
KMP2 aKmp = new KMP2();
System.out.println(Arrays.toString(aKmp.getNextval("BBC")));
System.out.println(Arrays.toString(aKmp.getNextval("ABDABC")));
System.out.println(Arrays.toString(aKmp.getNextval("ababaaaba")));
System.out.println(aKmp.index_KMP("goodgoogle", "google", 0));
}
}

  

[-, -, ]
[-, , , -, , ]
[-, , -, , -, , , , -]

KMP2

  改进的算法仅在第24到28行代码发生了改变。

  图中这句话可以结合下表仔细体会。(要记得nextval[j]的含义:j位置的字符未匹配时要跳转的下一个位置)

附:

    要记住上面的算法,一定要记住指针 i 和 j 代表的意义,j==-1的意义,以及next的意义。

    (getNext()中前缀位置和后缀位置,index_KMP()中主串位置和子串位置),(前缀或子串的首个字符就无法匹配),(要跳转的下一个位置)

         还有要注意的就是,i为后缀,我们求的是下一个位置的next值,即next[i+1]。

【Java】 大话数据结构(8) 串的模式匹配算法(朴素、KMP、改进算法)的更多相关文章

  1. 大话数据结构(8) 串的模式匹配算法(朴素、KMP、改进算法)

    --喜欢记得关注我哟[shoshana]-- 目录 1.朴素的模式匹配算法2.KMP模式匹配算法 2.1 KMP模式匹配算法的主体思路 2.2 next[]的定义与求解 2.3 KMP完整代码 2.4 ...

  2. 【算法】串的模式匹配算法(KMP)

    串的模式匹配算法     问题:         求子串位置的定位函数如何写? int index(SString S,SString T,int pos);         给定串S,子串T,问T在 ...

  3. 数据结构- 串的模式匹配算法:BF和 KMP算法

      数据结构- 串的模式匹配算法:BF和 KMP算法  Brute-Force算法的思想 1.BF(Brute-Force)算法 Brute-Force算法的基本思想是: 1) 从目标串s 的第一个字 ...

  4. 《数据结构》之串的模式匹配算法——KMP算法

    //串的模式匹配算法 //KMP算法,时间复杂度为O(n+m) #include <iostream> #include <string> #include <cstri ...

  5. 串的模式匹配算法 ------ KMP算法

    //KMP串的模式匹配算法 #include <stdio.h> #include <stdlib.h> #include <string.h> int* get_ ...

  6. 串、串的模式匹配算法(子串查找)BF算法、KMP算法

    串的定长顺序存储#define MAXSTRLEN 255,//超出这个长度则超出部分被舍去,称为截断 串的模式匹配: 串的定义:0个或多个字符组成的有限序列S = 'a1a2a3…….an ' n ...

  7. 串的模式匹配算法(求子串位置的定位函数Index(S,T,pos))

    串的模式匹配的一般方法如算法4.5(在bo4-1.cpp 中)所示:由主串S 的第pos 个字 符起,检验是否存在子串T.首先令i 等于 pos(i 为S 中当前待比较字符的位序),j 等于 1(j ...

  8. 串的模式匹配算法1 BF算法

    BF算法 字符串的模式匹配不一定要从主串的第一个位置开始,可以指定主串中查找的起始位置 pos. 2. 算法步骤: 1)分别利用计数器指针 i 和 j 指定主串和模式串即小字符串待比较的位置,初始化为 ...

  9. Java数据结构之字符串模式匹配算法---Brute-Force算法

    模式匹配 在字符串匹配问题中,我们期待察看源串 " S串 " 中是否含有目标串 " 串T " (也叫模式串).其中 串S被称为主串,串T被称为子串. 1.如果在 ...

随机推荐

  1. 【BZOJ1489】[HNOI2009]双递增序列(动态规划)

    [BZOJ1489][HNOI2009]双递增序列(动态规划) 题面 BZOJ 洛谷 题解 这\(dp\)奇奇怪怪的,设\(f[i][j]\)表示前\(i\)个数中,第一个数列选了\(j\)个数,第二 ...

  2. 【数论Day1】 最大公约数(gcd)题目

    20170529-3数论_gcd 题解: http://www.cnblogs.com/ljc20020730/p/6919116.html 日期 序号 题目名称 输入文件名 输出文件名 时限 内存 ...

  3. Python GIL全局解释器锁

    '''在python原始解释器Cpython中存在GIL(Global Interpreter Lock,全局解释器锁),因此在执行Python代码 时,会产生互斥锁来限制线程对共享资源的访问,指导接 ...

  4. Hello,Power BI

    Power BI 是什么 Power BI 是一套业务分析工具,用于分析数据和理解数据,快速便捷地监控数据变化,为商务决策提供依据. Power BI 有用户组的概念.分享权限等概念 Power BI ...

  5. 稳定排序nlogn之归并排序_一维,二维

    稳定排序nlogn之归并排序_一维,二维 稳定排序:排序时间稳定的排序 稳定排序包括:归并排序(nlogn),基数排序[设待排序列为n个记录,d个关键码,关键码的取值范围为radix,则进行链式基数排 ...

  6. scrapy 让指定的spider执行指定的pipeline

    处理scrapy中包括多个pipeline时如何让spider执行制定的pipeline管道1:创建一个装饰器from scrapy.exceptions import DropItemimport ...

  7. JavaScript中函数和类(以及this的使用<重点>,以及js和jquery讲解,原生js实现jquery)

    1.javascript中以函数来表示类: 一般函数是小写开头:function foo() 类开头是大写:function Foo() 实例化类: obj = new Foo() 其他属性就同类是一 ...

  8. mysql统计一个字段的多种状态

    假如我有下面的表:ID    Item           status            updatetime    author1    a        1        2014-01-0 ...

  9. 洛谷 P1603 斯诺登的密码

    我一开始还没看懂非正规数字的意义,以为那里写的单词不算,蒙了好久,而且这题非常考验仔细程度,一不小心就RE,WA. 嗯,好像讲了些废话,那我们看看思路,我的做法和前面的大佬们有些不同,因为这题只有六个 ...

  10. 数链剖分(Tree)

    题目链接:https://cn.vjudge.net/contest/279350#problem/D 题目大意:操作,单点查询,区间取反,询问区间最大值. AC代码: #include<ios ...