您的位置:首页 > 其它

串--->串的模式匹配:Brute-Force算法和 KMP算法

2017-09-26 16:46 344 查看


写在前面

       

其实关于串的模式匹配问题,我最初的了解并不深,只知道一个最简单的 Brute-Force模式匹配算法,所以感觉在串这一章应该很简单的就复习完了,现在不由的感慨,

井底之蛙不知天大。。。。。

 Brute-Force模式匹配算法,当然是轻车熟路,半个小时重新学习后,能够独立的写出完整的代码,简单随意。

但是KMP模式匹配算法,愣是让我好好的琢磨了整整两天,才算是明白了七七八八,之前一直感觉眼前有层纱,看不清真相。。。。


文字描述

在描述之前,我们先做个约定,因为串的匹配算法是找目标串在已存在的串中出现的位置(下标),我们把要找的下标所在的串称之为主串S,目标串称之为子串t。

那此时的串的匹配就可称为:寻找子串在主串中出现的位置。

有两个变量(整型变量或者指针变量)记录当前比对的主串(i)和子串(j)的字符对应的下标。

匹配的过程,其实类似于游标卡尺的读数的过程(当然不完全一致,只是再次举例做类比),每次移动子串,和主串进行比较,直到找到某一满足条件的位置,或者找不到,也就是匹配失败。




1、Brute-Force模式匹配算法

关于这个算法我不想做过多的描述,因为比较简单,比如从abababc(主串)中寻找abc(子串)的步骤如图



从主串的某一个字符开始,与子串的第一个字符进行比较,

如果相等,就比较二者对应的下一个字符,也就是Si=tj,i++;j++;

如果不相等,那么就让主串的下一个字符与子串的第一个字符进行比较

也就是一旦出现Si≠tj,匹配失败,那么此时要向右移动子串,并修改记录主串和子串的变量i=i-j+1,j=0;

直到匹配成功,返回子串在主串中出现的位置(i - j),或者主串中剩余未比较的字符串的长度(S.length)小于子串的长度(t.length)(肯定是不可能有匹配的情况了)返回-1。

优点是:简单,容易理解,容易实现
缺点是:需要匹配的次数太多,每次 i 和 j 的移动距离过大,存在大量不必要的比较


2、KMP模式匹配算法


     (1)总体介绍

在上面的Brute-Force算法的基础上进行了改进,改进后的算法,可以在匹配失败后,无需修改记录主串当前比较的字符的下标  i;而且,记录子串当前比较的字符的下标 j也不用清零。而是在已经比较过的字符的基础上,针对子串本身的特点,移动子串,以达到减少比较次数的目的


(2)详细描述

当某次匹配不成功时,主串s的当前比较位置i不用回退,此时主串Si 可以直接和子串中的某个tk进行比较,此处的k的确定与主串无关,只有子串本身的构有关,即从子串本身就可以计算出k的值

现在讨论一般情况。

假设主串S=“s0s1s2s3...s(n-1)”, 子串t=“t0t1t2...t(m-1)”,从主串S中某一个位置开始比较,当匹配不成功(si≠tj)时,此时一定存在

“s0s1s2s3...s(i-1)”=“t0t1t2...t(j-1)” ----->(等式1)

①如果子串中不存在满足下列式子的  k值

“t0t1t2...t(k-1)”=“t(j-k)t(j-k+1)...t(j-1)” (其中0<k<j) ---->(等式2)

说明在子串“t0t1t2...t(j-1)”中不存在前缀t0t1t2...t(k-1)与主串s(i-j)s(i-j+1)...s(i-1)中的s(i-j)s(i-j+1)...s(i-1)子串相匹配,

下一次可以直接比较Si 和t0

②若子串中存在满足等式2,则说明在子串“t0t1t2...t(j-1)”中存在前缀t0t1t2...t(k-1)已经与主串s(i-j)s(i-j+1)...s(i-1)中的s(i-j)s(i-j+1)...s(i-1)子串相匹配

下一次可以直接比较Si和tk

比如从abcabdabcabc(主串)中寻找abcabc(子串)的步骤如图



因此,当主串中的 Si 和子串中的 tj不匹配时,只需要将 Si 和 tk 比较即可,

也就是说此时问题的难点就在于如何求子串的每一个字符所对应的 k值

而此时选取k的原则是:子串中的前k个字符前缀子串等于 tj 之前的前k个字符子串,并且是具有此性质的最大子串的串长。

