• 字符串与模式匹配算法(三):KMP算法


    一、KMP算法介绍

      KMP算法与前面的MP算法一脉相承,都是充分利用先前匹配的过程中已经得到的结果来避免频繁回溯。回顾一下MP算法,如下图的模式串偏移,当前模式字符串P的左端的p0与目标字符串T中tj位置对齐。从左向右逐个进行比较,发现 p处的字符a 与 tj+1 处字符b发生失配。同时也表明 P(p0,p1,...,pi-1) 与 T'(tj,tj+1,...,tj+i-1) 是完全匹配的,这一部分子串在图中用字母u标示出。由于发生失配,随即移动模式字符串并进行下一轮的比较。此时,很自然地希望移动之后的结果可以使得模式字符串P中的一个前缀v,可以匹配到子串u的某一部分后缀。所以MP算法引入一个mpNext数组,并用它来对P中最长前缀进行标记。然后根据PmpNext[i] = c 和 Ti+j = b 之间展开下一轮比较。

      在MP算法的基础上再推进一步,继续前面的过程,当模式字符串P完成一次移动后,接下来马上要进行的工作是比较字符 b 和 c,为了避免随之而来的一次失配,在仅仅知道模式字符串P的情况下,保证一次移动后,紧随着前缀字符串v之后的那个字符c不等于原来失配的字符a(满足这个条件的最长前缀v是字符串u的加标边际)。KMP算法需要对mpNext表中符合要求的加标边际进行标识,符合要求指的是:① v可以匹配到u中某个后缀的最长前缀; ② 紧跟在v后面的字符c不同于紧跟在u后面的字符a。

    二、kmpNext表的规则

      在mpNext表生成的基础上,建立kmpNext表的规则分为4种情况,其中 1≤j≤m-1:

    1. 如果 mpNext[j] = 0 且 pj = p0,则令kmpNext[j] = -1;

    2. 如果 mpNext[j] = 0 且 pj ≠ p0,则令kmpNext[j] = 0;

    3. 如果 mpNext[j] ≠ 0 且 pj ≠ pmpNext[j],则令kmpNext[j] = mpNext[j];

    4. 如果 mpNext[j] ≠ 0 且 pj = pmpNext[j],则用mpNext[j]中的值替换原来mpNext[j]中的j值,直到情况转换为前面3种情况的一种,从而递归求解kmpNext[j]。

      在 j =0 的位置同样是 -1,并令kmpNext[m] = mpNext[m],m是模式串P的长度。kmpNext[m]的值也是指示了后续进行匹配而需要将模式字符串移动的位数。

      kmpNext表:

    j

    0

    1

    2

    3

    4

    5

    6

    7

    p(j)

    c

    a

    a

    t

    c

    a

    t

     

    mpNext[j]

    -1

    0

    0

    0

    0

    1

    2

    0

    kmpNext[j]

    -1

    0

    0

    0

    -1

    0

    2

    0

    三、代码

     1     public void preKmp(char[] x, int m, int[] kmpNext) {
     2         int i, j;
     3         i = 0;
     4         j = kmpNext[0] = -1;
     5         while(i < m-1) {
     6             while (j > -1 && x[i] != x[j])
     7                 j = kmpNext[j];
     8             i++;
     9             j++;
    10             if (x[i] == x[j])
    11                 kmpNext[i] = kmpNext[j];
    12             else
    13                 kmpNext[i] = j;
    14         }
    15     }
    16 
    17     public void kmp(String p, String t) {
    18         int m = p.length();
    19         int n = t.length();
    20 
    21         if (m > n) {
    22             System.err.println("Unsuccessful match!");
    23             return;
    24         }
    25 
    26         char[] x = p.toCharArray();
    27         char[] y = t.toCharArray();
    28 
    29         int i = 0;
    30         int j = 0;
    31         int[] kmpNext = new int[m+1];
    32         preKmp(x, m, kmpNext);
    33 
    34         while (j < n) {
    35             while (i > -1 && x[i] != y[j])
    36                 i = kmpNext[i];
    37             i++;
    38             j++;
    39             if (i >= m) {
    40                 System.out.println("Matching index found at: " + (j - i + 1));
    41                 i = kmpNext[i];
    42             }
    43         }
    44     }
  • 相关阅读:
    C# 之泛型详解
    C#方法的六种参数,值参数、引用参数、输出参数、参数数组、命名参数、可选参数
    在C#中理解和实现策略模式的绝对入门教程
    负载均衡的原理
    C#多线程编程
    深入研究虚幻4反射系统实现原理(二)
    深入研究虚幻4反射系统实现原理(一)
    虚幻4属性系统(反射)翻译
    UE4中使用数据表(Data Table)
    UE4 自定义物理表面类型(Surface Type)
  • 原文地址:https://www.cnblogs.com/magic-sea/p/11817290.html
Copyright © 2020-2023  润新知