天天天都是大晴天

KMP算法(POJ2406)

由于POJ2406开始研究这个算法。

在这两个博客中得到资料:

http://www.matrix67.com/blog/archives/115  Matrix67的,不多说

http://www.cnblogs.com/dolphin0520/archive/2011/08/24/2151846.html 这个有图解比较详细

July的经典算法研究里也有讲解KMP算法的部分

 

  本文主要是对于前几个博文我不大明白的地方的一个梳理。

  首先明确KMP算法是为了计算出字符串B是否为字符串A的子串。基本信息都可以从上面两个博文中了解到很详细,这里不赘述。

  讨论KMP算法之前,先提一下BF算法,对于第一次接触这个问题的人来说,第一反应可能就采用枚举的方法,也就是挨个比较目标串和模式串,这就是朴素的BF算法。当模式串和目标串有一个字符不匹配的时候,BF算法采取的是返回模式串的头,然后从目标串的下一个字符重新开始比较。如图

 

  KMP的优化之处就在于它要减少无必要的回溯,也就是在不匹配发生时,因为我们已经知道目标串和模式串前面部分是匹配的,如果知道模式串自身是有部分重复的,比如对于模式串ababa, 在最后一个a不匹配时,说明目标串是有abab部分的(至少前部分是匹配的),在回溯的时候模式串直接向右平移两位,平移到和原模式子串(”ab")相等的模式子串,减少不可能匹配的回溯。

(虽然这一步还是失配,但是至少减少了两次匹配)

 

    为了达到这个目标,首先要对于模式串做一个预处理。

  这个预处理是针对子串的(这让KMP有一个好处就是它经过一次预处理就可以和多个目标串匹配,不需要多次预处理),这个处理函数又很多种叫法,什么覆盖函数,getnext(),但是最终目标都是求得一个只与模式串相关的数组:

  覆盖的数学定义(转自July)

 

  这个k值数组P[i]是KMP算法思想的精髓所在,你可以采用不同的方法去实现它,初始计数值是0还是1也无所谓。但是你必须要理解它,才算是真正理解了KMP算法。

  关于这个数组有两个点,一个是如何计算,二是如何应用。

  计算:这个数组的值j'是指"子“模式串中头j'个字母和末j'个字母完全相等,也可以理解成模式串的“覆盖度”。P[j]应该是所有满足B[1..P[j]]=B[j-P[j]+1..j]的最大值。比如说对于模式串abcedabc

i

1

2

3

4

5

6

7

B[i]

a

b

a

b

a

c

b

P[i]

0

0

1

2

3

0

0

 

 

举个例子,求p[5],由于B[1…2]=B[3…5]="aba",所以P[5]=3(根据初始定义的不同也可以等于2,都减一就行了)。

  应用:在应用中你就会明白这样计算的目的是什么

      i = 1 2 3 4 5 6 7 8 9 ……

    A = a b a b a b a a b a b …

    B =      a b a b a c b

    j =       1 2 3 4 5 6 7(求失配字符前一个字符的P[j]值)

 

    由于P[5]=3,因此新的j=3:(也就是说,在应用中是将A[i]重新对应到失配字符B[i+1]前一个字符B[i]的P[i]值,从A[6]匹配B[5]变为A[6]匹配B[3], 3就是P[5]的值)

 

    i = 1 2 3 4 5 6 7 8 9 ……

    A = a b a b a b a a b a b …

    B =            a b a b a c b

    j =             1 2 3 4 5 6 7

现在明白了吧,因为在比较A[8]B[6]之前目标串和模式串已经进行了5次比较,并且都匹配,我们不能浪费这个信息啊,所以在预处理后我们知道在模式串中B[3…5]="aba"=B[1…2],就可以将模式串B直接向右平移2位(P[5]-1,也就是不匹配的B[4]的前一个字符的覆盖度减一),因为我们可以确保A[5…7]=B[1…2]

 

 就讲到这儿,只要明白的KMP算法的思想,再去看前面两个博客会更深入,我这里只是开个窍,计算P[i]有很多种办法,只要明白思路就行。

附 KMP代码(第二个博客中的,整理了一下)

#include <stdio.h>
#include <string.h>

//next[k]的值表示p[0...k-1]中最长后缀的长度
void getNext(char *p,int *next)
{
    int j,k;
    next[0]=-1;
    j=0;
    k=-1;
    while(j<strlen(p)-1)
    {
        if(k==-1||p[j]==p[k])    //匹配的情况下,p[j]==p[k]
        {
            j++;
            k++;
            next[j]=k;
        }
        else                   //p[j]!=p[k]
            k=next[k];
    }
}

int KMPMatch(char *s,char *p)
{
    int next[100];
    int i,j;
    i=0;
    j=0;
    getNext(p,next);
    while(i<strlen(s))
    {
        if(j==-1||s[i]==p[j])
        {
            i++;
            j++;
        }
        else
        {
            j=next[j];       //消除了指针i的回溯
        }
        if(j==strlen(p))
            return i-strlen(p);
    }
    return -1;
}

//int BFMatch(char *s, char* p)
//{
//    int i=0,j=0;
//    while(strlen(s)>strlen(p)&&i<strlen(s))
//    {
//        j=0;//要写在循环内,不然比较不能从子串的起点重新开始
//        while(s[i]==p[j]&&j<strlen(p))        //        if(s[i]==p[j]&&j<strlen(p))
//        {                                    //        {
//            i++;                            //            i++;
//            j++;                            //            j++;    
//        }                                   //        }
//        if(j==strlen(p))                    //        else if(s[i]==p[j]&&j==strlen(p))
//        {                                    //        {
//            return i-strlen(p);             //            return 1;//子串匹配
//        }                                    //        }
//        i=i-j+1;                            //        else 
//    }                                        //        {
//    return -1;                                //            i=i-j+1;//不匹配,回溯
//                                            //        }
//                                            //}
//                                            //    return 0;
//}

void main()
{
    char a[]={"ababcababa"};
    char b[]={"ababa"};
    //BFMatch(a,b);
    KMPMatch(a,b);
}

POJ 2406代码

package poj2406;
//找出循环子序列的个数
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main {

    public static void main(String[] args) {

        Scanner scn = new Scanner(System.in);
        // System.out.println(Main.class.getResource("in.dat"));
        // Scanner scn = new Scanner(Main.class.getResourceAsStream("in.dat"));
        PrintWriter out = new PrintWriter(System.out);
        String input = "";
        List<String> inputs = new ArrayList<String>(5000);
        int index = 0;
        while (scn.hasNext()) {
            input = scn.next();//read a string

            if (input.equals(".")) {
                break;
            }

            index++;
            inputs.add(input);
        }
        for (int i = 0; i < index; i++) {
            out.println(kmp(inputs.get(i)));
        }
        out.flush();
    }

    private static int kmp(String input) {
        int len = input.length();
        int ret[] = new int[len];
        ret[0] = -1;
        int k = -1;
        for (int i = 1; i < len; i++) {
            while (k >= 0 && input.charAt(k + 1) != input.charAt(i))
                k = ret[k];
            if (input.charAt(k + 1) == input.charAt(i))
                k++;
            ret[i] = k;
        }

        if (len % (len - ret[len - 1] - 1) == 0) {
            return len / (len - ret[len - 1] - 1);
        }
        return 1;
    }
}

 

 

 

posted on 2012-07-28 10:33  天天天都是大晴天  阅读(262)  评论(1)    收藏  举报