第五章 字串專題 ---------------- 5.12 字串匹配之PabinKarp

Curtis_發表於2019-03-19

題目:

假如要判斷字串A"ABA"是不是字串B"ABABABA"的子串。

解法一:

暴力破解法, 直接列舉所有的長度為3的子串,然後依次與A比較,這樣就能得出匹配的位置。 這樣的時間複雜度是O(M*N),M為B的長度,N為A的長度。

解法二:

       Rabin-Karp演算法:

  思想:假設待匹配字串的長度為N,目標字串的長度為M(M>N);首先計算待匹配字串的hash值,計算目標字串前N個字元的hash值;比較前面計算的兩個hash值,比較次數M-N+1:若hash值不相等,則繼續計算目標字串的下一個長度為N的字元子串的hash值,若hash值相同,則需要使用比較字元是否相等再次判斷是否為相同的子串(這裡若hash值相同,則直接可以判斷待匹配字串是目標字串的子串,之所以需要再次判斷字元是否相等,是因為不同的字元計算出來的hash值有可能相等,稱之為hash衝突或hash碰撞,不過這是極小的概率,可以忽略不計)。

  雜湊函式定義如下:

  其中Cm表示字串中第m項所代表的特地數字,有很多種定義方法,我習慣於用java自帶的char值,也就是ASCII碼值。java中的char是16位的,用的Unicode編碼,8位的ASCII碼包含在Unicode中。b是雜湊函式的基數,相當於把字串看作是b進位制數。h是防止雜湊值溢位。 

 程式碼:

public class RabinKarp {

    public static void main(String[] args) {
        String s = "ABABABA";
        String p = "ABA";
        match(p, s);
    }
    
    /**
     * @param p 模式
     * @param s 源串
     */
    static void match(String p,String s){
        long hash_p = hash(p);//p的hash值
        int p_len = p.length();
        for (int i = 0; i+p_len<= s.length(); i++) {
            long hash_i = hash(s.substring(i, i+p_len));// i 為起點,長度為p_len的子串的hash值
            if (hash_p==hash_i) {
                System.out.println("match:"+i);
            }
        }
    }

    final static long seed = 31;  // 進位制數
    
    /**
     * 不同的字元計算出來的hash值相同 稱為hash衝突
     * 使用100000個不同字串產生的衝突數,大概在0~3波動,使用100百萬不同的字串,衝突數大概110+範圍波動。
     * @param str
     * @return
     */
    private static long hash(String str) {
        long h = 0;
        for (int i = 0; i !=str.length(); i++) {
            // 這個計算方式就是 An²+Bn+c 的迴圈表示式,而這個計算方式就是二進位制轉十進位制的計算方式 
            // 這裡n=31,可以理解為轉為31進位制
            h = seed * h + str.charAt(i);
            
        }
        return h%Long.MAX_VALUE;  // 防止hash值過大
    }

}

 

相關文章