zoukankan      html  css  js  c++  java
  • KMP算法及next数组优化

    kmp算法思想.png

    一、KMP算法简介

    当目标串txt与模式串pat进行匹配时,如果txt[i]遇到不匹配的pat[j]时,不必像暴力匹配法那样每次只将pat后移一位,然后又从其第一位和txt[i+1]进行比较。

    因为如果pat[0]~pat[j-1]有公共前后缀的话,那么txt[i]前的子串也必定存在一个公共前后缀

    那么,我们可以将pat右移,使得从 pat的前缀匹配txt子串的前缀,pat的后缀匹配txt子串的后缀 ,变为pat的前缀匹配txt的后缀,这样就不必每次都移动一位,然后重新开始匹配

    上面提到的公共前后缀,应该为最长公共前后缀,因为公共前后缀越短,移动的越多,就有可能漏掉部分匹配,导致结果的出错。
    ababab的前缀为{'a','ab','aba','abab','ababa'},后缀为{‘b’,'ab','bab','abab','babab'}(不能是字符串本身
    它的最长公共字符串即为‘abab’.长度为4






    二、PMT

    PMT即 partial match table(部分匹配表),当前子串(pat[0]~pat[j])的最长公共前后缀
    例如“ababab"

    char a b a b a b
    index 0 1 2 3 4 5
    pmt 0 0 1 2 3 4
    public static int[] getPMT(String s0) {
            char[] s = s0.toCharArray();
            int[] pmt = new int[s.length];
    
            for (int flag = 0, i = 1; i < s.length; ) {
                if (s[flag] != s[i] && flag == 0) { //如果s[i]和字符串的第一个不相同
                    pmt[i] = 0;
                    i++;
                } else if (s[flag] == s[i]) {   //如果s[i]相匹配,pmt[i]为其pmt[j]+1 (j<i)
                    pmt[i] = flag + 1;
                    i++;
                    flag++;
                } else if (s[flag] != s[i] && flag != 0) { //如果不匹配且flag也不为0,则将最长公共前后缀缩小一个范围
                    flag = pmt[flag - 1];
                }
            }
    
            return pmt;
        }
    






    三、Next数组

    在当前字符pat[j]之前的子串(pat[0]~pat[j-1])的最长公共前后缀,那么我们可以将PMT数组整体右移一位且Next[0]设为-1即可得到。

    例如“ababab"

    char a b a b a b
    index 0 1 2 3 4 5
    pmt 0 0 1 2 3 4
    next -1 0 0 1 2 3

    如何求next数组

    递归得到

    • 令next[0]=-1

    • 如果当前位置元素s[i]的值与当前子串的最长公共前后缀(长度为K)的下一个元素s[k]相等,则最长公共前后缀长度加1,此时橙色长度+1(蓝色/绿色长度)作为s[i+1]的next值

    kmp(s[i]==s[i]).png


    • 如果不相等,则next值势必会缩小,由于长度为k的前缀、后缀(橙色部分)元素相同其相对位置也相同,那么他们各自的最长公共前后缀(朱红色)也势必相同

    • 如此,那么如果s[i]与 k缩小后的k`的下一个元素s[k`] 相等,那么m(s[i])就可以和n连起来了成为最长公共前后缀。

    • 所以依次缩小k的范围,直至匹配

    • 如果遇到next[k]=-1,则表名已经到了最后一个元素,,则表示无公共前后缀,停止匹配,next值为0

    kmp(s[i]!=s[i]).jpg

    public static int[] getNext(String s0) {
        char[] s = s0.toCharArray();
        int[] next = new int[s.length];
    
        // 初始条件
        int i = 0;
        int k = -1;
        next[0] = -1;
    
        // 根据已知的前i位推测第i+1位
        while (i < s.length - 1)  // 0 ~ i-2,next[i-1]是推出来的
        {
            //如果k=-1,则表示无公共前后缀,next值为0(-1++)
            //如果当前值和前一位的最大公共前后缀的下一位相等  例:如果s[i]=s[0] 则首位相等,next值:0+1=1
            if (k == -1 || s[i] == s[k]) {
                //k(之前的最长公共前后缀长度)+1 赋给 next[i+1] 
                //(因为s[i]和前面的在一起,作为s[i+1]的子串的长公共前后缀长度即next[i+1])
                next[++i] = ++k; 
            } else //不能匹配上
            {
                k = next[k];    //将k由 (0~S)的最长前后缀 改为  (0~S)的最长前后缀的最长前后缀
            }
        }
        return next;
    }
    






    四、KMP算法

    如果txt[i]和pat[j]不匹配,则将txt[i]与pat[next[j]]进行匹配

    设next[j]的长度为length,则pat[length]为最长公共前后缀的下一个元素
    之前的元素pat[0]~pat[length-1] (一共length个元素)依然和之前的txt的后缀相匹配。

    package array;
    
    /**
     * Description
     * Author cloudr
     * Date 2020/7/20 23:13
     * Version 1.0
     **/
    public class KMP {
        public static void main(String[] args) {
            String haystack = "mississippi";
            String needle = "issip";
            System.out.println(KMPByNext(haystack, needle));
        }
    
        public static int KMPByNext(String txt, String pat) {
            if (pat.length() == 0) return 0;
            int[] next = getNext(pat);
            int i = 0, j = 0;
            while (i < txt.length() && j < pat.length()) {
                //next值为-1,txt右移动一位,pat移动整个长度(j++=0)
                //如果匹配成功,继续将txt,pat的下一个元素进行匹配
                if (j == -1 || txt.charAt(i) == pat.charAt(j)) {
                    i++;
                    j++;
                } else //匹配失败则回溯
                    j = next[j];
            }
            if (j == pat.length())
                return i - j;
            else
                return -1;
        }
    
        public static int[] getNext(String ps) {
            char[] strKey = ps.toCharArray();
            int[] next = new int[strKey.length];
    
            // 初始条件
            int i = 0;
            int k = -1;
            next[0] = -1;
    
            // 根据已知的前i位推测第j+1位
            while (i < strKey.length - 1)  // 0~ i-2 因为要整体右移一位且0位置为-1
            {
                //如果k=-1,则表示无公共前后缀,next值为0
                //如果当前值和前一位的最大公共前后缀的下一位相等  例:如果s[i]=s[0] 则首位相等,next值:0+1=1
                if (k == -1 || strKey[i] == strKey[k]) {
                    next[++i] = ++k;    //k(之前的最长公共前后缀)+1 赋给 next[i+1] (因为next数组整体右移动一位)
                } else //不能匹配上
                {
                    k = next[k];    //将k由 (0~S)的最长前后缀 改为  (0~S)的最长前后缀的最长前后缀
                }
            }
            return next;
        }
    }
    
    



    改进next数组

    有txt="aaaabcdef",pat="aaaaax"

    next数组改进.jpg

    s[i]和p[j]已经匹配失败,下一步会与p[ next[j] ]相匹配,但是如果p[j] == p[ next[j] ],那么后面s[i]和p[ next[j] ]的匹配也必然会失败,所以我们将p[ next[j] ]之前的子串的最长公共前后缀 next[ next[j] ]赋值给next[j]

    public static int[] getNext(String s0) {
        char[] strKey = ps.toCharArray();
        int[] next = new int[strKey.length];
    
        // 初始条件
        int i = 0;
        int k = -1;
        next[0] = -1;
    
        // 根据已知的前i位推测第j+1位
        while (i < strKey.length - 1)  // 0~ i-2 因为要整体右移一位且0位置为-1
        {
            //如果k=-1,则表示无公共前后缀,next值为0
            //如果当前值和前一位的最大公共前后缀的下一位相等  例:如果s[i]=s[0] 则首位相等,next值:0+1=1
            if (k == -1 || strKey[i] == strKey[k]) {
                i++;    //赋给 next[i+1] (因为next数组整体右移动一位)
                k++;    //k(之前的最长公共前后缀)+1
                if (strKey[i] != strKey[k]) {
                    next[i] = k;
                }
                else {  //不能出现p[j] = p[ next[j] ]的情况,如果出现,则将next[j]的范围缩小
                    next[i] = next[k];      //  长度为k的pat[0]~pat[k-1]缩小一个范围 长度即为next[k]  k=next[i] , 则next[i] = next[k] = next [ next[i] ]
                }
            } else //不能匹配上
            {
                k = next[k];    //将k由 (0~S)的最长前后缀 改为  (0~S)的最长前后缀的最长前后缀
            }
    
        }
        return next;
    }
    


    基于PMT数组(未成功,对于某些子串符匹配会陷入死循环,待填坑)

    String haystack = "mississippi";
    String needle = "issip";

    class Solution {
        public int strStr(String haystack, String needle) {
           if (needle.length() == 0) return 0;
            int[] pmt = getPMT(needle);
            int i = 0, j = 0;
            while (i < haystack.length() && j < needle.length()) {
                if (j == 0 && haystack.charAt(i) != needle.charAt(j)) {
                    i++;
                    if (i == haystack.length())
                        break;
                }
    
                if (haystack.charAt(i) == needle.charAt(j)) {
    
                    i++;
                    j++;
                } else
                    j = j - pmt[j];
            }
            if (j == needle.length())
                return i - j;
            else
                return -1;
        }
    }
    






    推荐阅读



    参考
    [1]https://blog.csdn.net/qq_30974369/article/details/74276186
    [2]https://www.cnblogs.com/cherryljr/p/6519748.html

  • 相关阅读:
    使用Maven Helper解决Maven依赖冲突
    bzoj 1037[ZJOI2008]生日聚会Party
    bzoj 1031[JSOI2007]字符加密
    bzoj 1029 [JSOI2007]建筑抢修
    bzoj 1025[SCOI2009]游戏
    bzoj 1027[JSOI2007]合金
    bzoj 1024[SCOI2009]生日快乐
    bzoj 1023[SHOI2008]cactus仙人掌图
    bzoj 1022 [SHOI2008]小约翰的游戏John
    bzoj 1021[SHOI2008]Debt 循环的债务
  • 原文地址:https://www.cnblogs.com/ccink/p/13360294.html
Copyright © 2011-2022 走看看