1. 程式人生 > >poj1200 字符串hash 滾動哈希初探

poj1200 字符串hash 滾動哈希初探

arp 下一個 字符串轉換 ada 一個 problem public int abi

假如要判斷字符串A“AABA”是否是字符串B“AABAACAADAABAABA”的子串

技術分享

最樸素的算法是枚舉B的所有長度為4的子串,然後逐個與A進行對比,這樣的時間復雜度是O(mn),m為A的長度,n為B的長度。

另一個做法是用哈希函數計算出A的哈希值,然後計算出B所有長度為4的子串的哈希值,這樣比較就可以判斷出A是否在B中。雖然這樣做的時間復雜度還是O(mn),但是為接下來的滾動哈希打下了基礎。

Rabin-Karp算法采用了一種叫做滾動哈希的技巧,對哈希函數的類型有要求。

Rabin-Karp算法的思想:

  1. 假設待匹配字符串的長度為M,目標字符串的長度為N(N>M);
  2. 首先計算待匹配字符串的hash值,計算目標字符串前M個字符的hash值;
  3. 比較前面計算的兩個hash值,比較次數N-M+1:
    • 若hash值不相等,則繼續計算目標字符串的下一個長度為M的字符子串的hash值
    • 若hash值相同,則需要使用樸素算法再次判斷是否為相同的字串;

哈希函數定義如下。

其中Cm表示字符串中第m項所代表的特地數字,有很多種定義方法,我習慣於用java自帶的char值,也就是ASCII碼值。java中的char是16位的,用的Unicode編碼,8位的ASCII碼包含在Unicode中。

b是哈希函數的基數,相當於把字符串看作是b進制數。

h是防止哈希值溢出。

技術分享

滾動哈希的技巧就是:如果已經算出從k到k+m的子串的哈希值H(S[k,k+1...k+m]),那麽從k+1到k+m+1的子串的哈希值就可以基於前一個的哈希值計算得出

技術分享

在不考慮哈希碰撞的前提下,Rabin-Karp算法的時間復雜度就是O(m+n)。

那麽來看看POJ 1200

題目大意是有一個字符串,其中有NC種不同的字符,求出其長度為N的不同子串的個數。

技術分享

這個題目的本意是要利用這個NC,但是我這裏只是為了訓練滾動哈希,就沒有用NC。可以把哈希函數的基數b設為NC,就相當於把字符串轉換成了NC進制

 1 import java.util.HashSet;
 2 import java.util.Scanner;
 3 
 4 
 5 public class test {
 6 
 7     static long pat;    //原始長度為n的子串的哈希值
8 static long next; //右移一位子串的哈希值 9 static long B = 100000007; //哈希函數基數 10 static long max = Integer.MAX_VALUE; //取模防止哈希值溢出 11 12 public static void main(String[] args) { 13 14 HashSet<Long> res = new HashSet<Long>(); //把子串的哈希值放入hashset中,hashset的size就是所求子串個數 15 Scanner scanner = new Scanner(System.in); 16 17 int n = scanner.nextInt(); 18 int nc = scanner.nextInt(); 19 scanner.nextLine(); 20 String buff = scanner.nextLine(); 21 char[] s = buff.toCharArray(); 22 23 long t = 1; 24 25 //計算出B的n次方 26 for (int i = 0; i < n; i++) { 27 t = (t * B) % max; 28 } 29 30 //計算出第一個子串的哈希值 31 for (int i = 0; i < n; i++) { 32 pat = (pat * B + s[i]) % max; 33 } 34 next = pat; 35 res.add(next); 36 37 //沒有考慮哈希沖突,直接右移計算 38 for (int i = 0; i + n < s.length; i++) { 39 next = (next * B + s[i + n] - s[i] * t) % max; 40 res.add(next); 41 } 42 43 System.out.println(res.size()); 44 } 45 }

poj1200 字符串hash 滾動哈希初探