因为每一个字符 tj 都对应一个k值,而且这个k值仅与子串有关,与主串无关,因此我们可以在匹配 之前求出每一个字符所对应的 k值,记作next[ j ]


(3)next[ j ]的求法

在讲具体的求法之前,我想再重新强调一下next[ j ]函数的意义:

next[ j ]是:当子串 t j和主串s i  比较失败后,下一次和主串比较的子串的下标

next的求法:



很显然next[ j ]函数中下一个值与当前值之间有关系,用递推法容易求解

下面讨论求next[ j ]函数的问题

由定义可知   初始时  next[ 0 ]= - 1;     next[ 1 ]=0

若存在next[ j ]=k,  则表明在子串中存在

“t0t1t2...t(k-1)”=“t(j-k)t(j-k+1)...t(j-1)” (其中0<k<j)

 

其中,k为满足等式的最大值,那么对于next[ j+1 ]的值存在以下两种情况

(1)若 tk=tj,则表明在子串中存在

“t0t1t2...t(k-1)t(k)”=“t(j-k)t(j-k+1)...t(j-1)tj” (其中0<k<j)

则next[ j+1 ]=next[ j ]+1=k+1

(2)若 tk≠tj,说明此时子串中:

“t0t1t2...t(k-1)t(k)”≠“t(j-k)t(j-k+1)...t(j-1)tj”

那么此时可以将求next[ j ]函数的问题看做是一个模式匹配问题,子串既是主串也是子串,

当前已经匹配的有“t0t1t2...t(k-1)”=“t(j-k)t(j-k+1)...t(j-1)” (其中0<k<j),

则当t k≠tj 时,应该将子串t向右滑动到   k'=next[ k ],

(PS:为什么向右滑动到next[ k ]?? 

首先k不仅代表的是next[  j ]的值(PPS:已经求出的,子串 t j和主串s i  比较失败后,下一次和主串比较的子串的下标)

而且还代表了本次求next[ j ]匹配过程中,当前比较的"子串"的下标。

那么next[ k ]代表的含义是:当前比较失败后的下一次应该比较的子串的下标,正好符合)

 并将k' 位置上的字符与“主串”中第j个位置上的字符进行比较

如图,求ababaaa的next[ j ]的过程




代码实现


1、Brute-Force模式匹配算法

 

 

/**
* @Title: indexOf_BF
* @Description: TODO()
* @param t
* @param begin
* @return
*/
public int indexOf_BF(SeqString t, int begin) {
if (this != null && t != null && t.length() >= 0
&& this.length() > t.length()) {
int slen = this.length();
int tlen = t.length();
int i = begin;
int j = 0;
while (begin <= slen - tlen && j < tlen) {
if (this.charAt(i) == t.charAt(j)) {
i++;
j++;
} else {
begin++;
i = begin;
j = 0;
}
}
if (j >= tlen) {
return begin;
} else {
return -1;
}
} else {
return -1;
}
}


 


2、KMP模式匹配算法


2.1  next[ j ]函数实现

/**
* @Description: TODO(Next(j)函数,表示下标为j的子串的k值 ) 也就是子串下标j之前 满足
*               t0t1t2..t(k-1)=t(j-k+1)t(j-k+1)...t(j-1);的k的最大值
* @param T
* @return
*/
private int[] getNext(MString T) {
int[] next = new int[T.length()];
int j = 1;
int k = 0;
next[0] = -1;
next[1] = 0;
while (j < T.length() - 1) {
if (T.charAt(j) == T.charAt(k)) {
next[j + 1] = k + 1;
j++;
k++;
} else if (k == 0) {
next[j + 1] = 0;
j++;
} else {
k = next[k];
}
}
return next;
}



2.2  KMP算法的实现

/**
* @Description: TODO(KMP算法 ) 指向主串的下标不往回移动, 而是根据子串本身的性质来移动子串,大大的减少了匹配的次数
* @param T
* @param begin
* @return
*/
public int indexOf_KMP(MString T, int begin) {
int[] next = getNext(T);
int i = begin;
int j = 0;
while (i < this.length() && j < T.length()) {
if (j == -1 || this.charAt(i) == T.charAt(j)) {
i++;
j++;
} else {
j = next[j];
}
}
if (j < T.length()) {
return -1;
} else {
return (i - T.length());
}
}



后记

串这章的知识点终于算是总结完了,学完了串,不得不说,最初我的想法是幼稚的,一些看起来简单的东西一定有他不简单的地方,下面开始复习数组,希望继续像串这一章这样有那么大的收获

转至http://lib.csdn.net/article/aiplanning/67349
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  算法 kmp