串的模式匹配算法

尴尬啊,大学数据结构课程用的书看到了现在。说起来这本书在业内也是相当有名,它就是清华大学出版社出版的《数据结构(C语言版)》。在该书的 4.3 节,讲解了“串的模式匹配算法”,个人感觉这一节应该是前四章最值得看的一节了。下面就说说相关算法的爪哇实现吧!

简单粗暴

首先,肯定还是说最简单最暴力的方法喽,没废话就是从前向后遍历:

/**
 * Cerated by clearbug on 2018/2/23.
 *
 * 串的模式匹配算法
 */
public class SubStrMatcher {

    public static void main(String[] args) {
        System.out.println(indexForce("ababcabcacbab", "abcac", 5));
        System.out.println(indexForce("00000000000000000000000000000000000000000000000001", "0000000001", 0));
    }

    /**
     * 朴素字符串匹配算法
     *
     * @param s 主串
     * @param t 模式串
     * @param pos 匹配操作起始位置
     * @return 若模式串匹配不成功,则返回 -1;否则,返回主串中第一次匹配成功的子串的索引值;
     */
    public static int indexForce(String s, String t, int pos) {
        char[] sArr = s.toCharArray();
        char[] tArr = t.toCharArray();

        int sLen = sArr.length;
        int tLen = tArr.length;
        int i = pos, j = 0;
        while (i < sLen && j < tLen) {
            if (sArr[i] == tArr[j]) {
                i++;
                j++;
            } else {
                i = i -j + 1;
                j = 0;
            }
        }
        if (j == tLen) { // 匹配成功了
            return i - j;
        }
        return -1;
    }
}

后来,我听说这种算法也是有个优雅的名字的—BF算法,全称并不是美好的“Boy Friend”,而是更美好的“Brute Force”!

优雅点

优雅点的当然就是大名鼎鼎的 KMP 算法了,为啥要叫它 KMP 算法呢?因为它是由 D.E.Knuth 与 V.R.Pratt 和 J.H.Morris 这三位大佬同时发现的,所以简称为 KMP 算法,俗称为“看毛片”算法。
这个算法虽然思路简单,但是实现却是好复杂哦,像我这种算法渣渣看了一天的博客教程+视频教程才稍微写出了其实现。哎,上帝限制了我的智商!下面就直接贴代码实现吧:

/**
 * 原始 KMP 算法实现 
 * 
 * @param s 主串
 * @param t 模式串
 * @param pos 匹配操作起始位置
 * @return 若模式串匹配不成功,则返回 -1;否则,返回主串中第一次匹配成功的子串的索引值;
 */
public static int indexKmp(String s, String t, int pos) {
    char[] sArr = s.toCharArray();
    char[] tArr = t.toCharArray();

    int[] next = kmpNext(t);

    int sLen = sArr.length;
    int tLen = tArr.length;
    int i = pos, j = 0;
    while (i < sLen && j < tLen) {
        if (sArr[i] == tArr[j]) {
            i++;
            j++;
        } else {
            if (next[j] == -1) {
                i++;
                j = 0;
            } else {
                j = next[j];
            }
        }
    }
    if (j == tLen) {
        return i - j;
    }

    return -1;
}

/**
 * 原始 KMP 算法的 next 方法实现
 * 
 * @param t 模式字符串
 * @return next 数组
 */
public static int[] kmpNext(String t) {
    char[] tArr = t.toCharArray();

    int[] next = new int[tArr.length];
    next[0] = -1;

    int i = -1, j = 0; // i:前缀;j:后缀;
    while (j < tArr.length - 1) {
        if (i == -1 || tArr[i] == tArr[j]) {
            i++;
            j++;
            next[j] = i;
        } else {
            i = next[i];
        }
    }

    return next;
}

再优雅一点

后人在使用 KMP 算法的同时也没有忘记优化它,然后就出来了一个优化后的 next 方法实现:

/**
 * 优化后的 KMP 算法的 next 方法实现
 *
 * @param t 模式字符串
 * @return next 数组
 */
public static int[] optdKmpNext(String t) {
    char[] tArr = t.toCharArray();

    int[] next = new int[tArr.length];
    next[0] = -1;

    int i = -1, j = 0; // i:前缀;j:后缀;
    while (j < tArr.length - 1) {
        if (i == -1 || tArr[i] == tArr[j]) {
            i++;
            j++;
            if (tArr[i] == tArr[j]) {
                next[j] = next[i];
            } else {
                next[j] = i;
            }
        } else {
            i = next[i];
        }
    }

    return next;
}

再度进化-Horspool算法

