字串雜湊(進位制雜湊)
阿新 • • 發佈:2018-12-22
雜湊的過程,其實可以看作對一個串的單向加密過程,並且需要保證所加的密不能高概率重複(就像不能讓隔壁老王輕易地用它家的鑰匙開啟你家門一樣qwq),通過這種方式來替代一些很費時間的操作。
比如,最常見的,當然就是通過雜湊陣列來判斷幾個串是否相同(洛谷p3370)。此處的操作呢,很簡單,就是對於每個串,我們通過一個固定的轉換方式,將相同的串使其的“密”一定相同,不同的串 儘量 不同。
此處有人指出:那難道不能先比對字串長度,然後比對ASCLL碼之和嗎?事實上顯然是不行的(比如ab和ba,並不是同一個串,但是如是做卻會讓其認為是qwq)。這種情況就叫做hash衝突,並且在如此的單向加密雜湊中,hash衝突的情況在所難免(bzoj就有這種讓你給出一組樣例,使得一段雜湊程式碼衝突的題,讀者可以嘗試嘗試)。
而我們此處介紹的,即是最常見的一種雜湊:進位制雜湊。進位制雜湊的核心便是給出一個固定進位制k,將一個串的每一個元素看做一個進位制位上的數字,所以這個串就可以看做一個k進位制的數,那麼這個數就是這個串的雜湊值;則我們通過比對每個串的的雜湊值,即可判斷兩個串是否相同
下面上程式碼(洛谷P3370):
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> using namespace std; #define AA 1000007 typedef unsigned long long ull; int n; char s[AA]; ull a[AA]; ull k=131,prime=233317;//k 與 mod應該為互質的兩個數,prime 是一個大質數 ull mod=212370440130137957ll; ull Hash(char s[]){ int l=strlen(s); ull ans=0; for(int i=0;i<l;i++){ ans=(ans*k+(ull)s[i])%mod+prime; } return ans; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%s",s); a[i]=Hash(s); } sort(a+1,a+1+n); int ans=0; for(int i=1;i<n;i++){ if(a[i]==a[i+1]){ ans++; } } printf("%d",n-ans); return 0; }
雜湊還可以判斷兩個串s1,s2,求s1在s2中出現了多少次
設H(c,k)為前k個字元構成的字串的雜湊值
遞推式:H(c,k+1)= H(c,k) * b + s[k+1]
計算字串c從位置k+1開始的長度為n的子串的雜湊值,可得遞推式:
H(c') = H(c, k + n) - H(c,k) * b^n
程式碼如下:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<string> using namespace std; unsigned long long pow[1000005]; unsigned long long sum [1000005]; string s1; string s2; int main(){ int T,ans = 0; int b = 31; pow[0] = 1; for(int i = 1; i <= 1000000; i++){ pow[i] = pow[i-1] * b; } cin >> T; while(T--){ ans = 0; cin >> s1; getchar(); cin >> s2; int l1 = s1.size(); int l2 = s2.size(); sum[l2] = 0; for(int i = l2 - 1; i >= 0; i--){ sum[i] = sum[i+1] * b + (unsigned long long)(s2[i] - 'A' + 1); } unsigned long long s = 0; for(int j = l1 - 1; j >= 0; j--){ s = s * b + (unsigned long long)(s1[j] - 'A' + 1); } for(int i = 0; i <= l2 - l1; i++){ if(s == sum[i] - sum[i + l1] * pow[l1]) ++ans; } cout << ans << endl; } return 0; }