zoukankan      html  css  js  c++  java
  • KMP算法证明及实现

    KMP算法

    一、普通的字符串匹配

           平时我们在写普通的字符串匹配算法的时候,是拿着要匹配的串去匹配被匹配的串,字符逐个比较,当发现字符失配时,被匹配的字符串的指针要回到前一次开始匹配的指针的下一个位置。这里我们称要去匹配的字符串为模式串P,被匹配的字符串为主串S,即我们拿模式串P去匹配主串S,看看P是否是S的子串。

           例如:主串S是“abcabdsfabcdfrt”,模式串P是“abcd”,开始匹配时,可以看到,S和P的字符串0、1、2位置的字符是相同的,到3位置时出现不匹配,这是根据我们以往的方法,我们将主串的指针回到刚开始匹配的位置的下一个位置,即回到主串的1位置,即字符b,再拿着模式串的0位置去匹配。以此类推,每次出现失配时,主串的指针都回溯到初始匹配位置的下一个位置,模式串的指针都回到模式串的0位置。

    二、为何要用KMP算法

           当我们的模式串P中,出现了连续相同的字符串时,例如P=“abcabx”,我们拿这个P和主串S=“abcabqqeeabcabxxxaxxaa”去匹配时。开始匹配可以看到,主串S的0、1、2、3、4位置和模式串的0、1、2、3、4位置的字符都是相同的,接着两个串的指针都下移,到了S的5位置的字符就和P的5位置的字符就出现吧不匹配,根据以往经验我们会将S的指针回溯到1位置接着和P的0位置进行匹配。这是我们发现,我们的P的0-1和3-4位置的字符串是一样的,而且S的0-4已经和P的0-4匹配是相同的,所以主串S的3、4位置和模式串P的0、1位置是相同的。

           如果用主串的1、2位置与模式串从头匹配,则都是失配的,主串S的3、4位置和模式串P的0、1位置是相同的,所以我们就可以不回溯主串的指针,让模式串的2位置直接与主串的当前位置进行匹配即可。

    三、KMP算法的数学推导

           目前几乎所有将KMP算法的j都是从1开始的,为了让大家更好的理解,我在此证明阶段让j也从1开始计算。

           根据上面的情况,我们推广到一般情况。我们用i表示指向主串的指针,j表示指向模式串的指针,当主串的第i个字符和模式串的第j个字符失配时,主串中的第i个字符(指针i不回溯)应该与模式串中的哪个位置的字符再比较呢,假设我们的主串已经和模式串匹配比较到模式串的第k个字符了,那么模式串的前k-1个字符一定和主串的第i-k+1到i-1个字符是相同的,即

    P1 P2 ……Pk-1=Si-k+1 Si-K+2…… Si-1

    而已经得到的部分匹配结果是

    Pj-k+1 Pj-k+2 ……Pj-1=Si-k+1 Si-K+2…… Si-1

    由以上两式推导得出

    P1 P2 ……Pk-1= Pj-k+1 Pj-k+2 ……Pj-1

    反之,若模式串中存在满足上式的两个子串,则当匹配过程中,主串中的第i个字符和模式串中的第j个字符比较不相等时,仅需要将模式串向右滑动至模式串中的第k个字符和主串中第i个字符对齐,(此时,由于模式串中前k-1个字符和第i-k到i-1位置的字符都是对应相同的,所以模式串中前k-1个字符的子串P1 P2 ……Pk-1必定与主串中第i个字符之前长度为k-1的子串Si-k+1 Si-K+2…… Si-1相等)接着匹配从模式串的第k个字符与主串的第i个字符比较起继续进行。

    四、求每一个位置对应的k(即next数组)

           我们用next数组来存取模式串中每一个位置对应的k值,即next[j]表示当模式串中第j个字符和模式串中相应的字符失配时,在模式串中重新和主串中该字符进行比较的字符的位置。

           根据三中的数学推导,得到了next函数的定义(假设字符串起始位置是1)

    当j=1时,next[j]=0;

    当j!=1时,next[j]=max(k|1<k<j且P1 P2 ……Pk-1= Pj-k+1 Pj-k+2 ……Pj-1)此集合不    为空时。若此集合为空,next[j]=1。

    例,求模式串“abaabcac”的next数组

    j

    1

    2

    3

    4

    5

    6

    7

    8

    模式串

    a

    b

    a

    a

    b

    c

    a

    c

    Next[j]

    0

    1

    1

    2

    2

    3

    1

    2

     

    五、next数组的实现

           已知第j项,求第j+1

           假设我们已经求得了next数组的前j项,当我们求第j+1项时。根据K(next[])的定义,模式串P的前next[j]-1项和第j-next[j]+1到第j-1项是相同的,这时如果模式串P的第next[j]项和第j项相同,则next[j+1]=next[j]+1。

    如果模式串P的第next[j]项和第j项不相同,我们为了减少模式串P前后字符的比较次数,我们接下来寻找下一个最长的已知的前后对应相等的字符串,而且这个字符串要以P的第j个字符结尾。

           我们已知模式串的前next[next[j]]-1项等于其第next[j]- next[next[j]]+1到next[j]-1项,而且模式串P的前next[j]-1项和第j-next[j]+1到第j-1项是相同的,所以模式串的前next[next[j]]-1项等于其第j- next[next[j]]+1到第j项。这时,如果模式串的第next[next[j]项等于其第j+1项,next[j+1]=next[j]+1;否则以此往前迭代,如果全部不匹配,直到j=1,返回1,迭代结束。

           求next数组的代码实现:

    static void getNext(String str){

          // 为方便操作,把字符串转换为字符数组;定义next数组;

          char[] strKey = ("a"+str).toCharArray();

           int[] next = new int[strKey.length];

           // 初始条件,j从1开始.

           int j = 1;

           next[1] =0;

           int k = next[j];

           // 根据已知的前j位推测第j+1位

           while (j < next.length - 1)

           {

               if (k == 0 || strKey[j] == strKey[k])

               {

                   next[++j] = ++k;

               }

               else//不匹配时,往前寻找次长的子串

               {

                   k = next[k];

               }

           }

          //打印一下我们的next数组

          System.out.print("next[]的值 ");

          for (int i = 1; i < next.length; i++) {

             System.out.print(next[i] +" ");

          }

       }

    六、KMP算法整体代码实现

    public class KMP {

       /*

        * Kmp函数实现寻找模式串str2在主串str1中的位置,返回值即为str2在str1中的位置,

        * 如果str2不是str1的子串则返回-1.

        */

       private static int Kmp(String str1,String str2){

          //第一步 求next[j]数组

          char[] strKey = str2.toCharArray();

           int[] next = new int[strKey.length];

           // 初始条件

           int j1 = 0;

           int k = -1;

           next[0] =-1;

           // 根据已知的前j位推测第j+1位

           while (j1 < strKey.length - 1)

           {

               if (k == -1 || strKey[j1] == strKey[k])

               {

                   next[++j1] = ++k;

               }

               else

               {

                   k = next[k];

               }

           }

          //打印一下我们的next数组

          System.out.print("next[]的值 ");

          for (int i = 0; i < next.length; i++) {

             System.out.print(next[i]+1+" ");

          }

          System.out.println();

          //第二步 根据求得的next数组进行字符串匹配

          int j=0;//j指向模式串str2,i指向主串str1.

          for (int i = 0; i < str1.length(); i++) {

             if(j==str2.length()) return i-j;

             if(str1.charAt(i)==str2.charAt(j)) j++;

             else j=next[j]+1;

          }

          return -1;

       }

       //测试数据

       public static void main(String[] args) {

          String str1="12345abaabcac2356";

          String str2="abaabcac";

          int a=Kmp(str1,str2);

          System.out.println(a);

       }

    }

  • 相关阅读:
    Linux文件属性
    [Oracle] Listener的动态注册
    jQuery easyUI Pagination控件自定义div分页(不用datagrid)
    桂林电子科技大学出校流量控制器Android版1.0.0
    php使用check box
    Python windows ping
    Python selenium chrome 环境配置
    Linux wget auto login and backup database
    PyQt4 ShowHMDB show sqlite3 with QTableWidget summary
    PyQt4 py2exe 打包 HardwareManager
  • 原文地址:https://www.cnblogs.com/JCxiangbei/p/6059445.html
Copyright © 2011-2022 走看看