zoukankan      html  css  js  c++  java
  • KMP字符串匹配算法


    3.3.6 基于《最大长度表》与基于《next 数组》等价

        我们已经知道,利用next 数组进行匹配失配时,模式串向右移动 j - next [ j ] 位,等价于已匹配字符数 - 失配字符的上一位字符所对应的最大长度值。原因是:

    1. j 从0开始计数,那么当数到失配字符时,j 的数值就是已匹配的字符数;
    2. 由于next 数组是由最大长度值表整体向右移动一位(且初值赋为-1)得到的,那么失配字符的上一位字符所对应的最大长度值,即为当前失配字符的next 值。

        但为何本文不直接利用next 数组进行匹配呢?因为next 数组不好求,而一个字符串的前缀后缀的公共元素的最大长度值很容易求。例如若给定模式串“ababa”,要你快速口算出其next 数组,乍一看,每次求对应字符的next值时,还得把该字符排除之外,然后看该字符之前的字符串中有最大长度为多大的相同前缀后缀,此过程不够直接。而如果让你求其前缀后缀公共元素的最大长度,则很容易直接得出结果:0 0 1 2 3,如下表格所示:

        然后这5个数字 全部整体右移一位,且初值赋为-1,即得到其next 数组:-1 0 0 1 2。


    1. void GetNext(char* p,int next[])
    2. {
    3. int pLen = strlen(p);
    4. next[0] = -1;
    5. int k = -1;
    6. int j = 0;
    7. while (j < pLen - 1)
    8. {
    9. //p[k]表示前缀,p[j]表示后缀
    10. if (k == -1 || p[j] == p[k])
    11. {
    12. ++k;
    13. ++j;
    14. next[j] = k;
    15. }
    16. else
    17. {
    18. k = next[k];
    19. }
    20. }
    21. }


    行文至此,咱们全面了解了暴力匹配的思路、KMP算法的原理、流程、流程之间的内在逻辑联系,以及next 数组的简单求解(《最大长度表》整体右移一位,然后初值赋为-1)和代码求解,最后基于《next 数组》的匹配,看似洋洋洒洒,清晰透彻,但以上忽略了一个小问题。

        比如,如果用之前的next 数组方法求模式串“abab”的next 数组,可得其next 数组为-1 0 0 1(0 0 1 2整体右移一位,初值赋为-1),当它跟下图中的文本串去匹配的时候,发现b跟c失配,于是模式串右移j - next[j] = 3 - 1 =2位。

        右移2位后,b又跟c失配。事实上,因为在上一步的匹配中,已经得知p[3] = b,与s[3] = c失配,而右移两位之后,让p[ next[3] ] = p[1] = b 再跟s[3]匹配时,必然失配。问题出在哪呢?

       

        问题出在不该出现p[j] = p[ next[j] ]。为什么呢?理由是:当p[j] != s[i] 时,下次匹配必然是p[ next [j]] 跟s[i]匹配,如果p[j] = p[ next[j] ],必然导致后一步匹配失败(因为p[j]已经跟s[i]失配,然后你还用跟p[j]等同的值p[next[j]]去跟s[i]匹配,很显然,必然失配),所以不能允许p[j] = p[ next[j ]]。如果出现了p[j] = p[ next[j] ]咋办呢?如果出现了,则需要再次递归,即令next[j] = next[ next[j] ]。


    优化 求next数组
    1. class KMP {
    2. public static int[] GetNext(string str) {
    3. int length = str.Length;
    4. int[] next = new int[length];
    5. next[0] = -1;
    6. int k = -1;
    7. int j = 0;
    8. while (j < length - 1) {
    9. if (k == -1 || str[j] == str[k]) {
    10. ++j;
    11. ++k;
    12. if (str[j] != str[k]) {
    13. next[j] = k;
    14. } else {
    15. next[j] = next[k];
    16. }
    17. } else {
    18. k = next[k];
    19. }
    20. }
    21. return next;
    22. }
    23. }

    KMP搜索
    1. public static int Search(string str, string pat) {//搜索第一个匹配位置
    2. int i = 0, j = 0;
    3. int[] next = KMP.GetNext(str);
    4. while (i < str.Length && j < pat.Length) {
    5. if (j == -1 || str[i] == pat[j]) {
    6. j++;
    7. i++;
    8. } else {
    9. j = next[j];
    10. }
    11. }
    12. if (j == pat.Length) {
    13. return i - j;
    14. } else {
    15. return -1;
    16. }
    17. }

    1. static public IList<int> Search(string s, string p) {//搜索所有匹配位置
    2. List<int> result = new List<int>();
    3. int[] next = KMP.GetNext(s);
    4. int j = 0;
    5. int i = 0;
    6. while (i < s.Length) {
    7. while (i < s.Length && j < p.Length) {
    8. if (j == -1 || s[i] == p[j]) {
    9. j++;i++;
    10. } else {
    11. j = next[j];
    12. }
    13. }
    14. if (j == p.Length) {
    15. result.Add(i - j);
    16. i = (i - j) + p.Length;
    17. j = next[j];
    18. }
    19. }
    20. return result;
    21. }





  • 相关阅读:
    WEB项目运行时,多次遇到 The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one time zone问题解决办法
    如何区分研究背景与研究意义
    xml学习笔记
    visio2013/2016软件及激活码总汇
    Spyder 常用快捷键总汇
    java.sql.SQLNonTransientConnectionException: Cannot load connection class because of underlying exception:
    Myeclipse 与 MysqlSQL数据库连接报错-The Server time zone value 'XXXXX' 乱码 is unrecognized...
    一个数除以9余8除以8余7除以7余6
    js关于饮料瓶换饮料的算法
    cocos creator集成小游戏去掉背景
  • 原文地址:https://www.cnblogs.com/xiejunzhao/p/67bd4e2e5a57435d013aa05b92857bc4.html
Copyright © 2011-2022 走看看