zoukankan      html  css  js  c++  java
  • 字符串单模式匹配 暴力+哈希

    字符串匹配问题是很经典的问题,在此详细记录一下各种方法。(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;
      }
    }

     参考链接

  • 相关阅读:
    51 Nod 1086 多重背包问题(单调队列优化)
    51 Nod 1086 多重背包问题(二进制优化)
    51 Nod 1085 01背包问题
    poj 2559 Largest Rectangle(单调栈)
    51 Nod 1089 最长回文子串(Manacher算法)
    51 Nod N的阶乘的长度 (斯特林近似)
    51 Nod 1134 最长递增子序列(经典问题回顾)
    51 Nod 1020 逆序排列
    PCA-主成分分析(Principal components analysis)
    Python中cPickle
  • 原文地址:https://www.cnblogs.com/faded828x/p/13156549.html
Copyright © 2011-2022 走看看