• java实现字符串匹配之Rabin-Karp算法


    前言

    字符串匹配就是求一个子串在给定字符串的起始位置。我们先用暴力解法实现,然后在此基础上优化成Rabin-Karp算法。

    暴力解法

    public interface StringMatcher {
    
      int indexOf(String source, String target);
    }
    
    
    /**
     * 暴力解法
     */
    public class BruteForceStringMatcher implements StringMatcher {
    
    
      @Override
      public int indexOf(String source, String target) {
        if (target.length() == 0) {
          return 0;
        }
        if (source.length() < target.length()) {
          return -1;
        }
        char[] sourceArr = source.toCharArray();
        char[] targetArr = target.toCharArray();
        for (int i = 0; i < source.length(); i++) {
          if (equals(sourceArr, i, i + targetArr.length - 1, targetArr)) {
            return i;
          }
        }
        return -1;
      }
    
      private boolean equals(char[] source, int start, int end, char[] target) {
        if (end >= source.length) {
          return false;
        }
        for (int i = 0; i <= end - start; i++) {
          if (source[i + start] != target[i]) {
            return false;
          }
        }
        return true;
      }
    
    }
    

    在主串中,检查起始位置分别是 0、1、2…n-m 且长度为 m 的 n-m+1 个子串,看有没有跟模式串匹配的。

    Rabin-Karp算法

    /**
     * Rabin-Karp算法,将子串的比较转换成子串哈希值的比较
     */
    public class RabinKarpStringMatcher implements StringMatcher {
    
      /**
       * 表示进制,参考go语言中Rabin-Karp算法实现中的值
       */
      private static final int R = 16777619;
      /**
       * 哈希值可能太大,取模,随机值BigInteger.probablePrime(31, new Random())
       */
      private static final long Q = 1538824213;
    
      @Override
      public int indexOf(String source, String target) {
        int targetLen = target.length();
        int sourceLen = source.length();
    
        if (targetLen == 0) {
          return 0;
        }
        if (sourceLen < targetLen) {
          return -1;
        }
        long RM = initRM(target);
        long targetHash = hash(target, 0, targetLen - 1);
        int index = 0;
        long sourceHash = 0;
        while (index <= sourceLen - targetLen) {
          // 开始比较
          sourceHash = nextHash(source, target, index, sourceHash, RM);
          if (sourceHash == targetHash) {
            if (equals(source, index, index + targetLen - 1, target)) {
              return index;
            }
          }
          index++;
        }
        return -1;
      }
    
      private long nextHash(String source, String target, int index, long preHash, long RM) {
        int targetLen = target.length();
        if (index == 0) {
          return hash(source, 0, targetLen - 1);
        }
        long hash = preHash;
        // 去掉第一个字符的hash值
        hash = mod(hash - mod(RM * source.charAt(index - 1)));
        // 加上下一个字符的hash值
        hash = mod(mod(hash * R) + source.charAt(index + targetLen - 1));
        return hash;
      }
    
      private long hash(String str, int start, int end) {
        long hash = 0;
        for (int i = start; i <= end; i++) {
          hash = mod(hash * R + str.charAt(i));
        }
        return hash;
      }
    
      private long mod(long hash) {
        if (hash < 0) {
          return hash + Q;
        }
        return hash % Q;
      }
    
      private long initRM(String target) {
        long RM = 1;
        for (int i = 1; i < target.length(); i++) {
          RM = (R * RM) % Q;
        }
        return RM;
      }
    
      private boolean equals(String source, int start, int end, String target) {
        if (end >= source.length()) {
          return false;
        }
        for (int i = 0; i <= end - start; i++) {
          if (source.charAt(i + start) != target.charAt(i)) {
            return false;
          }
        }
        return true;
      }
    
    }
    

    暴力解法的瓶颈在于子串的比较,需要每个字符依次比较,Rabin-Karp算法将其转换成哈希值的比较,当然哈希值可能有冲突,在哈希值相等的情况下,还需要再进行一次字符串的朴素比较。关于Rabin-Karp算法的详细介绍,可以看 基础知识 - Rabin-Karp 算法 这篇博客,【go源码分析】strings.go 里的那些骚操作 这篇文章解释了为什么go语言没有显式的对hash值取模。

    参考

    基础知识 - Rabin-Karp 算法
    子字符串查找----Rabin-Karp算法(基于散列)
    Rabin-Karp算法github开源实现
    Rabin-Karp在go语言中的实现
    【go源码分析】strings.go 里的那些骚操作

  • 相关阅读:
    Labeling Balls
    Following Orders
    Frame Stacking
    Window Pains
    Sort it all out
    Ferry Loading||
    今年暑假不AC
    xcode10 出现 框架 或者 pod 出错
    网络请求 步骤
    swift UIAlertController使用 UIAlertController的宽度 为270
  • 原文地址:https://www.cnblogs.com/strongmore/p/14737057.html
Copyright © 2020-2023  润新知