1. 程式人生 > >hash詳解

hash詳解

ring 說明 真的 scanf 相同 orange memset ems 小寫字母

首先介紹一下hash?

事實上是一種叫做蛤絲的病毒

hash的做法:

首先設一個進制數base,並設一個模數mod

而哈希其實就是把一個數轉化為一個值,這個值是base進制的,儲存在哈希表中,註意一下在存入的時候取模一下即可

比如說現在有一個字符串orzc

枚舉這個字符串的每一位,與base相乘得到ans,然後mod一下,就得到orzc的哈希值

但是哈希有一個很大的弊端:

哈希沖突

什麽是哈希沖突呢?

就比如說orzc的哈希值是233,而orzhjw的哈希值也是233

那麽我們在查詢的時候代碼會認為這兩個字符串是相同的,但顯然這兩個字符串是不同的

減少哈希沖突的方法很多

自然溢出法,雙哈希之類的

看一道例題理解一下

洛谷P3370 【模板】字符串哈希

題目描述

如題,給定N個字符串(第i個字符串長度為Mi,字符串內包含數字、大小寫字母,大小寫敏感),請求出N個字符串中共有多少個不同的字符串。

友情提醒:如果真的想好好練習哈希的話,請自覺,否則請右轉PJ試煉場:)

輸入輸出格式

輸入格式:

第一行包含一個整數N,為字符串的個數。

接下來N行每行包含一個字符串,為所提供的字符串。

輸出格式:

輸出包含一行,包含一個整數,為不同的字符串個數。

輸入輸出樣例

輸入樣例#1: 復制
5
abc
aaaa
abc
abcc
12345
輸出樣例#1: 復制
4

說明

時空限制:1000ms,128M

數據規模:

對於30%的數據:N<=10,Mi≈6,Mmax<=15;

對於70%的數據:N<=1000,Mi≈100,Mmax<=150

對於100%的數據:N<=10000,Mi≈1000,Mmax<=1500

樣例說明:

樣例中第一個字符串(abc)和第三個字符串(abc)是一樣的,所以所提供字符串的集合為{aaaa,abc,abcc,12345},故共計4個不同的字符串。

Tip: 感興趣的話,你們可以先看一看以下三題:

BZOJ3097:http://www.lydsy.com/JudgeOnline/problem.php?id=3097

BZOJ3098:http://www.lydsy.com/JudgeOnline/problem.php?id=3098

BZOJ3099:http://www.lydsy.com/JudgeOnline/problem.php?id=3099

如果你仔細研究過了(或者至少仔細看過AC人數的話),我想你一定會明白字符串哈希的正確姿勢的^_^

事實上如果理解了剛剛講的hash的原理的話,這道題就很水了,因為本來就是模板題

用一段hash的代碼再來鞏固一下剛才的知識

#define base 233
#define inf 1<<30
ull mod=inf;
//定義一個大數(最好是質數)作為模數,這裏用的是1<<30
//定義一個base進制,這裏是233
il ull hash(char s[]){
    ll ans=0,len=strlen(s);
    for(ll i=0;i<len;i++){
        ans=(base*ans+(ull)s[i])%mod;
    }
    return ans;
    //枚舉該字符串的每一位,與base相乘,轉化為base進制,加(ull)是為了防止爆棧搞出一個負數,(ull)是無符號的,但其實加了一個ull是可以不用mod的,加個mod更保險
    //然而加了mod會很玄學,莫名比不加mod慢了300多ms
}

因為懶就沒有去找一個大質數來當mod,用了1<<30代替,但是最好還是找一個大質數當mod(搜索一下生日悖論?大概就會明白原因了)

最後貼一下剛剛的例題的兩種解法:

解法1:單hash/自然溢出法

這裏就當一種解法來說吧

因為代碼差異不大

這道題的話單hash mod開大質數是可以過的,但是在大多數難一些的題目裏面是會被卡掉的#include <cstdio>

