• 字符串单模式匹配 暴力+哈希


    字符串匹配问题是很经典的问题,在此详细记录一下各种方法。(Java实现)LeetCode28

    给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回  -1。(字符为小写)

    一,BF算法(暴力)

    //1.substring
    class
    Solution { public int strStr(String haystack, String needle) { int h = haystack.length(); int n = needle.length(); for (int i = 0; i <= h - n; i++) { if (haystack.substring(i, i + n).equals(needle)) return i; } return -1; } }
    //2.indexOf
    public int strStr(String haystack, String needle) {
            return haystack.indexOf(needle);
    }

    /**
      indexOf源码:(找到首字母后双指针)
       * @param   source       the characters being searched.      haystack底层数组
         * @param   sourceOffset offset of the source string.       0
         * @param   sourceCount  count of the source string.       haystack.length()
         * @param   target       the characters being searched for.   needle
         * @param   targetOffset offset of the target string.      0
         * @param   targetCount  count of the target string.       needle.length()
         * @param   fromIndex    the index to begin searching from.   0 
    */
    //先在source中找到首字母与target相同的index。再用双指针判定,如果指到末尾则完全匹配,返回索引,
    //否则回溯source下一个字符。(
    只保留重要部分)
    static int indexOf(char[] source, int sourceOffset, int sourceCount,
                char[] target, int targetOffset, int targetCount,
                int fromIndex) {char first = target[targetOffset];
            int max = sourceOffset + (sourceCount - targetCount);
    
            for (int i = sourceOffset + fromIndex; i <= max; i++) {
                /* Look for first character. */
                if (source[i] != first) {
                    while (++i <= max && source[i] != first);
                }
    
                /* Found first character, now look at the rest of v2 */
                if (i <= max) {
                    int j = i + 1;
                    int end = j + targetCount - 1;
                    for (int k = targetOffset + 1; j < end && source[j]
                            == target[k]; j++, k++);
    
                    if (j == end) {
                        /* Found whole string. */
                        return i - sourceOffset;
                    }
                }
            }
            return -1;
        }

    二,RK算法(哈希)

    RK算法的全称叫 Rabin-Karp 算法,是由它的两位发明者 Rabin 和 Karp 的名字来全名的。

     因为字符为小写,可以将主串转换成0~25的数字序列。遍历子串(长度为L)的L个数计算哈希值Hash(needle),再遍历主串(长度为M),

    对所有要匹配的模式串取哈希,边取哈希边与Hash(needle)比较。为了快速对模式串取哈希,利用滑动窗口的特性,每次滑动都有一个元素进,一个出。

    按照将字符映射数字的方式,abcd 整数数组形式就是 [0, 1, 2, 3],转换公式为:h0=0*263+1*262+2*261+3*260。即按从高位到地位的顺序,将其表示成26进制。

    由十进制类比可得,该方式取的哈希值不会产生哈希冲突,因为一个数值用指定的进制只有一种表示方法。

    下面来考虑窗口从 abcd 滑动到 bcde 的情况。这时候模式串从 [0, 1, 2, 3] 变成了 [1, 2, 3, 4],数组最左边的 0 被移除,同时最右边新添了 4。

    滑动后数组的哈希值可以根据滑动前数组的哈希值来计算,计算公式如下所示。h1=(h0-0*263)*26+4*260;写成通式如下所示:h1=(h0*26−c[0]*26L)+c[L]。

    (c为主串数组,L为子串长度)。即Hash(i+1)=(Hash(i)-c[i]*26L-1)*26+c[L+i]*260

    如何避免溢出?  

    L为8的时候,26L-1溢出。因此需要设置数值上限来避免溢出。设置数值上限可以用取模的方式,即用 h % modulus 来代替原本的哈希值。理论上,

    modules 应该取一个很大数,对于这个问题来说 231足够了。

    计算子字符串 haystack.substring(0, L) 和 needle.substring(0, L) 的哈希值。从起始位置开始遍历:从第一个字符遍历到第 N - L 个字符。

    根据前一个哈希值计算滚动哈希。如果子字符串哈希值与 needle 字符串哈希值相等,返回滑动窗口起始位置。返回 -1,这时候 haystack 字符串中不存在 needle 字符串。

    class Solution {
      // function to convert character to integer
      public int charToInt(int idx, String s) {
        return (int)s.charAt(idx) - (int)'a';
      }
    
      public int strStr(String haystack, String needle) {
        int L = needle.length(), n = haystack.length();
        if (L > n) return -1;
    
        // base value for the rolling hash function
        int a = 26;
        // modulus value for the rolling hash function to avoid overflow
        long modulus = (long)Math.pow(2, 31);
    
        // compute the hash of strings haystack[:L], needle[:L]
        long h = 0, ref_h = 0;
        for (int i = 0; i < L; ++i) {
          h = (h * a + charToInt(i, haystack)) % modulus;
          ref_h = (ref_h * a + charToInt(i, needle)) % modulus;
        }
        if (h == ref_h) return 0;
    
        // const value to be used often : a**L % modulus
        long aL = 1;
        for (int i = 1; i <= L; ++i) aL = (aL * a) % modulus;
    
        for (int start = 1; start < n - L + 1; ++start) {
          // compute rolling hash in O(1) time
          h = (h * a - charToInt(start - 1, haystack) * aL
                  + charToInt(start + L - 1, haystack)) % modulus;
          if (h == ref_h) return start;
        }
        return -1;
      }
    }

     参考链接

  • 相关阅读:
    Python3之json模块
    How To Enable EPEL Repository in RHEL/CentOS 7/6/5?
    安装CentOS 6.x出现Disk sda contains BIOS RAID metadata
    详解hdparm: linux下的硬盘测速工具
    {转载}需要同时设置 noatime 和 nodiratime 吗?
    ubuntu17.10安装LAMP并测试部署php探针系统
    shell监控网卡状态,故障时自动重启网卡
    L2TP/IPSec一键安装脚本
    Linux系统下用find命令查找最近修改过的文件
    Hyper-V 手动导入虚机配置实例(转载)
  • 原文地址:https://www.cnblogs.com/faded828x/p/13156549.html
Copyright © 2020-2023  润新知