1. 程式人生 > >hash 散列表

hash 散列表

mod clas 復雜度 style fin tdi ble 查詢 cstring

一個字符串的hash值:

?現在我們希望找到一個hash函數,使得每一個字符串都能夠映射到一個整數上 ?比如hash[i]=(hash[i-1]*p+idx(s[i]))%mod ?字符串:abc,bbc,aba,aadaabac ?字符串下標從0開始 ?先把a映射為1,b映射為2,c->3,d->4,即idx(a)=1, idx(b)=2, idx(c)=3,idx(d)=4; ?好!開始對字符串進行hash

假設我們取p=13 ,mod=101

先把abc映射為一個整數

hash[0]=1,表示 a 映射為1

hash[1]=(hash[0]*p+idx(b))%mod=15,表示 ab 映射為 15

hash[2]=(hash[1]*p+idx(c))%mod=97

這樣,我們就把 abc 映射為 97 這個數字了。

?用同樣的方法,我們可以把bbc,aba,aadaabac都映射到一個整數 ?用同樣的hash函數,得到如下結果 ? abc -> 97 ? bbc -> 64 ? aba -> 95 ? aadaabac -> 35 ?那麽,我們發現,這是一個字符串到整數的映射 ?這樣子,我們就可以記錄下每個字符串對應的整數,當下一次出現了一個已經出現的字符串時,查詢整數是否出現過,就可以知道 字符串是否重復出現。 ?現在要判斷兩個字符串是否一致,怎麽辦呢?直接用它們的hash值判斷即可,若hash值一致,則認為字符串一致;若hash值不一致,則認為是不同的字符串。 ?我們要判斷兩個字符串是否一致,沒有那麽麻煩,直接先判斷長度是否一致,然後再判斷每個對應的字符是否一致即可。 ?但,如果要判斷多個字符串裏有多少個不同的字符串,怎麽辦呢? ?兩兩字符串都進行比較?時間復雜度太高 ?把每個字符串hash成一個整數,然後把所有整數進行一個去重操作,即可知道答案了。 當遇到沖突時,我們可以想辦法調整p和mod,使得沖突概率減小之又小。我們一般認為p和mod一般取素數,p取一個較大的素數即可(6位到8位),mod取一個大素數,比如1e9+7,或者1e9+9。 如何求一個子串的hash值? ?在之前,我們求出了hash[i],表示第i個前綴的hash值。現在怎麽求出每個子串的

hash值呢?

?我們看下hash的公式: ? hash[i]=(hash[i-1]*p+idx(s[i]))%mod ?這表示第 i 個前綴的hash值,是一個hash的前綴和。 ?hash[i]=(hash[i-1]*p+idx(s[i]))%p; ?那麽,我要求S[l…r]這個子串的hash值 ? hash[l..r]=(hash[r]-hash[l-1]*(p^(r-1+1)))%mod(假設字符串下標從1開始) ?但註意下取模時候的問題! ?hash[l..r]=(hash[r]-hash[l-1]*(p^(r-1+1)))%mod ? hash[l..r]是不是可能有負數? ?怎麽辦呢?當得到的hash[l..r]<0的時候,hash[l..r]+=mod,就好啦。 ?這樣就可以保證每個子串的hash值在[0, mod-1]的範圍內,準確地用hash值來處理字符串 常用的幾個字符串hash法 ?1. unsigned long long hash[N]; hash[i]=hash[i-1]*p(自動取模) 解釋:

unsigned long long hash[N];

定義一個unsigned long long類型的變量,它的範圍是在[0, 2^64) 內,這就相當於,當數超不過2^64-1後,它會溢出!這就相當於一個數模2^64的過程。

那麽hash函數可以理解為:

hash[i]=(hash[i-1]*p)%(2^64)

P取一個大素數,一般習慣取1e9+7或1e9+9

安全指數:三星(所以並不是很安全)

?2. hash[i]=(hash[i-1]*p+idx(s[i]))%mod 解釋:

這個之前已經提到過了。

hash[i]=(hash[i-1]*p+idx(s[i]))%mod

p取一個6到8位的素數,mod取一個大素數,一般取1e9+7或1e9+9 安全指數:四星 (還可以) ?3. 雙hash

hash1[i]=(hash1[i-1]*p+idx(s[i]))%mod1

hash2[i]=(hash2[i-1]*p+idx(s[i]))%mod2

pair<hash1,hash2>表示一個字符串!

解釋:

double hash 即取兩個mod值,mod1和mod2

hash1[i]=(hash1[i-1]*p+idx(s[i]))%mod1

hash2[i]=(hash2[i-1]*p+idx(s[i]))%mod2

mod1一般取1e9+7,mod2一般取1e9+9為什麽這麽取?

1000000007和1000000009是一對孿生素數,取它們,沖突的概率極低!

安全指數:五星!(非常穩!) 小結: ?可以這麽說,hash某種程度上就是亂搞,把hash函數弄的越沒有規律越好,使得沖突的概率小到 大部分數據都卡不掉。 ?如果你開心,你想triple hash,ultra hash,rampage hash… 都沒有問題!

但請註意,hash的維度越高,耗時越高,耗內存越大!一般情況下,single hash可以被hack掉,但double hash極難被hack掉, 用double hash足以解決問題

根據hash函數去求得一段區間的的 hash 值

#include <cstdio>
#include <cstring>
using namespace std;
#define ll unsigned long long
const ll maxn = 1e6+5;
 
 
char s1[maxn], s2[maxn];
ll p = 100007;
ll hash[maxn];
 
ll pp[maxn];
void init() {
    pp[0] = 1;
    for(int i = 1; i <= 1000; i++) {
        pp[i] = pp[i-1]*p;
    }
}
 
int main() {
    scanf("%s%s", s1, s2);
    ll len1 = strlen(s1);
    ll len2 = strlen(s2);
    init();
     
    ll hash_1 = 0;
    for(ll i = 0; i < len1; i++){
        hash_1 = hash_1*p+(s1[i]-a);
    }
    printf("hash_1 = %llu\n",hash_1);
     
    for(ll i = 0; i < len2; i++){
        hash[i] = hash[i-1]*p+(s2[i]-a);
        printf("+++ i = %llu -> %llu\n", i, hash[i]);
    }
    ll ans = hash[5]-hash[2]*pp[3];
    printf("%llu \n", ans);
    return 0;
}
/*
qwe
rasqwe
*/

hash 散列表