字符串部分:子字符串查找

子字符串查找

在母串中匹配对应的模式串(pattern)

暴力查找

public class PatternMatch {
    public static int search(String pat, String txt) {
        int M = pat.length();
        int N = txt.length();

        for (int i = 0; i <= N - M; i++) {
            int j;
            // j 主要是用来遍历模式串
            for (j = 0; j < M; j++) {
                if (txt.charAt(i + j) != pat.charAt(j)) break;
            }
            if (j == M) return i;
        }
        return N;
    }

    public static int search2(String pat, String txt) {
        int j, M = pat.length();
        int i, N = txt.length();

        for (i = 0, j = 0; i < N && j < M; i++) {
            if (txt.charAt(i) == pat.charAt(j)) j++;
            else {
                i -= j; // 如果在这个位置匹配失败,那么i可以直接移步到这个位置,而不是往后进一位
                j = 0;
            }
        }
        return j == M ?
                i - M : // 找到匹配
                N; // 未找到匹配
    }
}

KMP算法

推荐B站UP主,正月点灯笼的讲解视频

基本思想:出现不匹配时,就能知晓一部分文本的内容;避免将指针回退到所有这些已知晓的字符之前

image-20200709222121865

  • 首先需要获取模式串的所有前缀表

image-20200709222527351

  • 构造前缀表,通过最长公共前后缀来计算

image-20200709222620798

  • 第一次匹配失败,根据前缀表,退到数组序号为1的位置

image-20200709222602441

  • 第二次匹配失败退到数组为0的位置

image-20200709222819810

  • 0号匹配失败,数组移动到-1,表示整个字符串往右移动一位

构造最长公共前后缀数组:

image-20200709224436148

  • 前缀为2,那么必须是前一个串再后面加了一个字符,这个字符的值和前一个串的第二位是相等的(动态规划)

整体算法:

public class KMP {

    private static List<Integer> matches = new ArrayList<>();

    public static int[] prefixTable(String pattern) {

        char[] array = pattern.toCharArray();
        int n = array.length;
        int[] prefix = new int[n];
        prefix[0] = 0;

        // len 表示公共前后缀长度
        int len = 0, i = 1;
        while (i < n) {
            if (array[i] == array[len]) {
                len++;
                prefix[i] = len;
                i++;
            } else {
                if (len > 0) {
                    // 斜着对应
                    // len的值替换为前一位的后缀长度
                    len = prefix[len - 1];
                } else {
                    // i一开始会出现死循环,第一位要置为0,然后i++退出死循环
                    prefix[i] = len;
                    // i++退出循环
                    i++;
                }
            }
        }
        return prefix;
    }

    public static void movePrefixTable(int[] prefix) {
        for (int i = prefix.length - 1; i > 0; i--) {
            prefix[i] = prefix[i - 1];
        }
        prefix[0] = -1;
    }

    public static void kmpSearch(String text, String pattern) {
        // 定义前缀表
        int[] prefix = prefixTable(pattern);
        movePrefixTable(prefix);

        char[] t = text.toCharArray();
        char[] p = pattern.toCharArray();

        int m = t.length, n = p.length;
        // i是母串的游标,j是模式串的游标
        int i = 0, j = 0;

        while (i < m) {
            // 当模式串到达末尾且此时与对应母串的字符相等,代表找到了子串
            if (j == n - 1 && t[i] == p[j]) {
                System.out.println("Found pattern at " + (i - j));
                // 将所有匹配的串的开始位置加入到列表中
                matches.add(i - j);
                // 找到一个不代表找完,还会继续查找
                j = prefix[j];
            }
            // 相等,那么两个游标都向右移动
            if (t[i] == p[j]) {
                i++;
                j++;
            } else {
                // 如果不相等,模式串的游标进行回退操作,根据前缀表进行回退
                // 这个时候母串的游标不移动
                j = prefix[j];
                // 当模式串退到首位时,i、j两者都要+1,这样会j到了0位
                // 模式串整体向右移动一位
                if (j == -1) {
                    i++;
                    j++;
                }
            }
        }

    }

    public static void main(String[] args) {
        String pattern = "ABA";
        String text = "ABAABAABAABC";
        kmpSearch(text, pattern);
        matches.forEach(i -> System.out.print(i + " "));
    }

}

Rabin-Karp指纹识别算法

核心思想:高效计算文本中i+1位置的字符串散列值。通过多位的字符串取模,对比大小进行判定。核心就是计算散列值然后取模比较,总体还是比较简单,KMP比较难

public class RabinKarp {
    private String pat;
    private long patHash;
    private int M;
    private long Q;
    private int R;
    private long RM;

    public RabinKarp(String pat) {
        this.pat = pat;

        this.M = pat.length();
        Q = longRandomPrime();
        RM = 1;
        for (int i = 1; i <= M - 1; i++) {
            RM = (R * RM) % Q; // 减去第一个数字时的计算
        }
        patHash = hash(pat, M);
    }

    private long hash(String key, int m) {
        long h = 0;
        for (int j = 0; j < m; j++)
            h = (R * h + key.charAt(j)) % Q;
        return h;
    }

    private boolean check(int i) {
        return true;
    }

    // a random 31-bit prime
    private static long longRandomPrime() {
        BigInteger prime = BigInteger.probablePrime(31, new Random());
        return prime.longValue();
    }

    private int search(String txt) {
        int N = txt.length();
        long txtHash = hash(txt, M);
        // 一开始就匹配成功
        if (patHash == txtHash && check(0)) return 0;
        for (int i = 1; i < N; i++) {
            // 计算散列值
            //散列值的移位方式,先减去首位,再乘上RM,加上最后一个数字,再执行匹配
            txtHash = (txtHash + Q - RM * txt.charAt(i - M) % Q) % Q;
            txtHash = (txtHash * R + txt.charAt(i)) % Q;
            if (patHash == txtHash) {
                if (check(i - M + 1))
                    return i - M + 1;
            }
        }
        return N; //未找到匹配
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值