#include <cstring>
#include <algorithm>
#define ll int
#define inf 1<<30
#define mt(x,y) memset(x,y,sizeof(x))
#define il inline 
#define ull unsigned long long
il ll max(ll x,ll y){return x>y?x:y;}
il ll min(ll x,ll y){return x<y?x:y;}
il ll abs(ll x){return x>0?x:-x;}
il ll swap(ll x,ll y){ll t=x;x=y;y=t;}
il void read(ll &x){
    x=0;ll f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
using namespace std;
#define N 10001
#define base 233
ull mod=212370440130137957ll;
ll f[N],n;
char a[N];
//ull hash(char s[]){ ll ans=0,len=strlen(s); for(ll i=0;i<len;i++){ ans=((base*ans+(ull)s[i])+mod)%mod; } return ans; }
//這個是單hash+大質數mod,也是可以過的,但是會比較慢
ull hash(char s[]){//自然溢出 ull ans=0,len=strlen(s); for(ll i=0;i<len;i++){ ans=base*ans+(ull)s[i]; //這裏不使用mod讓它自然溢出,定義為ull的數在超過2^32的時候會自然溢出 //如果把這個換成上面的hash就會400ms+ //所以說自然溢出大法好 } return ans; } int main(){ read(n); for(ll i=1;i<=n;i++){ scanf("%s",a); f[i]=hash(a); } sort(f+1,f+n+1);ll ans=1; for(ll i=1;i<n;i++){ if(f[i]!=f[i+1])ans++; } printf("%d\n",ans); return 0; }

解法2:雙hash

其實就是用兩個不同的mod來算hash,哈希沖突的概率是降低了很多,不過常數大,容易被卡,這道題要700ms+

本人還是更推薦自然溢出法

#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll int
#define inf 1<<30
#define mt(x,y) memset(x,y,sizeof(x))
#define il inline 
#define ull unsigned long long
il ll max(ll x,ll y){return x>y?x:y;}
il ll min(ll x,ll y){return x<y?x:y;}
il ll abs(ll x){return x>0?x:-x;}
il ll swap(ll x,ll y){ll t=x;x=y;y=t;}
il void read(ll &x){
    x=0;ll f=1;char c=getchar();
    while(c<0||c>9){if(c==-)f=-f;c=getchar();}
    while(c>=0&&c<=9){x=x*10+c-0;c=getchar();}
    x*=f;
}
using namespace std;
#define N 10001
#define base 233
ull mod1=212370440130137957ll;
ull mod2=inf;
ll n;
char a[N];
struct node{ll x,y;}f[N];
il ull hash1(char s[]){
    ll ans=0,len=strlen(s);
    for(ll i=0;i<len;i++){
        ans=(base*ans+(ull)s[i])%mod1;
    }
    return ans;
}
il ull hash2(char s[]){
    ll ans=0,len=strlen(s);
    for(ll i=0;i<len;i++){
        ans=(base*ans+(ull)s[i])%mod2;
    }
    return ans;
}
il bool cmp1(node a,node b){return a.x<b.x;}
il bool cmp2(node a,node b){return a.y<b.y;}
int main(){
    read(n);
    for(ll i=1;i<=n;i++){
        scanf("%s",a);
        f[i].x=hash1(a);
        f[i].y=hash2(a);
    }
    sort(f+1,f+n+1,cmp1);sort(f+1,f+n+1,cmp2);
    ll ans=1;
    for(ll i=1;i<n;i++){
        if(f[i].x!=f[i+1].x||f[i].y!=f[i+1].y)ans++;
    }
    printf("%d\n",ans);
    return 0;
}

這道題也是可以打字典樹的,rank1的dalao就是用的字典樹,不過就不介紹了,其他的題解裏面也有,況且既然是哈希模板,就好好打哈希咯233

我是不會承認因為我懶而且不是很懂字典樹才不放字典樹做法的

hash詳解