KMP字符串匹配

Knuth-Morris-Pratt

KMP算法是一种在文本串s中快速查找模式串p的一种算法。

动态规划实现

关键步骤:构建状态转移数组

package code;

/**
 * 动态规划实现KMP
 */
public class KMP {
    private int[][] dp;
    
    public int getIndex(String s, String pattern) {
        buildFSM(pattern);
        return search(s, pattern);
    }

    public void buildFSM(String pattern) {
        int m = pattern.length();

        /**
         * 状态转移数组
         * @param m 一共有 m 个状态
         * @param 26 本次只考虑小写字母字符串的匹配情况,可自由扩展
         */
        dp = new int[m][26];

        /**
         * 初始状态定义
         * @param prefixState 表示当前位置的前缀状态,匹配字符失败时要回退的状态
         */
        dp[0][pattern.charAt(0) - 'a'] = 1;
        int prefixState = 0;
        
        /**
         * 状态转移
         */
        for (int i = 1; i < m; i++) {
            for (int j = 0; j < 26; j++) {
                dp[i][j] = dp[prefixState][j];  // 全部使用前缀状态做初始填充
            }

            /**
             * 更新下一状态和前缀状态
             */
            dp[i][pattern.charAt(i) - 'a'] = i + 1;
            prefixState = dp[prefixState][pattern.charAt(i) - 'a'];
        }  
    }

    public int search(String s, String pattern) {
        int m = pattern.length();
        int n = s.length();
        int state = 0;

        for (int i = 0; i < n; i++) {
            state = dp[state][s.charAt(i) - 'a'];    // 计算下一状态
            
            // 到达最终状态, 匹配成功
            if (state == m) {
                return i - m + 1;
            }
        }

        return -1;
    }
}

传统实现

关键步骤:构建 next 数组

什么是 next 数组?

next 数组中保存着当前位置真前缀与真后缀的交集中最长元素的长度(真前缀的意思是不能是整个串,必须是子串)。

Example

模式串(p):"ababcabaa"

对应的 next 数组:

index 0 1 2 3 4 5 6 7 8
p a ab aba abab ababc ababca ababcab ababcaba ababcabaa
next 0 0 1 2 0 1 2 3 1
package code;

/**
 * 传统KMP实现
 */
public class KMP {
    private int[] next;

    /**
     * 计算最长公共前后缀长度, 构建 next 数组
     * @param pattern
     */
    public void buildNext(String pattern) {
        int m = pattern.length();
        next = new int[m];

        next[0] = 0;  // 单个字符只能为0

        /**
         * 将 pattern 右移1位, 自己匹配自己(用前缀匹配后缀)
         * @param i 指示后缀
         * @param j 指示前缀
         */
        for (int i = 1, j = 0; i < m; i++) {
            while (j != 0 && (pattern.charAt(i) != pattern.charAt(j))) {
                j = next[j - 1];
            }

            if (pattern.charAt(i) == pattern.charAt(j)) {
                j += 1;
            }

            next[i] = j;
        }
    }

    public int search(String s, String pattern) {
        for (int i = 0, j = 0; i < s.length(); i++) {
            /**
             * 字符匹配失败, 则回退到上一状态
             * i指针不变; j指针左移
             */
            while (j != 0 && s.charAt(i) != pattern.charAt(j)) {
                j = next[j - 1];
            }
            
            if (s.charAt(i) == pattern.charAt(j)) {
                j += 1;
            }

            if (j == pattern.length()) {
                return i - j + 1;
            }
        }

        return -1;
    }
}

参考文章:

[1] KMP算法详解

[2] 算法学习笔记

posted @ 2022-07-07 12:41  ylyzty  阅读(16)  评论(0编辑  收藏  举报