字符串哈希入門
說得通俗一點,字符串哈希實質上就是把每個不同的字符串轉成不同的整數。
為什么會有這樣的需要呢?很明顯,存儲一個超長的字符串和存儲一個超大但是能存的下的整數,后者所占的空間會少的多,但主要還是為了方便判斷一個字符串是否出現過,這是最基礎的部分。
當然也很容易想到,如果有不同的字符串轉成同一個整數,那么區分功能就基本廢掉 ,所以我們需要一個算法把每個字符串轉成唯一的整數。所以字符串哈希算法就應運而生,哈希算法的難點也就在於如何構造一個合適的Hash函數來滿足我們的需求。
下面就簡單介紹幾種字符串哈希的基本方法。
基本哈希方法
一般地,給定一個字符串 \(S=s_1s_2s_3s_4...s_n\),令\(idx(x)=x-'a'+1\),當然,直接(int)x(用它的ASCll碼)也一樣。
自然溢出法
這種方法是利用數據結構unsigned long long
的范圍自然溢出:即當存儲的數據大於unsigned long long
的存儲范圍時,會自動mod \(2^{64}-1\),就不用mod其他質數來保證唯一性了。
Hash公式
unsigned long long Hash[n]
hash[i]=hash[i−1]∗p+idx(s[i]);
注意:這里的p一定要是個質數,不然可能無法保證唯一性。
單Hash法
相當於自然溢出法沒有了自動取模的操作,所以需要自己進行取模操作。但是這種Hash方法在模數較小的時候的穩定性不一定得到保證,所以在這個方面不如其他方法。
Hash公式
hash[i]=(hash[i−1])∗p+idx(s[i])%mod;
注意:這里的\(p\)和\(mod\)都是質數,且滿足\(p<mod\)。最好在選取的時候把\(p\)和\(mod\)的值取大一點。
舉例
如取\(p=13,mod=101\),對字符串\(abc\)進行Hash
hash[0]=1;
hash[1]=(hash[0] × 13 + 2)%101=15;
hash[2]=(hash[1] × 13 + 3)%101=97;
所以最終字符串\(abc\)的hash值就是97
雙Hash法
其實網上很多博客講了多Hash,但我覺得雙Hash已經足夠穩定了,再多一些也只是浪費時間而已。
顧名思義,雙Hash就是對一個hash值用兩個不同的質數進行兩次\(mod\)操作,然后最后用一對數\(<hash1[n],hash2[n]>\)來表示一個字符串的哈希值,這樣的一對數的重復幾率加上選擇較大的質數,沖突率幾乎為0。
Hash方法
hash1[i]=(hash1[i−1])∗p+idx(s[i]) % mod1
hash2[i]=(hash2[i−1])∗p+idx(s[i]) % mod2
這樣的哈希很安全
Hash素數的選擇
為了防止沖突,要選擇合適的素數,像1e9+7,1e9+9的一些素數,出題人一般會卡一下下,所以盡量選擇其他的素數,防止被卡。下面是一些可供選擇的素數。
上界和下界指的是離素數最近的\(2^n\)的值。
lwr | upr | % err | prime |
2^5 | 2^6 | 10.416667 | 53 |
2^6 | 2^7 | 1.041667 | 97 |
2^7 | 2^8 | 0.520833 | 193 |
2^8 | 2^9 | 1.302083 | 389 |
2^9 | 2^10 | 0.130208 | 769 |
2^10 | 2^11 | 0.455729 | 1543 |
2^11 | 2^12 | 0.227865 | 3079 |
2^12 | 2^13 | 0.113932 | 6151 |
2^13 | 2^14 | 0.008138 | 12289 |
2^14 | 2^15 | 0.069173 | 24593 |
2^15 | 2^16 | 0.010173 | 49157 |
2^16 | 2^17 | 0.013224 | 98317 |
2^17 | 2^18 | 0.002543 | 196613 |
2^18 | 2^19 | 0.006358 | 393241 |
2^19 | 2^20 | 0.000127 | 786433 |
2^20 | 2^21 | 0.000318 | 1572869 |
2^21 | 2^22 | 0.000350 | 3145739 |
2^22 | 2^23 | 0.000207 | 6291469 |
2^23 | 2^24 | 0.000040 | 12582917 |
2^24 | 2^25 | 0.000075 | 25165843 |
2^25 | 2^26 | 0.000010 | 50331653 |
2^26 | 2^27 | 0.000023 | 100663319 |
2^27 | 2^28 | 0.000009 | 201326611 |
2^28 | 2^29 | 0.000001 | 402653189 |
2^29 | 2^30 | 0.000011 | 805306457 |
2^30 | 2^31 | 0.000000 | 1610612741 |
獲取子串的hash
如果我們求出一個串的Hash,就可以\(O(1)\)求解其子串的Hash值。
公式的推導太復雜...干脆直接貼上來 (絕對不是我想偷懶)
公式
若已知一個\(|S|=n\)的字符串的hash值,\(hash[i]\),\(1≤i≤n\),其子串\(sl..sr,1≤l≤r≤n\),對應的hash值為:
ov.