参考博客里面我看作者提到了这个 Horspool 算法,于是我也试着写了下它的实现:

public static int indexHorspool(String s, String t, int pos) {
    char[] sArr = s.toCharArray();
    char[] tArr = t.toCharArray();

    int sLen = sArr.length;
    int tLen = tArr.length;
    int i = pos, j = tLen - 1;
    while (i + j < sLen && j >= 0) {
        if (sArr[i + j] == tArr[j]) {
            j--;
        } else {
            int k = j - 1;
            while (k >= 0) {
                if (tArr[k] == sArr[i + j]) {
                    i = i + (j - k);
                    break;
                }
                k--;
            }
            if (k == -1) {
                i = i + j;
            }
            j = tLen - 1;
        }
    }
    if (j == -1) {
        return i;
    }
    return -1;
}

超级进化-BM算法

到了这里我已经完全看不懂了,看了不少博客视频教程都把我看傻逼了。暂时先把找到的源码贴在这里吧:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * Cerated by clearbug on 2018/2/25.
 */
public class BoyerMoore {

    /** function findPattern **/
    public void findPattern(String t, String p) {
        char[] text = t.toCharArray();
        char[] pattern = p.toCharArray();
        int pos = indexOf(text, pattern);
        if (pos == -1) {
            System.out.println("\nNo Match\n");
        } else {
            System.out.println("Pattern found at position: " + pos);
        }
    }

    /** Function to calculate index of pattern substring **/
    public int indexOf(char[] text, char[] pattern) {
        if (pattern.length == 0) {
            return 0;
        }
        int[] charTable = makeCharTable(pattern);
        int[] offsetTable = makeOffsetTable(pattern);
        for (int i = pattern.length - 1, j; i < text.length; ) {
            for (j = pattern.length - 1; pattern[j] == text[i]; --i, --j) {
                if (j == 0) {
                    return i;
                }
            }
            i = i + Math.max(offsetTable[pattern.length - 1 -j], charTable[text[i]]);
        }
        return -1;
    }

    /** Makes the jump table based on the mismatched character information **/
    private int[] makeCharTable(char[] pattern) {
        final int ALPHABET_SIZE = 256;
        int[] table = new int[ALPHABET_SIZE];
        for (int i = 0; i < table.length; i++) {
            table[i] = pattern.length;
        }
        for (int i = 0; i < pattern.length - 1; i++) {
            table[pattern[i]] = pattern.length - 1 - i;
        }
        return table;
    }

    /** Makes the jump table based on the scan offset which mismatch occurs. **/
    private static int[] makeOffsetTable(char[] pattern) {
        int[] table = new int[pattern.length];
        int lastPrefixPosition = pattern.length;
        for (int i = pattern.length - 1; i >= 0; i--) {
            if (isPrefix(pattern, i + 1)) {
                lastPrefixPosition = i + 1;
            }
            table[pattern.length - 1 - i] = lastPrefixPosition - i + pattern.length - 1;
        }
        for (int i = 0; i < pattern.length - 1; i++) {
            int slen = suffixLength(pattern, i);
            table[slen] = pattern.length - 1 - i + slen;
        }
        return table;
    }

    /** function to check if needle[p:end] a prefix of pattern **/
    private static boolean isPrefix(char[] pattern, int p) {
        for (int i = p, j = 0; i < pattern.length; i++, j++) {
            if (pattern[i] != pattern[j]) {
                return false;
            }
        }
        return true;
    }

    /** function to returns the maximum length of the substring ends at p and is a suffix **/
    private static int suffixLength(char[] pattern, int p) {
        int len = 0;
        for (int i = p, j = pattern.length - 1; i >= 0 && pattern[i] == pattern[j]; i--, j--) {
            len = len + 1;
        }
        return 0;
    }

    /** Main Function **/
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("Boyer Moore Algorithm Test");
        System.out.println("Enter Text\n");
//        String text = br.readLine();
        String text = "abcdefghijklmnopqrstuvwxyz";
        System.out.println("Enter Pattern\n");
//        String pattern = br.readLine();
        String pattern = "qrstuv";
        BoyerMoore bm = new BoyerMoore();
        bm.findPattern(text, pattern);
    }

}

参考

  1. http://blog.csdn.net/u010366748/article/details/50721299
  2. http://blog.csdn.net/qq_35644234/article/details/55806885
  3. http://blog.fishc.com/2297.html
  4. http://www.sanfoundry.com/java-program-boyer-moore-algorithm/
posted @ 2018-02-23 21:06  optor  阅读(535)  评论(0编辑  收藏  举报