字符串匹配RK(RabinKarp)算法

思想

  • 设: n是主串长度,m是模式串长度
  • RK算法如果想要实现,必须满足以下几个条件:
  1. 单个字符计算hash值的函数为hc(c),复杂度为O(1)
  2. 多个字符(c1,c2,c3,…,cn)计算hash值的函数设为hstr(c1,c2,c3,...,cn) = hashhc(hc(c1),hc(c2),hc(c3),....,hc(cn)),hashhc是通过各个字符的hash值来计算整个字符串的哈希值的函数,复杂度为O(n),且不太容易发生hash冲突
  3. 计算(c2,c2,c3,…,c(n+1))的hash值的函数为hnext(hstr(c1,c2,c3,...,cn),hc(n+1)),若hstr(c1,c2,c3,…,cn)已知则复杂度为O(1)
  • 当设计一个hash算法,可以满足以上条件,则RK算法时间复杂度为O(n+m)
  • 如果hash冲突异常频繁,最差会下降到O(n*m)

简易的hash值实现

  • hc(c)= c的utf-8编码值
  • hstr(c1,c2,c3,...,cn) = hashhc(hc(c1),hc(c2),hc(c3),....,hc(cn)) = hc(c1)+hc(c2)+hc(c3)+...+hc(cn)
  • hnext(hstr(c1,c2,c3,...,cn),hc(n+1)) = hstr(c1,c2,c3,...,cn) - hc(c1) + hc(n+1)

java实现

/**
 * RK算法:
 *
 * @author 18118224_周亿进
 * @since 2020/4/1
 */
public class RabinKarpDemo {

    public static void main(String[] args) {
        System.out.println(indexOf("1234567890", "6789"));
    }

    static int indexOf(String str, String regex) {
        return indexOf(str.toCharArray(), regex.toCharArray());
    }

    /**
     * RK算法:
     * 匹配模式串在主串中的位置
     * <p>
     * 这个算法的难度在于设计hash算法,
     * 假如我们的字符集是自己定义的,只会26位 0-25各代表一个字母
     * 那么我们完全可以将hash值作为一个26进制的数字,每一位代表一个字母,非常高效
     * 但是如果我们字符集是utf-8那么就不能这么简单的去设计了.
     * <p>
     * 所以我们是无法完全避免hash冲突的问题,那么我们就需要将hash值计算得足够分散,这就要靠大佬了,我是不行
     * <p>
     * 这里要设计一个好的hash算法才能提升此算法的效率
     * 好的hash算法会让hash冲突减少,如果hash冲突非常多,复杂度还是会上升到O(n*m)
     * 如果没有hash冲突,则复杂度是O(n),因为只对每个字符(char)计算了一次hash就知道所有匹配串的hash值
     *
     * @param str   主串
     * @param regex 模式串
     * @return
     */
    static int indexOf(char[] str, char[] regex) {
        int n = str.length;//主串的长度设为n
        int m = regex.length;//模式串的长度设为m
        //1. 计算主串和模式串各个字符的hash值
        int[] strCharsHashArray = hashArray(str);
        int[] regexCharsHashArray = hashArray(regex);
        //2. 计算模式串hash值
        int regexHash = hashhc(regexCharsHashArray);
        //3. 计算str第一个匹配位置的hash值 todo hash计算的算法会影响hash冲突的概率
        int compareStrHash = hashhc(strCharsHashArray,0,m);
        //4. 比较hash,i是str的下标
        for (int i = 0; i < (n - m); i++) {
            if (compareStrHash == regexHash) {
                //5. 检查是否匹配上,排除hash冲突的情况
                if (compare(str, regex, i)) {
                    //匹配上直接返回下标
                    return i;
                }
            }
            //如果是最后一次,则不用计算了
            if (i < n - m) {
                //6. 计算后移一位的hash值 todo hash计算的算法会影响hash冲突的概率
                compareStrHash = hnext(strCharsHashArray, compareStrHash, i, m);
            }
        }
        return -1;
    }

    //计算下一位hash的方法
    //strCharsHash主串上各个位置的hash值
    //compareStrHash现在正在匹配的hash值
    //i现在正在匹配的str,第一位字符的下标
    //m模式串的长度
    //返回的是模式串在主串上后移一位的候选字符串hash值
    //时间复杂度O(1)
    private static int hnext(int[] strCharsHash, int compareStrHash, int i, int m) {
        return compareStrHash - strCharsHash[i] + strCharsHash[i + m];
    }

    //比较str从下标i开始和regex是否匹配上了,解决hash冲突的问题
    private static boolean compare(char[] str, char[] regex, int i) {
        for (int j = 0; j < regex.length; j++) {
            if (str[i + j] != regex[j]) {
                return false;
            }
        }
        return true;
    }

    //start include
    //end exclude
    //已知主串上各个字符的hash值(charsHash)
    //返回start~end位置字符串的hash值
    static int hashhc(int[] hashArray, int start, int end) {
        int hash = 0;
        for (int i = start; i < end; i++) {
            hash += hashArray[i];
        }
        return hash;
    }

    static int hashhc(int[] charsHash) {
        return hashhc(charsHash, 0, charsHash.length);
    }

    //计算出每个字符的hash值
    static int[] hashArray(char[] str) {
        int length = str.length;
        //先计算每个字符的hash值,先搞个简单设计
        int[] charHash = new int[length];
        for (int i = 0; i < length; i++) {
            charHash[i] = hc(str[i]);
        }
        return charHash;
    }

    //字符计算hash值的简单实现
    static int hc(char c) {
        return c;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值