您的位置:首页 > 编程语言 > Java开发

串模式匹配算法--KMP图解

2013-05-02 16:55 302 查看

       前言

                  串:是有0个或多个字符组成的有限的序列。对于串的操作,经典的也就是串的模式匹配问题了。

             也就是子串的定位操作。

       算法实现一

                 先来看看传统的算法实现。  这种算法是一种暴力匹配的方式。假设要搜索的串为S长度为N

           要匹配的串为T长度为M则,则这种算法的时间复杂度为O(NM)。因为它需要遍历S的每一个字符。

                 假设S为Kiritor,T为it,则其匹配的过程如下:

          


              这种实现比较简单,直接看源代码吧:注意匹配位置是从0开始的。

public static int index_pattern(String source, String pattern, int pos) {
char[] source_str = source.toCharArray();
char[] pattern_str = pattern.toCharArray();
int i = pos;
int j = 0;
while (i < source_str.length && j < pattern_str.length) {
if (source_str[i] == pattern_str[j]) {
i++;//继续比较后续的字符
j++;
} else {
i = i - j + 1;//比较的位置后移一个继续在进行比较
j = 0;
}
}
if (j >= pattern_str.length)
return i - pattern_str.length;
return -1;
}

         KMP算法

              不过我们需要了解的是串的匹配有存在一些较为极端的情况,例如S=abababaababacb

                 T="ababacb"的时候会出现如下情况。

                 


                 如果按照上述算法思维来说的话,我们会进行大量的移动,我们用i表示S的位置,j表示T的位

           位置此时我们将j=3,i不变之后再进行比较,就减少了比较的次数。之后再i=7的时候又不匹配了

           之后的情况简化为下图。

         


                    不过每次j的位置是如何变化的,才会导致T向前“滑动”的距离最大,从而减少最多的比较

             次数的呢?

                  通过如下图我们来探究T串"滑动"最大距离。

             


                

                     考虑上面的图,T中灰色部分已经和S的灰色部分匹配上了,而灰色部分后一个字符不匹配,

            则现在 T  要向后滑动,假设一直向后滑动,直到如图位置又和S再一次匹配上了,

                    那么从这里我们可以得到如下的结论

                          1、 A段字符串是M的一个前缀。

                          2、B段字符串是M的一个后缀。

                          3、A段字符串和B段字符串相等。

                     这样,如果暂时不考虑S,只看T的话,假设已经匹配的T的字串(即图中M中灰色部分)为

                  subT,则subT有个【相等】的【前缀】和【后缀】。而且T在遇到不匹配的时候可以直接

                  滑动到使subT的前缀和subT的后缀重合的地方。而T向后滑动的时候,第一次subT的前缀

                  和后缀重合意味着此时这个相等的subT的前缀和后缀的长度是最大的。

                     我们的任务就是要寻找subT的最长的前缀和后缀相等的串。

                     知道了这一点,离KMP的真谛也就不远了。

                    现在结合这上面的图模拟一下KMP算法的整个流程:

                          1、将S串和T串从第一个字符开始匹配;

                          2、如果匹配成功,则subT即灰色部分增加;

                          3、如果不成功,则T向后滑动使滑动后的subT的前缀和滑动前的subT的后缀重合,

                                再进行匹配,如果还不成功,则再次滑动T,直到匹配成功或者T滑动的长度超出自己

                                的长度。超出自己长度则从T串的起始位置进行匹配。

                  从上面的步骤可以知道,KMP的关键就是要知道当S串中的字符和T串中的字符不匹配时,S串

             要和T串中的哪个字符继续进行匹配。这个就是在利用状态机模型来解释KMP算法时的状态转移.

                KMP是通过一个定义了一个next数组,这个next数组保存了如果S中的字符和T中的字符不匹配时

             S要和T中的哪个字符重新进行匹配的坐标值。next[i]总是保存了当T[i]不匹配时要从T[next[i]]处进行

            匹配,这个T[next[i]] 可能会匹配,如果还不匹配?那么可能会在T[next[next[i]]]处匹配了。这里同时

            隐含着一个信息,就是i之前的一段字符和next[i]之前的一段字符是相同的,也就是T[0…i-1]相等的

           前缀和后缀。现在考虑next[0],next[1]…next[i]都已经知道了,那么图示如下:

                 


 

              设j=next[i],灰色部分表明这两段字符是相等的,如果i位置的字符和j位置的字符相等,那么

        next[i+1]=j+1;因为前一段灰色部分和j位置的字符组成的字符串和后一段灰色的与i连接所形成的

         字符串是相等的。这正是前面对next数组的定义。如果不相等,则要找到从i开始包括i往前的一

        段字符串与从0开始的一段字符串相等,这样形成相等的前缀和后缀。所幸我们知道next[next[i]

        ]的值,因为next[i]前面的字串也有最长的公共前缀和后缀,而这个公共的前缀与现在i以及往前

       形成的字串可能相等,这样一直向前找,如果找不到,则说明i位置的字符从来没有在之前出现过。

              这样求出来的next数组其实是从下标1开始的,因为下标0之前是个空串,下标1则对应着T串

        的第0个字符。我们设next[0]=-1,仅仅是个标志而已,没有什么特殊的含义。

               下面看看具体的实现吧:

/*初始化next数组*/
public static int[] BuildKMP(String[] pattern)
{
int[] next = new int[pattern.length];

next[0] = 0;        // 第一个位置一定为 0

int j = 0;          // 匹配的起始位置
for (int i = 1; i < pattern.length; i++)
{
// 如果已经匹配上,但是现在不能匹配,回溯寻找
while( j>0 && pattern[j].equals(pattern[i]) )
{
j = next[j-1];
}

// 如果能够匹配上,向下推进一个位置
// 注意 i 在 for 循环中自动推进
if (pattern[j].equals(pattern[i]))
j++;

// 保存
next[i] = j;
}
return next;
}
                匹配算法
public static int KMPSearch(String[] source,String[] pattern, int pos)
{
int length = pattern.length;
int[] next = BuildKMP(pattern);
int j = 0;
for (int i = pos; i < source.length; i++)
{
while (j > 0 && !source[i].equals(pattern[j]))
j = next[j-1];      // 调整下一个匹配的位置
if (source[i].equals(pattern[j]))
j++;
if (j == length)
return i-length +1;
}
return -1;
}
                测试代码及结果

public static void main(String[] args) {
String [] source = {"K","I","R","I","T"};
String [] parten ={"R","I"};
System.out.println(KMPSearch(source,parten , 0));

}
            结果为2
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息